树
树(Tree): n(n≥0)个结点构成的有限集合。 当n=0时,称为空树; 对于任一棵非空树(n> 0),它具备以下性质:
- 树中有一个称为“根(Root)”的特殊结点,用 r 表示;
- 其余结点可分为m(m>0)个互不相交的有限集T1,T2,... ,Tm,其 中每个集合本身又是一棵树,称为原来树的“子树(SubTree)”
树的一些基本术语
- 结点的度(Degree):结点的子树个数
- 树的度:树的所有结点中最大的度数
- 叶结点(Leaf):度为的结点
- 父结点(Parent):有子树的结点是其子树 的根结点的父结点
- 子结点(Child):若A结点是B结点的父结点,则称B结点是A结点的子结点;子结点也 称孩子结点。
- 兄弟结点(Sibling):具有同一父结点的各 结点彼此是兄弟结点。
- 路径和路径长度:从结点n1到nk的路径为一 个结点序列n1 , n2 ,… , nk , ni是 ni+1的父结 点。路径所包含边的个数为路径的长度。
- 祖先结点(Ancestor):沿树根到某一结点路 径上的所有结点都是这个结点的祖先结点。
- 子孙结点(Descendant):某一结点的子树 中的所有结点是这个结点的子孙。
- 结点的层次(Level):规定根结点在1层, 其它任一结点的层数是其父结点的层数加1。
- 树的深度(Depth):树中所有结点中的最 大层次是这棵树的深度。
树的表示
数组?
--- 树的分枝是不确定的,因此用数组表示不太方便链表?
常规表示
劣势:此种链表表示方法由于子结点的个数不确定,因此需要多个结构来实现。
如是直接选取子结点最多的结构来代替所有结构,简化了结构,但会造成资源浪费。
儿子兄弟表示法
链表结构固定,两个指针,第一个指针指向他的第一个儿子,第二个指针指向他旁边的兄弟,如不存在,则指针为空。如下图所示:
二叉树
- 定义:一个有穷的结点集合,由根结点和称为其左子树T_L和右子树T_R的两个不相交的二叉树组成(非空集合)。也可以说是特殊度为2的树,有左右之分。
相关概念:
- 叶结点:没有子节点的结点,个数表示为n_0
- 只有一个子节点的结点,个数表示为n_1
- 有两个子节点的结点,个数表示为n_2
由左至右分别为:空树
特殊二叉树:
斜二叉树:只有一边的结点
完美二叉树:所有根节点都由两个子结点
完全二叉树:相对于完美二叉树而言,最后一层的子节点可以缺项,但是从上至下从左往右编号时,前面的编号和完美二叉树完全一致
性质:
1. 一个二叉树第i层的最大结点数为:2^(i-1) , i >= 1 2. 深度为k的二叉树有最大的结点总数为:2^k-1 , k >= 1 3. 对于任何非空二叉树,若 n_0 表示叶节点个数,n_2是度为2的非叶节点个数,那么两者之间满足关系 n_0 = n_2 +1
证明:一个二叉树的两个结点之间的连线可以看作一条边,由下往上看,每一个结点都有一条边,除去根节点,所以,节点数 = 边数 + 1
由上往下看,则可以得到如下等式:n_0 + n_1 + n_2 - 1(节点数)= 0 * n_0 + 1 * n_1 + 2 * n_2(边数,由上往下看,结点拥有子节点的个数等于其边数),化简即可证得。
二叉树的存储结构
### 顺序存储结构:
完全二叉树:从上至下、从左到右的方式进行顺序存储
Q:储存后相关操作是否方便?
A :方便,相关操作是有规律的
非根节点 (序号 i > 1) 的父节点序号是 [i/2]
结点(序号为 i )的左孩子结点是 2i (若 2i <= n , 否则没有左孩子)
结点(序号为 i )的右孩子结点是 2i+1 (若 2i+1 <= n , 否则没有右孩子)
一般二叉树:存储方式同完全二叉树,如结点有缺,则在数组中留出空位
缺点:会造成空间浪费。
链表存储
儿子兄弟表示法
二叉树的遍历
先序遍历:void PreOrderTraversal( BinTree BT ):先序----根、左子树、右子树;
中序遍历:void InOrderTraversal( BinTree BT ): 中序---左子树、根、右子树;
后序遍历:void PostOrderTraversal( BinTree BT ):后序---左子树、右子树、根
层次遍历:void LevelOrderTraversal( BinTree BT ):层次遍历,从上到下、从左到右
先序遍历
遍历过程为:
1. 访问根节点 2. 先序遍历左子树 3. 先序遍历右子树
1 | void PreOrderTraversal ( BinTree BT){ // 先序遍历 |
输出顺序如图所示:
中序遍历
遍历过程为: 1. 中序遍历其左子树; 2. 访问根结点; 3. 中序遍历其右子树。
1 | void InOrderTraversal ( BinTree BT ){ // 中序遍历 |
输出顺序如图所示:后序遍历遍历过程为: 1. 后序遍历其左子树; 2. 后序遍历其右子树; 3. 访问根结点。
1 | void PostOrderTraversal ( BinTree BT ){ // 后序遍历 |
输出顺序如图所示:
以上三种遍历均属于递归遍历
我们可以发现,上述三种遍历,经过节点的路线都是一样的,之时访问节点的时机不同,也就是输出顺序不同。
中序遍历非递归遍历算法:
基本思路:使用堆栈
1 | void InOrderTraversal ( BinTree BT ){ |
中序遍历非递归遍历算法:
递归实际也是一种用堆栈实现的算法,所以两种方式实现是相同的,只不过不用递归,直接采用建立堆栈,用压栈弹栈进行操作而已。
层序遍历
使用队列实现:
1 | void LevelOrderTraversal ( BinTree BT ){ |
遍历二叉树的应用
输出二叉树中的叶节点
由题意可知,输出叶节点,也就是输出没有没有左右子树的结点,那么我们使用前序便历,在输出之时,检测其是否有左右子树。代码如下:
1 | void PreOrderPrintLeaves( BinTree BT ){ |
求二叉树的高度
二叉树的高度等于左右子树最高的高度,所以遍历之时添加计数功能。
1 | int PostOrderGetHeight( BinTree BT ) { |
二元运算表达树及其遍历
三种遍历得到不同的访问结果
先序遍历得到前缀表达式
中序遍历得到中缀表达式:但是中缀表达式可能由于优先级不同,出现问题
---- 如何解决? 输出左子树之前加左括号,输出右子树之后加右括号
后序遍历得到后续表达式
由两种遍历序列确定二叉树
Q:可以是任意两种吗? A:不可以,确定之时必须要有中序遍历,仅仅依靠前序和后序无法确定出一个唯一的树
先序和中序遍历来确定一棵二叉树 分析: 根据先序遍历序列第一个结点确定根结点; 根据根结点在中序遍历序列中分割出左右两个子序列 对左子树和右子树分别递归使用相同的方法继续分解。