二叉树的基本概念
在计算机中,二叉树是每个节点最多有两个子树的树结构。通常子树被称作“左子树”(left subtree)和“右子树”(right subtree)。
二叉树是一个连通的无环图,并且每一个顶点的度不大于3。有根二叉树还要满足根结点的度不大于2。有了根节点之后,每个顶点定义了唯一的父节点,和最多2个子节点。然而,没有足够的信息来区分左节点和右节点。
从逻辑上讲二叉树有五种形态,如上图:
二叉树相关的术语
- 树的节点(node):包含一个数据元素及若干指向子树的分支;
- 孩子节点(child node):节点的子树的根称为该节点的孩子;
- 双亲节点:B 节点是A 节点的孩子,则A结点是B 节点的双亲;
- 兄弟节点:同一双亲的孩子节点;
- 堂兄结点:同一层上节点;
- 祖先节点: 从根到该节点的所经分支上的所有节点
- 子孙节点:以某节点为根的子树中任一节点都称为该节点的子孙
- 节点层:根节点的层定义为1;根的孩子为第2层节点,依此类推;
- 树的深度:树中最大的节点层
- 节点的度:节点子树的个数
- 树的度: 树中最大的节点度。
- 叶子结点:也叫终端节点,是度为 0 的节点;
- 分枝结点:度不为0的节点;
- 有序树:子树有序的树,如:家族树;
- 无序树:不考虑子树的顺序;
二叉树的特性
- 在非空二叉树中,第i层的节点总数不超过 , i>=1;
- 深度为h的二叉树最多有 个结点(h>=1),最少有h个节点;
- 对于任意一棵二叉树,如果其叶节点数为N0,而度数为2的节点总数为N2,则N0=N2+1;
- 具有n个节点的完全二叉树的深度为
- 有N个节点的完全二叉树各节点如果用顺序方式存储,则节点之间有如下关系:若I为节点编号则 如果I>1,则其父节点的编号为I/2;如果2*I<=N,则其左儿子(即左子树的根节点)的编号为2*I;若2*I>N,则无左儿子;如果2*I+1<=N,则其右儿子的节点编号为2*I+1;若2*I+1>N,则无右儿子。
- 给定N个节点,能构成h(N)种不同的二叉树。h(N)为卡特兰数的第N项。h(n)=C(2*n,n)/(n+1)。
- 设有i个枝点,I为所有枝点的道路长度总和,J为叶的道路长度总和J=I+2i
二叉树的遍历
遍历是对树的一种最基本的运算,所谓遍历二叉树,就是按一定的规则和顺序走遍二叉树的所有节点,使每一个节点都被访问一次,而且只被访问一次。由于二叉树是非线性结构,因此,树的遍历实质上是将二叉树的各个结点转换成为一个线性序列来表示。
设L、D、R分别表示遍历左子树、访问根节点和遍历右子树, 则对一棵二叉树的遍历有三种情况:DLR(称为先序遍历),LDR(称为中序遍历),LRD (称为后序遍历)
先序遍历(DLR):
首先访问根,再先序遍历左子树,最后先序遍历右子树。如上图遍历的结果是:ABCDE
中序遍历(LDR):
首先中序遍历左子树,再访问根,最后中序遍历右子树,如上图遍历的结果是:CBDAE
后序遍历(LRD):
首先后序遍历左子树,再后序遍历右子树,最后访问根。如上图遍历的结果是:CDBEA
传统递归对二叉树的遍历
public class Node { public Node(int i) { Data = i; } public int Data { get; set; } public Node Left { get; set; } public Node Right { get; set; } public void Display(string lr) { Console.Write(lr + ": " + Data); }
先序遍历
public void DLRDisplay(Node theNode) { if (theNode != null) { theNode.Display("InOrder"); InOrder(theNode.Left); InOrder(theNode.Right); } }
中序遍历
public void LDRDisplay(Node theNode) { if (theNode != null) { InOrder(theNode.Left); theNode.Display("InOrder"); InOrder(theNode.Right); } }
后序遍历
public void LRDDisplay(Node theNode) { if (theNode != null) { InOrder(theNode.Left); InOrder(theNode.Right); theNode.Display("InOrder"); } }
组合模式实现二叉树的三种遍历
代码实现
public enum Order { DLR,//先序遍历 LDR,//中序遍历 LRD //后序遍历 } public abstract class AbstractTreeNode { protected string name; public AbstractTreeNode(string name) { this.name = name; } public AbstractTreeNode Left { get; set; } public AbstractTreeNode Right { get; set; } public abstract void Display(Order order); } public class TreeNode : AbstractTreeNode { public TreeNode(string name) : base(name) { } public override void Display(Order order) { switch (order) { case Order.DLR:// D->L->R DLRDisplay(order); break; case Order.LDR://L->D->R LDRDisplay(order); break; case Order.LRD://L->R->D LRDDisplay(order); break; } } private void DLRDisplay(Order order) { Console.Write(name); if (Left != null) { Left.Display(order); } if (Right != null) { Right.Display(order); } } private void LDRDisplay(Order order) { if (Left != null) { Left.Display(order); } Console.Write(name); if (Right != null) { Right.Display(order); } } private void LRDDisplay(Order order) { if (Left != null) { Left.Display(order); } if (Right != null) { Right.Display(order); } Console.Write(name); } } public class LeafNode : AbstractTreeNode { public LeafNode(string name) : base(name) { } public override void Display(Order order) { Console.Write(name); } }
测试:
/// <summary> /// A /// / /// B E /// / /// C D /// </summary> /// <param name="args"></param> static void Main(string[] args) { AbstractTreeNode rootA, b, c, d, e; rootA = new TreeNode("A"); b = new TreeNode("B"); c = new LeafNode("C"); d = new LeafNode("D"); e = new LeafNode("E"); rootA.Left = b; rootA.Right = e; b.Left = c; b.Right = d; rootA.Display(Order.DLR);//output->ABCDE Console.WriteLine(); rootA.Display(Order.LDR);//output->CBDAE Console.WriteLine(); rootA.Display(Order.LRD);//output->CDBEA Console.ReadKey(); }
结果:
先序遍历的结果:ABCDE
中序遍历的结果:CBDAE
后序遍历的结果:CDBEA
在这里其实可以省略掉叶子节点类,只需要一个实现类TreeNode就可以了这样就变得更简单了:
代码中可以删掉LeafNode类,客户端只针对TreeNode实现就可以了。因为这里没有针对复杂的树枝节点的管理操作。因此不需要分别对待树枝节点和叶子节点。
public enum Order { DLR,//先序遍历 LDR,//中序遍历 LRD //后序遍历 } public abstract class AbstractTreeNode { protected string name; public AbstractTreeNode(string name) { this.name = name; } public AbstractTreeNode Left { get; set; } public AbstractTreeNode Right { get; set; } public abstract void Display(Order order); } public class TreeNode : AbstractTreeNode { public TreeNode(string name) : base(name) { } public override void Display(Order order) { switch (order) { case Order.DLR:// D->L->R DLRDisplay(order); break; case Order.LDR://L->D->R LDRDisplay(order); break; case Order.LRD://L->R->D LRDDisplay(order); break; } } private void DLRDisplay(Order order) { Console.Write(name); if (Left != null) { Left.Display(order); } if (Right != null) { Right.Display(order); } } private void LDRDisplay(Order order) { if (Left != null) { Left.Display(order); } Console.Write(name); if (Right != null) { Right.Display(order); } } private void LRDDisplay(Order order) { if (Left != null) { Left.Display(order); } if (Right != null) { Right.Display(order); } Console.Write(name); } }
测试:
/// <summary> /// A /// / /// B E /// / /// C D /// </summary> /// <param name="args"></param> static void Main(string[] args) { AbstractTreeNode rootA, b, c, d, e; rootA = new TreeNode("A"); b = new TreeNode("B"); c = new TreeNode("C"); d = new TreeNode("D"); e = new TreeNode("E"); rootA.Left = b; rootA.Right = e; b.Left = c; b.Right = d; rootA.Display(Order.DLR);//output->ABCDE Console.WriteLine(); rootA.Display(Order.LDR);//output->CBDAE Console.WriteLine(); rootA.Display(Order.LRD);//output->CDBEA Console.ReadKey(); }
结果:
先序遍历的结果:ABCDE
中序遍历的结果:CBDAE
后序遍历的结果:CDBEA
两次测试的结果是一样的。