二 叉 树 : {\color{Red} 二叉树:} 二叉树:是个神魔东西( ﹁ ﹁ ) →???
简单的介绍一下二叉树
二叉树是每个结点最多有两个子树的树结构。通常子树被称作“左子树” ( l e f t s u b t r e e ) (left subtree) (leftsubtree)和“右子树” ( r i g h t s u b t r e e ) (right subtree) (rightsubtree)。二叉树常被用于实现二叉查找树和二叉堆。
一棵深度为k,且有 2 k − 1 2^{k}-1 2k−1个节点的二叉树,称为满二叉树。这种树的特点是每一层上的节点数都是最大节点数。而在一棵二叉树中,除最后一层外,若其余层都是满的,并且最后一层或者是满的,或者是在右边缺少连续若干节点,则此二叉树为完全二叉树。具有 n n n个节点的完全二叉树的深度为 f l o o r ( l o g 2 n ) + 1 floor(log2n)+1 floor(log2n)+1。深度为 k k k的完全二叉树,至少有 2 k − 1 2^{k}-1 2k−1个叶子节点,至多有 2 k − 1 2^{k}-1 2k−1个节点。
定义
二叉树是一个连通的无环图,并且每一个顶点的度不大于 3 3 3。有根二叉树还要满足根结点的度不大于 2 2 2。有了根结点之后,每个顶点定义了唯一的父结点,和最多 2 2 2个子结点。然而,没有足够的信息来区分左结点和右结点。如果不考虑连通性,允许图中有多个连通分量,这样的结构叫做森林
二叉树的形态
二叉树是递归定义的,其结点有左右子树之分,逻辑上二叉树有五种基本形态:
(1)空二叉树——如图:
(2)只有一个根结点的二叉树——如图
(3)只有左子树——如图:
(4)只有右子树——如图:
(5)完全二叉树——如图:
注意:尽管二叉树与树有许多相似之处,但二叉树不是树的特殊情形;
(1)完全二叉树——若设二叉树的高度为 h h h,除第 h h h 层外,其它各层 ( 1 ~ h − 1 ) (1~h-1) (1~h−1) 的结点数都达到最大个数,第h层有叶子结点,并且叶子结点都是从左到右依次排布,这就是完全二叉树。
(2)满二叉树——除了叶结点外每一个结点都有左右子叶且叶子结点都处在最底层的二叉树。
(3)平衡二叉树——平衡二叉树又被称为 A V L AVL AVL树(区别于 A V L AVL AVL算法),它是一棵二叉排序树,且具有以下性质:它是一棵空树或它的左右两个子树的高度差的绝对值不超过 1 1 1,并且左右两个子树都是一棵平衡二叉树。
相关术语
树的结点(node):包含一个数据元素及若干指向子树的分支;
孩子结点(child node):结点的子树的根称为该结点的孩子;
双亲结点: B B B 结点是 A A A 结点的孩子,则 A A A结点是 B B B 结点的双亲;
兄弟结点:同一双亲的孩子结点; 堂兄结点:同一层上结点;
祖先结点: 从根到该结点的所经分支上的所有结点
子孙结点:以某结点为根的子树中任一结点都称为该结点的子孙
结点层:根结点的层定义为 1 1 1;根的孩子为第二层结点,依此类推;
树的深度:树中最大的结点层
结点的度:结点子树的个数
树的度: 树中最大的结点度。
叶子结点:也叫终端结点,是度为 0 0 0 的结点;
分枝结点:度不为 0 0 0的结点;
有序树:子树有序的树,如:家族树;
无序树:不考虑子树的顺序;
二叉树性质
( 1 ) (1) (1) 在非空二叉树中,第 i i i层的结点总数不超过, i > = 1 i>=1 i>=1;
( 2 ) (2) (2) 深度为h的二叉树最多有 2 h − 1 2^{h}-1 2h−1 个结点 ( h > = 1 ) (h>=1) (h>=1),最少有 h h h个结点;
( 3 ) (3) (3) 对于任意一棵二叉树,如果其叶结点数为 N 0 N_{0} N0,而度数为 2 2 2的结点总数为 N 2 N_{2} N2,则 N 0 N_{0} N0= N 2 + 1 N_{2}+1 N2+1;
( 4 ) (4) (4) 具有 n n n个结点的完全二叉树的深度为(注:[ ]表示向下取整)
( 5 ) (5) (5) 有 N N N个结点的完全二叉树各结点如果用顺序方式存储,则结点之间有如下关系:
若 I I I为结点编号则 如果 I > 1 I>1 I>1,则其父结点的编号为 I / 2 I/2 I/2;
如果 2 ∗ I < = N 2*I<=N 2∗I<=N,则其左孩子(即左子树的根结点)的编号为 2 ∗ I 2*I 2∗I;若 2 ∗ I > N 2*I>N 2∗I>N,则无左孩子;
如果 2 ∗ I + 1 < = N 2*I+1<=N 2∗I+1<=N,则其右孩子的结点编号为 2 ∗ I + 1 ; 2*I+1; 2∗I+1;若 2 ∗ I + 1 > N 2*I+1>N 2∗I+1>N,则无右孩子。
( 6 ) (6) (6) 给定 N N N个节点,能构成 h ( N ) h(N) h(N)种不同的二叉树。 h ( N ) h(N) h(N)为卡特兰数的第 N N N项。 h ( n ) = C ( 2 ∗ n , n ) / ( n + 1 ) h(n)=C(2*n,n)/(n+1) h(n)=C(2∗n,n)/(n+1)。
( 7 ) (7) (7) 设有 i i i个枝点, I I I为所有枝点的道路长度总和, J J J为叶的道路长度总和 J = I + 2 i [ 2 ] J=I+2i[2] J=I+2i[2]
二叉树遍历
遍历是对树的一种最基本的运算,所谓遍历二叉树,就是按一定的规则和顺序走遍二叉树的所有结点,使每一个结点都被访问一次,而且只被访问一次。由于二叉树是非线性结构,因此,树的遍历实质上是将二叉树的各个结点转换成为一个线性序列来表示。
设 L 、 D 、 R L、D、R L、D、R分别表示遍历左子树、访问根结点和遍历右子树, 则对一棵二叉树的遍历有三种情况: D L R DLR DLR(称为先根次序遍历), L D R LDR LDR(称为中根次序遍历), L R D LRD LRD (称为后根次序遍历)。
先 序 遍 历 {\color{Red} 先序遍历} 先序遍历
首先访问根,再先序遍历左(右)子树,最后先序遍历右(左)子树,C语言代码如下:
void preOrderRecur(binaryTree T)
{
if(T == NULL)
return;
printf("%d ",T->value);
preOrderRecur(T->pLeft);
preOrderRecur(T->pRight);
}
中 序 遍 历 {\color{Red} 中序遍历} 中序遍历
首先中序遍历左(右)子树,再访问根,最后中序遍历右(左)子树,设二叉树中元素数目为
n
n
n,中序遍历算法的空间复杂性均为
O
(
n
)
O (n)
O(n),时间复杂性为
(
n
)
(n)
(n)。
当t的高度为n时(右斜二叉树的情况),通过观察其前序、中序和后序遍历时所使用的递归栈空间即可得到上述结论。
先左子树再根再右子树
中序遍历就是 B A C B A C BAC,如果 B B B有左右子树,如下图,再访问 B B B之前先访问 B B B的左子树
中序遍历为 D B E A C D B E A C DBEAC,如果 C C C有右子树没有左子树,如下图则是先访问 C C C再访问 F F F
给出序列 E B F A D H C G I K J EBFADHCGIKJ EBFADHCGIKJ由此我们可以推出此序列的树如下图
中序就是: A B C D E F G H I J K ABCDEFGHIJK ABCDEFGHIJK,在访问 E E E的时候,发现 E E E有左子树 B B B,先 B B B,再访问 B B B的时候发现有左子树 A A A,所以肯定还是 A A A先,所以这个序列是从 A A A开始的。
/* 递归实现中序遍历 */
void inOrderRecur(binaryTree T)
{
if(T == NULL)
return;
inOrderRecur(T->pLeft);
printf("%d ", T->value);
inOrderRecur(T->pRight);
}
分 析 {\color{Red} 分析} 分析
根据先序和中序遍历的特点,可以发现如下规律:
先序遍历的每个节点,都是当前子树的根节点。同时,以对应的节点为边界,就会把中序遍历的结果分为左子树和右子树,用这种方法也能求出 后 序 遍 历 {\color{Red}后序遍历 } 后序遍历。例如:
先序:
a
b
d
c
e
f
a b d c e f
abdcef
a
a
a是根节点
中序:
d
b
a
e
c
f
d b a e c f
dbaecf
a
a
a是根节点,把字符串分成左右两个子树
a a a是先序遍历节点的第一个元素,可以看出,它把中序遍历的结果分成 d b db db和 e c f ecf ecf两部分。这就是 a a a的左子树和右子树的遍历结果。
如果能够找到先序遍历中对应的左子树和右子树,就可以把 a a a作为当前的根节点,然后依次递归下去,这样就能够依次恢复左子树和右子树的遍历结果.
后序遍历二叉树的操作可定义为:
若二叉树为空,则空操作;否则
(1)后序遍历左子树;
(2)后序遍历右子树;
(3)访问根结点。
后 序 遍 历 {\color{Red} 后序遍历} 后序遍历
首先后序遍历左(右)子树,再后序遍历右(左)子树,最后访问根
其访问顺序是先左再右再根,下面的例子,后序就是
B C A BCA BCA
如果 B B B有左右子树,如下图,先访问 B B B的左右子树,再访问 B B B,其后序是 D E B C A DEBCA DEBCA
如果 C C C有右子树没有左子树有右子树,再访问 C C C时的右子树F再访问 C C C,其后序是 D E B F C A DEBFCA DEBFCA
给出序列 E B F A D H C G I K J EBFADHCGIKJ EBFADHCGIKJ由此我们可以推出此序列的树如下图
后序是 A C D B G J K I H F E ACDBGJKIHFE ACDBGJKIHFE
void posOrderRecur(binaryTree T)
{
if(T == NULL)
return;
posOrderRecur(T->pLeft);
posOrderRecur(T->pRight);
printf("%d ", T->value);
}
层 次 遍 历 {\color{Red} 层次遍历} 层次遍历
即按照层次访问,通常用队列来做。访问根,访问子女,再访问子女的子女(越往后的层次越低)(两个子女的级别相同)
推 树 {\color{orange} 推树} 推树
由前中序列求后序列
座号 | 1 | 2 | 3 | 4 | 5 | 6 |
---|---|---|---|---|---|---|
前 | 1 | 2 | 3 | 4 | 5 | 6 |
中 | 3 | 2 | 4 | 1 | 6 | 5 |
从前序可以看出根节点为
1
1
1
根节点在中序的位置为
4
4
4号,可以看出它的左右节点分别有
3
3
3和
2
2
2个;
从前序列可以看出左
3
3
3节点为
(
2
,
3
,
4
)
(2,3,4)
(2,3,4),右2节点为
(
5
,
6
)
(5,6)
(5,6),根节点的子节点为左:
2
2
2,右:
5
5
5;看下图:
以此我们把 2 2 2作为节点则上表可变为
座号 | 1 | 2 | 3 |
---|---|---|---|
前 | 2 | 3 | 4 |
中 | 3 | 2 | 4 |
从前序可以看出根节点为
2
2
2
根节点在中序的位置为
2
2
2号,可以看出它的左右节点分别有
1
1
1和
1
1
1个;
从前序列可以看出左
1
1
1节点为
(
3
)
(3)
(3),右1节点为
(
4
)
(4)
(4),根节点的子节点为左:
3
3
3,右:
4
4
4;
施行完之后就变如下:
以此我们把 3 3 3作为节点,但是由于 2 2 2的左节点 3 3 3左右无节点,所以需找 2 2 2的右节点 4 4 4但是也没有(⊙﹏⊙),这时我们可以看做为 1 1 1的左节点 2 2 2已经找完了所有节点,可以继续向右需找 1 1 1的右点 5 5 5,看下表:
座号 | 1 | 2 |
---|---|---|
前 | 5 | 6 |
中 | 6 | 5 |
由此我们可以得到下图,也即是完整的树:
从此树我们很容易推出后续(3,4,6,2,5,1);
座号 | 1 | 2 | 3 | 4 | 5 | 6 |
---|---|---|---|---|---|---|
前 | 1 | 2 | 3 | 4 | 5 | 6 |
中 | 3 | 2 | 4 | 1 | 6 | 5 |
后 | 3 | 4 | 6 | 2 | 5 | 1 |