AVL-tree、RB-tree、AA-tree均可以实现平衡的二叉查找树,虽然相对于一般的二叉搜索树其插入、删除节点的平均时间会比较长,但它们可以避免极验证应付的最坏的情况–树高度不平衡。
平衡二叉查找树所谓的平衡并不是绝对的平衡,而是要求任何一个节点的左右子树高度相差不会超过1,此时仍能够保证树的“对数深度”。
如上图所示,X节点本来是平衡的,插入一个新节点后“平衡被破坏”了,这可以分为4种情况:
1.插入节点位于X的左子节点的左子树–左左;
2.插入节点位于X的左子节点的右子树–左右;
3.插入节点位于X的右子节点的左子树–右左;
4.插入节点位于X的右子节点的右子树–右右。
情况1、4彼此对称,称为外侧插入,可以采用单旋操作调整解决;情况2、3彼此对称,称为内侧插入,可以采用双旋转操作调整解决。
单反转
插入新节点11出现情况1的调整策略:把中k1作为其左子孩子k2的右孩子,而k2原先的右孩子作为现在k1的左孩子。
出现情况4的调整策略类同。
双反转
插入新节点15出现情况2的调整策略:先让k2和k1做一次单旋转,再让k2和k3做一次单旋转。
出现情况3的调整策略类同。
红黑树RB-tree
C++中的set和map底层用的就是红黑树。
RB-tree是满足以下4个条件的二叉查找树:
1.每个节点不是红色就是黑色
2.根节点为黑色
3.红节点的子节点必须是黑色
4.任一节点至NULL(树尾端)的每一条路径上,所含黑节点的数目必须相同
红黑树并不要求左右子树高度差控制在1以内,它的平衡条件比AVL树弱,它只能保证任何一个节点的左右子树的高度差不会超过较矮的那棵的一倍。然而红黑树通常能够导致良好的平衡状态,经验告诉我们,红黑树的平均搜索效率和AV-tree几乎相等,但是其插入节点的开销相对较低,实践中发生旋转的次数相对较少。
以下所有操作,在新插入节点后,首先将新节点设为红色。
按照二叉查找树的规则插入新节点后,如果新增节点的父节点为黑,则直接插入。否则分为4种情况(在此作一些符号约定,新增节点为X,其父节点为P,祖父节点为G,伯父节点为S,曾祖父节点为GG):
1.S为黑,且X为外侧插入。P,G做一次单旋转,并更改P,G的颜色。
2.S为黑,且X为内侧插入。 X,P做一次单旋转,并更改X和P的颜色。再对X,G做一次单旋转。
3.S为红,且X为外侧插入。 P,G做一次单旋转,并更改X的颜色。此时如果GG为黑,一切搞定;
否则,还得继续往上做,直到不再有父子节点连续为红的情况。
4.S为红,且X为内侧插入。直接更改P,S,G的颜色。此时如果GG为黑,一切搞定;否则,还得继续往上做,直到不再有父子节点连续为红的情况。
为了避免上述情况3、4中GG也为红的情况发生,我们设计一个“自顶向下”的红黑树。假设新增节点为A,那么就延着从根节点到A的路径,只要看到某个节点X的两个子节点皆为红色,就把这两个子节点改为黑色,同时把X改为红色。但是如果此时X的父节点也是红色(注意此时X的伯父节点已经不可能是红色),就像上述情况1那样作一次单旋转改变颜色,或像情况2那样做一些双旋转再改变颜色。