二叉树学习(上)
一:树
我们思维中的”树“就是一种枝繁叶茂的形象,那么数据结构中的”树“该是怎么样呢?对的,他是一种现实中倒立的树。
1:术语
其实树中有很多术语的,这个是我们学习树形结构必须掌握的。
<1> 父节点,子节点,兄弟节点
这个就比较简单了,B和C的父节点就是A,反过来说就是B和C是A的子节点。B和C就是兄弟节点。
<2> 结点的度
其实”度“就是”分支数“,比如A的分支数有两个“B和C",那么A的度为2。
<3> 树的度
看似比较莫名其妙吧,他和”结点的度“的区别就是,树的度讲究大局观,乃树中最大的结点度,其实也就是2。
<4> 叶结点,分支结点
叶结点就是既没有左孩子也没有右孩子结点,也就是结点度为0。分支节点也就是if的else的条件咯。
<5> 结点的层数
这个很简单,也就是树有几层。
<6> 有序树,无序树
有序树我们先前也用过,比如“堆”和“二叉排序树”,说明这种树是按照一定的规则进行排序的,else条件就是无序树。
<7> 森林
现实中,很多的树形成了森林,那在数据结构中,我们把上图的“A”节点砍掉,那么B,C子树合一起就是森林咯。
2: 树的表示
树这个结构的表示其实有很多种,常用的也就是“括号”表示法。
比如上面的树就可以表示为:(A(B(D),(E)),(C(F),(G)))
二: 二叉树
在我们项目开发中,很多地方都会用到树,但是多叉树的处理还是比较纠结的,所以俺们本着“大事化小,小事化了“的原则
把”多叉树“转化为”二叉树“,那么问题就简化了很多。
1: ”二叉树“和”树“有什么差异呢?
第一点: 树的度没有限制,而“二叉树”最多只能有两个,不然也就不叫二叉树了,哈哈。
第二点:树中的子树没有左右划分,很简单啊,找不到参照点,二叉树就有参照物咯。
2: 二叉树的类型
二叉树中有两种比较完美的类型,“完全二叉树”和“满二叉树”。
<1> 满二叉树
除叶子节点外,所有节点的度都为2,文章开头处的树就是这里的“满二叉树”。
<2> 完全二叉树
必须要满足两个条件就即可: 干掉最后一层,二叉树变为“满二叉树”。
最后一层的叶节点必须是“从左到右”依次排开。
我们干掉文章开头处的节点“F和”G",此时还是“完全二叉树”,但已经不是“满二叉树”了,你懂的。
3: 二叉树的性质
二叉树中有5点性质非常重要,也是俺们必须要记住的。
<1> 二叉树中,第i层的节点最多有2(i-1)个。
<2> 深度为k的二叉树最多有2k-1个节点。
<3> 二叉树中,叶子节点树为N1个,度为2的节点有N2个,那么N1=N2+1。
<4> 具有N个结点的二叉树深度为(Log2 N)+1层。
<5> N个结点的完全二叉树如何用顺序存储,对于其中的一个结点i,存在以下关系,
2*i是结点i的父结点。
i/2是结点i的左孩子。
(i/2)+1是结点i的右孩子。
4: 二叉树的顺序存储
同样的存储方式也有两种,“顺序存储”和“链式存储”。
<1> 顺序存储
说实话,树的存储用顺序结构比较少,因为从性质定理中我们都可以看出只限定为“完全二叉树”,那么如果二叉树不是
“完全二叉树”,那我们就麻烦了,必须将其转化为“完全二叉树”,将空的节点可以用“#”代替,图中也可看出,为了维护
性质定理5的要求,我们牺牲了两个”资源“的空间。
<2> 链式存储
上面也说了,顺序存储会造成资源的浪费,所以嘛,我们开发中用的比较多的还是“链式存储”,同样“链式存储”
也非常的形象,非常的合理。
一个结点存放着一个“左指针”和一个“右指针”,这就是二叉链表。
如何方便的查找到该结点的父结点,可以采用三叉链表。
5: 常用操作
一般也就是“添加结点“,“查找节点”,“计算深度”,“遍历结点”,“清空结点
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace BinaryTree { ///<summary> ///二叉链表存储结构 ///</summry> ///<typeparam name="T"></typeparam> public class ChinaTree<T> { //字段 public T data; public ChinaTree<T> left; public ChinaTree<T> right; public int Length { get; set; } //构造函数 public ChinaTree(T nodevalue) { this.data = nodevalue; this.left = null; this.right = null; } /// <summary> /// 将指定节点插入到二叉树中 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="tree"></param> /// <param name="node"></param> /// <param name="direction">插入做左是右</param> /// <returns></returns> public ChinaTree<T> BinTreeAddNode<T>(ChinaTree<T> tree, ChinaTree<T> node, T data, Direction direction) { //树为空 if (tree == null) { return null; } if(tree.data.Equals(data)) { switch(direction) { case Direction.Left: if(tree.left!=null) throw new Exception("树的左节点不为空,不能插入"); else tree.left=node; break; case Direction.Right: if (tree.right != null) throw new Exception("树的右节点不为空,不能插入"); else tree.right = node; break; } } BinTreeAddNode(tree.left, node, data, direction); BinTreeAddNode(tree.right, node, data, direction); return tree; } //查找节点 在二叉树中查找指定的key public ChinaTree<T> BinTreeFind<T>(ChinaTree<T> tree, T data) { if (tree == null) return null; if (tree.data.Equals(data)) return tree; return BinTreeFind(tree, data); } //计算深度 获取二叉树的深度 public int BinTreeLen<T>(ChinaTree<T> tree) { int leftLength; int rightLength; if (tree == null) return 0; //递归左子树的深度 leftLength = BinTreeLen(tree.left); //递归右子树的深度 rightLength = BinTreeLen(tree.right); if (leftLength > rightLength) return leftLength + 1; else return rightLength + 1; } //遍历节点分为: //先序:先访问根,然后递归访问左子树,最后递归右子树。(DLR模式) //中序:先递归访问左子树,在访问根,最后递归右子树。(LDR模式) //后序:先递归访问左子树,然后递归访问右子树,最后访问根。(LRD模式) //按层:这个比较简单,从上到下,从左到右的遍历节点。 //先序遍历 public void BinTree_DLR(ChinaTree<T> tree) { if(tree==null) { return; } //先输出根元素 Console.WriteLine(tree.data+"\t"); //然后遍历左子树 BinTree_DLR(tree.left); //然后遍历右子树 BinTree_DLR(tree.right); } //中序遍历 public void BinTree_LDR(ChinaTree<T> tree) { if (tree == null) return; //先遍历左子树 BinTree_LDR(tree.left); //输出节点值 Console.WriteLine(tree.data + "\t"); //遍历右子树 BinTree_LDR(tree.right); } //后序遍历 public void BinTree_LRD<T>(ChinaTree<T> tree) { if (tree == null) return; //先遍历左子树 BinTree_LRD(tree.left); //再遍历右子树 BinTree_LRD(tree.right); //输出节点值 Console.WriteLine(tree.data + "\t"); } //按层遍历 public void BinTree_Level<T>(ChinaTree<T> tree) { if (tree == null) return; //申请保存空间 ChinaTree<T>[] treelist = new ChinaTree<T>[Length]; int head = 0; int tail = 0; //存放数组 treelist[tail] = tree; //循环链中计算tail的位置 tail = (tail + 1) % Length; while (head != tail) { var tempNode = treelist[head]; head = (head + 1) % Length; //输出节点 Console.WriteLine(tempNode.data + "\t"); //如果左子树不为空 则将左子树存于数组的tail位置 if (tempNode.left != null) { treelist[tail] = tempNode.left; tail = (tail + 1) % Length; } //如果右子树不为空,则将右子树存于数组的tail位置 if (tempNode.right != null) { treelist[tail] = tempNode.right; tail = (tail + 1) % Length; } } } //清空二叉树 public void BinTreeClear<T>(ChinaTree<T> tree) { //递的结束点,归的起始点 if (tree == null) return; BinTreeClear(tree.left); BinTreeClear(tree.right); //在归的过程中,释放当前节点的数据空间 tree = null; } } //枚举左节点和右节点 public enum Direction {Left=1,Right=2} }
汇总代码:
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace ConsoleApplication1 { class Program { static void Main(string[] args) { ChainTreeManager manager = new ChainTreeManager(); //插入节点操作 ChinaTree<string> tree = CreateRoot(); //插入节点数据 AddNode(tree); //先序遍历 Console.WriteLine("\n先序结果为: \n"); manager.BinTree_DLR(tree); //中序遍历 Console.WriteLine("\n中序结果为: \n"); manager.BinTree_LDR(tree); //后序遍历 Console.WriteLine("\n后序结果为: \n"); manager.BinTree_LRD(tree); //层次遍历 Console.WriteLine("\n层次结果为: \n"); manager.Length = 100; manager.BinTree_Level(tree); Console.WriteLine("\n树的深度为:" + manager.BinTreeLen(tree) + "\n"); Console.ReadLine(); } //生成根节点 static ChinaTree<string> CreateRoot() { ChinaTree<string> tree = new ChinaTree<string>(); Console.WriteLine("请输入根节点,方便我们生成树\n"); tree.data = Console.ReadLine(); Console.WriteLine("根节点已经生成\n"); return tree; } //插入节点操作 static ChinaTree<string> AddNode(ChinaTree<string> tree) { ChainTreeManager mananger = new ChainTreeManager(); while (true) { ChinaTree<string> node = new ChinaTree<string>(); Console.WriteLine("请输入要插入节点的数据\n"); node.data = Console.ReadLine(); Console.WriteLine("请输入要查找的父节点的数据\n"); var parentData = Console.ReadLine(); bool flag = mananger.BinTreeFind(tree, parentData); if (!flag) { Console.WriteLine("未找到你输入的父节点,请重新输入"); continue; } Console.WriteLine("你确定要插到父节点的:1 左侧,2右侧"); Direction direction = (Direction)Enum.Parse(typeof(Direction), Console.ReadLine()); tree = mananger.BinTreeAddNode(tree, node, parentData, direction); Console.WriteLine("是否继续? 1 继续, 2 退出"); if (int.Parse(Console.ReadLine()) == 1) continue; else break; } return tree; } } public enum Direction { Left = 1, Right = 2 } public class ChinaTree<T> { //字段 public T data; public ChinaTree<T> left; public ChinaTree<T> right; } public class ChainTreeManager { //按层遍历的Length的空间存储 public int Length { get; set; } /// <summary> /// 将指定节点插入到二叉树中 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="tree"></param> /// <param name="node"></param> /// <param name="direction">插入做左是右</param> /// <returns></returns> public ChinaTree<T> BinTreeAddNode<T>(ChinaTree<T> tree, ChinaTree<T> node, T data, Direction direction) { //树为空 if (tree == null) { return null; } try { if (tree.data.Equals(data)) { switch (direction) { case Direction.Left: if (tree.left != null) throw new Exception("树的左节点不为空,不能插入"); else tree.left = node; break; case Direction.Right: if (tree.right != null) throw new Exception("树的右节点不为空,不能插入"); else tree.right = node; break; } } BinTreeAddNode(tree.left, node, data, direction); BinTreeAddNode(tree.right, node, data, direction); } catch (Exception ex) { Console.WriteLine(ex.Message); } return tree; } public ChinaTree<T> BinTreeChild<T>(ChinaTree<T> tree, Direction direction) { ChinaTree<T> childNode = null; if (tree == null) throw new Exception("二叉树为空"); switch (direction) { case Direction.Left: childNode = tree.left; break; case Direction.Right: childNode = tree.right; break; } return childNode; } //查找节点 在二叉树中查找指定的key public bool BinTreeFind<T>(ChinaTree<T> tree, T data) { bool flag = false; if (tree == null) return false; if (tree.data.Equals(data)) return true; if (tree.left != null) { return BinTreeFind(tree.left, data); } if (tree.right != null) { return BinTreeFind(tree.right, data); } return flag; } //计算深度 获取二叉树的深度 public int BinTreeLen<T>(ChinaTree<T> tree) { int leftLength; int rightLength; if (tree == null) return 0; //递归左子树的深度 leftLength = BinTreeLen(tree.left); //递归右子树的深度 rightLength = BinTreeLen(tree.right); if (leftLength > rightLength) return leftLength + 1; else return rightLength + 1; } //判断二叉树是否为空 public bool BinTreeisEmpty<T>(ChinaTree<T> tree) { return tree == null ? true : false; } //遍历节点分为: //先序:先访问根,然后递归访问左子树,最后递归右子树。(DLR模式) //中序:先递归访问左子树,在访问根,最后递归右子树。(LDR模式) //后序:先递归访问左子树,然后递归访问右子树,最后访问根。(LRD模式) //按层:这个比较简单,从上到下,从左到右的遍历节点。 //先序遍历 public void BinTree_DLR<T>(ChinaTree<T> tree) { if (tree == null) { return; } //先输出根元素 Console.Write(tree.data + "\t"); //然后遍历左子树 BinTree_DLR(tree.left); //然后遍历右子树 BinTree_DLR(tree.right); } //中序遍历 public void BinTree_LDR<T>(ChinaTree<T> tree) { if (tree == null) return; //先遍历左子树 BinTree_LDR(tree.left); //输出节点值 Console.Write(tree.data + "\t"); //遍历右子树 BinTree_LDR(tree.right); } //后序遍历 public void BinTree_LRD<T>(ChinaTree<T> tree) { if (tree == null) return; //先遍历左子树 BinTree_LRD(tree.left); //再遍历右子树 BinTree_LRD(tree.right); //输出节点值 Console.Write(tree.data + "\t"); } //按层遍历 public void BinTree_Level<T>(ChinaTree<T> tree) { if (tree == null) return; //申请保存空间 ChinaTree<T>[] treelist = new ChinaTree<T>[100]; int head = 0; int tail = 0; //存放数组 treelist[tail] = tree; //循环链中计算tail的位置 tail = (tail + 1) % 100; while (head != tail) { var tempNode = treelist[head]; head = (head + 1) % 100; //输出节点 Console.Write(tempNode.data + "\t"); //如果左子树不为空 则将左子树存于数组的tail位置 if (tempNode.left != null) { treelist[tail] = tempNode.left; tail = (tail + 1) % 100; } //如果右子树不为空,则将右子树存于数组的tail位置 if (tempNode.right != null) { treelist[tail] = tempNode.right; tail = (tail + 1) % 100; } } } //清空二叉树 public void BinTreeClear<T>(ChinaTree<T> tree) { //递的结束点,归的起始点 if (tree == null) return; BinTreeClear(tree.left); BinTreeClear(tree.right); //在归的过程中,释放当前节点的数据空间 tree = null; } } }
运行图: