二叉查找树
(1)左子树上所有节点的值均小于或等于它的根结点的值。
(2)右子树上所有节点的值均大于或等于它的根结点的值。
(3)子树也要遵循上面的规则
二叉查找树可能出现的问题
如果我们在刚刚开始的时候有三个节点,
然后依次插入7,6, 5,4。我们看会发生什么情况:
这样极端的情况下一个二叉搜索树可能退化成一个链表,在插入和查找的时候效率就会很低。
平衡二叉树(AVL)
从任何一个节点出发,左右子树深度之差的绝对值不超过1。
红黑树
红黑树是一种自平衡二叉查找树,它的统计性能要好于平衡二叉树(AVL树)。查找和插入的时间复杂度都是logN。
五个特性(重点)
红黑树通过如下的性质定义实现自平衡:
- 节点不是红色就是黑色。
- 根是黑色。
- 所有叶子都是黑色(叶子是NIL节点)。
- 每个红色节点必须有两个黑色的子节点。不能有两个连续的红色节点。
- 从任一节点到其每个叶子的所有简单路径都包含相同数目的黑色节点(简称黑高)。
这五个性质强制了红黑树的关键性质: 从根到叶子的最长的可能路径不多于最短的可能路径的两倍长。为什么呢?性质4暗示着任何一个简单路径上不能有两个毗连的红色节点,这样,最短的可能路径全是黑色节点,最长的可能路径有交替的红色和黑色节点。同时根据性质5知道:所有最长的路径都有相同数目的黑色节点,这就表明了没有路径能多于任何其他路径的两倍长。
红黑树的基本操作
因为红黑树也是二叉查找树,因此红黑树上的查找操作与普通二叉查找树上的查找操作相同。然而,红黑树上的插入操作和删除操作会导致不再符合红黑树的性质。恢复红黑树的性质需要少量(O(log n))的颜色变更(实际是非常快速的)和不超过三次树旋转(对于插入操作是两次)。虽然插入和删除很复杂,但操作时间仍可以保持为 O(log n) 次。
插入
插入操作可以概括为以下几个步骤:
(1) 查找要插入的位置,时间复杂度为:O(N)
(2) 将新节点的color赋为红色
(3) 自下而上重新调整该树为红黑树
第(1)步的查找方法跟普通二叉查找树一样。
第(2)步之所以将新插入的节点的颜色赋为红色,是因为:如果设为黑色,就会导致根到叶子的路径上有一条路上,多一个额外的黑节点,这个是很难调整的。但是设为红色节点后,可能会导致出现两个连续红色节点的冲突,那么可以通过颜色调换(color flips)和树旋转来调整,这样简单多了。
重点难点是第三步,设要插入的节点为N,其父节点为P,其父亲G的兄弟节点为U(即P和U是同一个节点的两个子节点)。
[1] 如果P是黑色的,则整棵树不必调整便是红黑树。
[2] 如果P是红色的(可知,其父节点G一定是黑色的),则插入z后,违背了性质4,需要进行调整。调整时分以下5种情况:
(1)N是根节点
此时只需要将N由红色改成黑色即可。
**(2)N 的父节点是黑色 **
性质4(每个红色节点必须有两个黑色的子节点)和性质5没有受到影响,不需要调整。
(3)N的父亲P是红色,叔叔U是红色的
如上图所示,由于P和N都是红色,不符合性质4,需要调整。我们将P和U重绘为黑色并重绘节点G为红色。现在新节点N有了一个黑色的父节点P,因为通过父节点P或叔父节点U的任何路径都必定通过祖父节点G,在这些路径上的黑节点数目没有改变(仍然满足性质5)。但是,红色的祖父节点G的父节点也有可能是红色的,这就违反了性质4。为了解决这个问题,我们在祖父节点G上递归调整颜色。
(4)N的父亲P还红色,叔叔U是黑色的,且N是右孩子
我们对P进行一次左旋转调换新节点和其父节点的角色; 接着,按情形(5)处理以前的父节点P以解决仍然失效的性质4。
(5)N的父亲P还红色,N的叔叔U是黑色的,且N是左孩子
如上图所示,对祖父节点G 的一次右旋转; 在旋转产生的树中,以前的父节点P现在是新节点N和以前的祖父节点G 的父节点, 然后交换以前的父节点P和祖父节点G的颜色,结果的树满足性质4,同时性质5也仍然保持满足。
插入总结
上面五种情况中,情况一和情况二比较简单,情况三、四、五稍复杂。但如果细心观察,会发现这三种情况的区别在于叔叔节点的颜色,如果叔叔节点为红色,直接变色即可。如果叔叔节点为黑色,则需要选选择,再交换颜色。当把这三种情况的图画在一起就区别就比较容易观察了,如下图:
删除
相较于插入操作,红黑树的删除操作则要更为复杂一些。删除操作首先要确定待删除节点有几个孩子,如果有两个孩子,不能直接删除该节点。而是要先找到该节点的前驱(该节点左子树中最大的节点)或者后继(该节点右子树中最小的节点),然后将前驱或者后继的值复制到要删除的节点中,最后再将前驱或后继删除。由于前驱和后继至多只有一个孩子节点,这样我们就把原来要删除的节点有两个孩子的问题转化为只有一个孩子节点的问题,问题被简化了一些。我们并不关心最终被删除的节点是否是我们开始想要删除的那个节点,只要节点里的值最终被删除就行了,至于树结构如何变化,这个并不重要。
删除操作可以概括为以下几个步骤:
(1) 查找要删除位置,时间复杂度为:O(N)
(2) 用删除节点后继或者节点替换该节点(只进行数据替换即可,不必调整指针,后继节点是中序遍历中紧挨着该节点的节点,即:右孩子的最左孩子节点)
(3) 如果删除节点的替换节点为黑色,则需重新调整该树为红黑树
其中,第(1)步的查找方法跟普通二叉查找树一样,
第(2)步之所以用后继节点替换删除节点,是因为这样可以保证该后继节点之上仍是一个红黑树,而后继节点可能是一个叶节点或者只有右子树的节点,这样只需用有节点替换后继节点即可达到删除的目的。如果需要删除的节点有两个儿子,那么问题可以被转化成删除另一个只有一个儿子的节点的问题。
在第(3)步中,如果,如果删除节点为红色节点,则他的父亲和孩子全为黑节点,这样直接删除该节点即可,不必进行任何调整。如果删除节点是黑节点,分四种情况:
设要删除的节点为N,其父节点为P,其兄弟节点为S。
由于N是黑色的,则P可能是黑色的,也可能是红色的,S也可能是黑色的或者红色的
(1)S是红色的
此时P肯定是红色的。我们对N的父节点进行左旋转,然后把红色兄弟转换成N的祖父。我们接着对调 N 的父亲和祖父的颜色。尽管所有的路径仍然有相同数目的黑色节点,现在 N 有了一个黑色的兄弟和一个红色的父亲,所以我们可以接下去按 (2)、(3)或(4)情况来处理。
(2)S和S的孩子全是黑色的
在这种情况下,P可能是黑色的或者红色的,我们简单的重绘S 为红色。结果是通过S的所有路径,它们就是以前不通过 N 的那些路径,都少了一个黑色节点。因为删除 N 的初始的父亲使通过 N 的所有路径少了一个黑色节点,这使事情都平衡了起来。但是,通过 P 的所有路径现在比不通过 P 的路径少了一个黑色节点。接下来,要调整以P作为N递归调整树。
(3)S是黑色的,S的左孩子是红色,右孩子是黑色
这种情况下我们在 S 上做右旋转,这样 S 的左儿子成为 S 的父亲和 N 的新兄弟。我们接着交换 S 和它的新父亲的颜色。所有路径仍有同样数目的黑色节点,但是现在 N 有了一个右儿子是红色的黑色兄弟,所以我们进入了情况(4)。N 和它的父亲都不受这个变换的影响。
(4)S是黑色的,S的右孩子是红色
在这种情况下我们在 N 的父亲上做左旋转,这样 S 成为 N 的父亲和 S 的右儿子的父亲。我们接着交换 N 的父亲和 S 的颜色,并使 S 的右儿子为黑色。子树在它的根上的仍是同样的颜色,所以属性 3 没有被违反。但是,N 现在增加了一个黑色祖先: 要么 N 的父亲变成黑色,要么它是黑色而 S 被增加为一个黑色祖父。所以,通过 N 的路径都增加了一个黑色节点。
转载地址:董的博客