1:首先红黑树是什么的,它的基本性质:
从大到小的范围来描述红黑树,它依次是一棵树>一个二叉树>一个二叉查找树>一个平衡二叉树>一个红黑树。
红黑树的性质:
性质1. 节点是红色或黑色。
性质2. 根节点是黑色。
性质3 每个叶节点是黑色的。(如果发现叶子节点是红色的,也不要疑惑,实际上红色并不是叶子节点,当红色节点作为最后一个元素的时候,会默认在后边添加一个空的nil或者null黑色叶子节点。只不过现在很多地方都会省略显示nil或者null黑色叶子节点)
性质4 每个红色节点的两个子节点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色节点)
性质5. 从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。
2:红黑树可以理解成一个特殊的平衡二叉树那么它和AVL平衡二叉树的有什么异同?
- AVL更平衡,他要求左右子树高度差不超过1,结构上更加直观,时间效能针对读取而言更高;维护稍慢,空间开销较大。红黑树,读取略逊于AVL,维护强于AVL,空间开销与AVL类似,内容极多时略优于AVL,维护优于AVL。
- 红黑树的查询性能略微逊色于AVL树,因为其比AVL树会稍微不平衡最多一层,也就是说红黑树的查询性能只比相同内容的AVL树最多多一次比较,但是,红黑树在插入和删除上优于AVL树,AVL树每次插入删除会进行大量的平衡度计算,而红黑树为了维持红黑性质所做的红黑变换和旋转的开销,相较于AVL树为了维持平衡的开销要小得多。
- 实际应用中,若搜索的次数远远大于插入和删除,那么选择AVL,如果搜索,插入删除次数几乎差不多,应该选择RB。
3:红黑树的变色左旋右旋操作规则
在说红黑树变色左旋右旋操作之前,我们需要知道红黑树所有新插入的结点除了根结点外默认都都是红色
变色前提 :当前结点(指针指向的结点)的父亲是红色,且它的祖父结点的另一个子结点也是红色(叔叔结点)。
规则:
-
把当前结点的父节点设为黑色
-
把当前结点的叔叔也设为黑色
-
把当前结点的祖父也就是父亲的父亲设为红色(爷爷)
-
把指针定义到当前结点的祖父结点,把祖父结点设为当前要操作的结点,此时若还满足变色前提且变色操作不会破坏红黑树其他性质,则基于当前要操作的结点继续执行前三步直到不满足变色前提或者再次变色会破坏红黑树其他性质(比如需要把根节点变成红色)为止,则变色操作结束。
当进行变色操作完成之后依然无法使红黑树满足基本性质要求的,则需要对红黑树进行左旋或者右旋操作来使其满足要求。
左旋前提:当前节点的父结点是红色,叔叔是黑色的时候,且当前结点是右子树(基于父节点)。
左旋动态流程图:
左旋操作是基于当前结点的父亲结点进行左旋的。在这里上图中E代表父亲结点,S代表当前结点。在进行左旋操作之后,S结点旋转到父亲结点E的位置,原父亲结点E变为当前结点S的左子结点,在E和S之间(大于E小于S)的结点从原来是S结点的左子树变成原父亲结点E的右子树。比E小的结点都作为E的左子树,比S大的结点都作为S的右子树
右旋前提:当前节点的父结点是红色,叔叔是黑色的时候,且当前结点是左子树(基于父节点)。
右旋动态流程图:
右旋操作相对左旋操作稍微复杂一点,右旋操作需要先把当前结点的父亲结点变为黑色,然后把其祖父结点变为红色。 最后基于当前结点的祖父结点进行右旋的。在这里上图中E代表当前结点的父节点,S代表当前结点的祖父结点。在进行右旋操作之后。当前结点的父亲结点E旋转到其祖父结点S的位置,其祖父结点S变为其父亲结点E的右子树,原父亲结点E的右子树(在E和S之间的结点)变成其祖父结点S的左子树。比E小的结点都作为E的左子树,比S大的结点都作为S的右子树
最后记录一个可以直观验证红黑树变色左旋右旋等操作流程的网页,该网页是美国旧金山大学下用于帮助学生更好的理解红黑树的一个网页。在这个网页上可以通过输入值动态的观看红黑树的形成过程。网页地址如下https://www.cs.usfca.edu/~galles/visualization/Algorithms.html