树结点结构定义如下:
typedef struct bitnode { int num; struct bitnode *lchild; struct bitnode *rchild; }TREENODE;
链式存储结构
二叉树的遍历
(1)先序遍历(先根遍历)
1. 访问根结点
2. 先序遍历左子树
3. 先序遍历右子树
(2)中序遍历(中根遍历)
1. 中序遍历左子树
2. 访问根结点
3. 中序遍历右子树
(3)后序遍历(后根遍历)
1. 后序遍历左子树
2. 后序遍历右子树
3. 访问根结点
对于上图遍历结果:
先序遍历:ABDECFG
中序遍历:DBEAFCG
后序遍历:DEBFGCA
SGI STL的关联容器(map、set、multimap、multiset)底层都是基于红黑树(Red Black Tree,RBT)来实现的,红黑树是一种被广泛使用的二叉查找树(Binary Search Tree,BST),有比较良好的操作效率。
1. 二叉查找树
二叉查找树可提供对数时间的元素插入和访问,其遵循以下规则:任何节点的键值一定大于其左子树中每一个节点的键值,并小于其右子树中每一个节点的键值。不过在极端情况下,当所有节点位于一条链上时,二叉查找树的操作时间为O(N)。二叉查找树样例:
1.1 最大值与最小值
根据二叉查找树的性质,最小值即沿着根节点不断沿着左子树下降,最后一个节点即为值最小的节点;同理最大值即沿着根节点不断沿着右子树下降,最后一个节点即为值最大的节点。
1.2 节点的插入
从根节点开始,如果新插入节点的值比当前节点的值要大,则选择右子节点继续对比,否则选择左子结点继续对比,这样操作直到叶节点。将新插入节点做为叶节点的子节点(根据大小关系设置为左子结点或者右子节点)。
1.3 节点的删除
根据被删除节点的不同,存在以下几种情况:
1.3.1 被删除节点为叶子节点
直接删除该节点即可(将父节点对应的左[右]子树设置为NULL);
1.3.2 被删除节点只有左子树(或者只有右子树)
将被删除节点的父节点指向被删除节点的指针指向被删除节点的子节点;
1.3.3 被删除节点同时存在左右子树
方法一:找到被删除节点的后继,将被删除节点的值更新为后继节点的值,最后删除后继节点;
方法二:找到被删除节点的前趋,将被删除节点的值更新为前趋节点的值,最后删除前趋节点;
下图展示了方法一删除方法:
我们中序遍历这两棵树发现一个有序的数据序列: 【1 2 3 4 5 6 7 8 】
二叉查找树的效率分析
那么我们再来看看二叉查找树的效率问题
很显然,在a,b两图的二叉查找树结构中查找一个数据,并不需要遍历全部的节点元素,查找效率确实提高了。但是有一个很严重的问题:我们在a图中查找8需要比较5次数据,而在B图中只需要比较3次。更为严重的是:如果按有序序列[1 2 3 4 5 6 7 8]建立一颗二叉查找树,整棵树就退化成了一个线性结构(如c输入图:单支树),此时查找8需要比较8次数据,和顺序查找没有什么不同。
总结一下:最坏情况下,构成的二叉排序树蜕变为单支树,树的深度为n,其查找时间复杂度与顺序查找一样O(N)。最好的情况是二叉排序树的形态和折半查找的判定树相同,其平均查找长度和log2(N)成正比 (O(log2(n)))。
1.4 CLRS 12.2-5
证明:如果二叉查找树中的某个节点有两个子女,则其后继没有左子女,其前趋没有右子女。
这个结论是1.3.3的一个支撑。可以用反证法证明:
后继即右子树中值最小的节点,也就是右子树中的最左叶子节点,该节点肯定不可能还存在左子女,否则就不可能是最左的叶子节点了。同理可证前趋没有右子女。
2. AVL树
AVL树属于二叉查找树,在AVL树中任何节点的两个子树的高度最大差别为一,所以它也被称为高度平衡树。查找、插入和删除在平均和最坏情况下都是O(log
n),增加和删除可能需要通过一次或多次树旋转来重新平衡这个树。
二叉查找树的查找和插入操作在最坏情况下复杂度为O(N),而AVL树最坏时仍然为O(lgN)。
2.1 节点的旋转
AVL树的插入和删除操作需要借助于节点的旋转来保持树的高度平衡。节点的旋转分为左旋和右旋。旋转之后仍然保持二叉查找树的性质。
右旋操作时左旋操作的逆操作,对旋转操作只要记住保持BST的性质就很好理解了。
2.2 节点的插入
节点的插入结果可以分为四种情况。节点插入之后如果AVL树的平衡遭到破坏,那么,令X为平衡状态被破坏的节点中最深(下方)的节点。
1. 插入点位于X的左子结点的左子树——左左;
2. 插入点位于X的左子结点的右子树——左右;
3. 插入点位于X的右子结点的左子树——右左;
4. 插入点位于X的右子结点的右子树——右右;
对于第1种和第4种情况,进行一次旋转即可保持AVL树的性质;对于第2种、第3种需要进行两次旋转操作来保持AVL树的性质。
情况一:
情况二:
3. 红黑树
如果BST满足如何红黑性质,则该BST为红黑树:
1. 每个节点要么是红色,要么是黑色;
2. 根节点为黑色;
3. 如果一个节点为红色,则其子节点必为黑色;
4. 从任一节点至NULL节点的任何路径,包含同样个数的黑节点;
当给红黑树插入一个节点时,为了保证尽可能少的破坏红黑树的性质,我们让:新增的节点着为红色。此时如果插入节点后整棵树只有一个节点,那么性质2被破坏,只要把节点染为黑色即可;而如果新增节点的父节点也为红色,就破坏了性质3,这种情况下操作起来就有点复杂了。
红黑树性质的保持
如果插入一个节点后红黑树性质被破坏,我们需要进行一些操作来保持红黑树节点的性质。这里划分为四种情况(还有另外四种情况是对称的),先做如下约定:
1. 新增节点:X
2. 父节点:P
3. 祖父节点:G
4. 伯父节点:S
5. 曾祖父节点:GG
情况一
S为黑且X为外侧插入,此时旋转PG并更改PG颜色:
情况二
S为黑且X为内侧插入,先对PX做一次单旋转并更改GX颜色,再对GX做一次单旋转:
情况三
S为红且X为外侧插入,先对PG做一次单旋转,并更改X的颜色,此时如果GG为黑,则完毕,否则进入情况四:
情况四
S为红且X为外侧插入,先对PG做一次单旋转,并更改X的颜色,此时GG为红,继续往上做,直到不再有父子节点连续为红。
上述四种情况均为P为G的左子结点的情况,当P为G的右子节点时情况是类似的。