作者:美团点评技术团队

链接:https://zhuanlan.zhihu.com/p/24367771

来源:知乎

著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

红黑树是平衡二叉查找树的一种。为了深入理解红黑树,我们需要从二叉查找树开始讲起。

BST

二叉查找树(Binary Search Tree,简称BST)是一棵二叉树,它的左子节点的值比父节点的值要小,右节点的值要比父节点的值大。它的高度决定了它的查找效率。

在理想的情况下,二叉查找树增删查改的时间复杂度为O(logN)(其中N为节点数),最坏的情况下为O(N)。当它的高度为logN+1时,我们就说二叉查找树是平衡的。

BST的查找操作

  1. T key = a search key
  2. Node root = point to the root of a BST
  3. while(true){
  4. if(root==null){
  5. break;
  6. }
  7. if(root.value.equals(key)){
  8. return root;
  9. }
  10. else if(key.compareTo(root.value)<0){
  11. root = root.left;
  12. }
  13. else{
  14. root = root.right;
  15. }
  16. }
  17. return null;

从程序中可以看出,当BST查找的时候,先与当前节点进行比较:

  • 如果相等的话就返回当前节点;
  • 如果少于当前节点则继续查找当前节点的左节点;
  • 如果大于当前节点则继续查找当前节点的右节点。

直到当前节点指针为空或者查找到对应的节点,程序查找结束。

BST的插入操作

  1. Node node = create a new node with specify value
  2. Node root = point the root node of a BST
  3. Node parent = null;
  4. //find the parent node to append the new node
  5. while(true){
  6. if(root==null)break;
  7. parent = root;
  8. if(node.value.compareTo(root.value)<=0){
  9. root = root.left;
  10. }else{
  11. root = root.right;
  12. }
  13. }
  14. if(parent!=null){
  15. if(node.value.compareTo(parent.value)<=0){//append to left
  16. parent.left = node;
  17. }else{//append to right
  18. parent.right = node;
  19. }
  20. }

插入操作先通过循环查找到待插入的节点的父节点,和查找父节点的逻辑一样,都是比大小,小的往左,大的往右。找到父节点后,对比父节点,小的就插入到父节点的左节点,大就插入到父节点的右节点上。

BST的删除操作

删除操作的步骤如下:

  1. 查找到要删除的节点。
  2. 如果待删除的节点是叶子节点,则直接删除。
  3. 如果待删除的节点不是叶子节点,则先找到待删除节点的中序遍历的后继节点,用该后继节点的值替换待删除的节点的值,然后删除后继节点。

BST存在的问题

BST存在的主要问题是,数在插入的时候会导致树倾斜,不同的插入顺序会导致树的高度不一样,而树的高度直接的影响了树的查找效率。理想的高度是logN,最坏的情况是所有的节点都在一条斜线上,这样的树的高度为N。

RBTree

基于BST存在的问题,一种新的树——平衡二叉查找树(Balanced BST)产生了。平衡树在插入和删除的时候,会通过旋转操作将高度保持在logN。其中两款具有代表性的平衡树分别为AVL树和红黑树。AVL树由于实现比较复杂,而且插入和删除性能差,在实际环境下的应用不如红黑树。

红黑树(Red-Black Tree,以下简称RBTree)的实际应用非常广泛,比如Linux内核中的完全公平调度器、高精度计时器、ext3文件系统等等,各种语言的函数库如Java的TreeMap和TreeSet,C++ STL的map、multimap、multiset等。

RBTree也是函数式语言中最常用的持久数据结构之一,在计算几何中也有重要作用。值得一提的是,Java 8中HashMap的实现也因为用RBTree取代链表,性能有所提升。

RBTree的定义

RBTree的定义如下:

  1. 任何一个节点都有颜色,黑色或者红色
  2. 根节点是黑色的
  3. 父子节点之间不能出现两个连续的红节点
  4. 任何一个节点向下遍历到其子孙的叶子节点,所经过的黑节点个数必须相等
  5. 空节点被认为是黑色的

数据结构表示如下:

  1. class Node<T>{
  2. public T value;
  3. public Node<T> parent;
  4. public boolean isRed;
  5. public Node<T> left;
  6. public Node<T> right;
  7. }

RBTree在理论上还是一棵BST树,但是它在对BST的插入和删除操作时会维持树的平衡,即保证树的高度在[logN,logN+1](理论上,极端的情况下可以出现RBTree的高度达到2*logN,但实际上很难遇到)。这样RBTree的查找时间复杂度始终保持在O(logN)从而接近于理想的BST。RBTree的删除和插入操作的时间复杂度也是O(logN)。RBTree的查找操作就是BST的查找操作。

RBTree的旋转操作

旋转操作(Rotate)的目的是使节点颜色符合定义,让RBTree的高度达到平衡。

Rotate分为left-rotate(左旋)和right-rotate(右旋),区分左旋和右旋的方法是:待旋转的节点从左边上升到父节点就是右旋,待旋转的节点从右边上升到父节点就是左旋。

RBTree的查找操作

RBTree的查找操作和BST的查找操作是一样的。请参考BST的查找操作代码。

RBTree的插入操作

RBTree的插入与BST的插入方式是一致的,只不过是在插入过后,可能会导致树的不平衡,这时就需要对树进行旋转操作和颜色修复(在这里简称插入修复),使得它符合RBTree的定义。

新插入的节点是红色的,插入修复操作如果遇到父节点的颜色为黑则修复操作结束。也就是说,只有在父节点为红色节点的时候是需要插入修复操作的。

插入修复操作分为以下的三种情况,而且新插入的节点的父节点都是红色的:

  1. 叔叔节点也为红色。
  2. 叔叔节点为空,且祖父节点、父节点和新节点处于一条斜线上。
  3. 叔叔节点为空,且祖父节点、父节点和新节点不处于一条斜线上。

插入操作-case 1

case 1的操作是将父节点和叔叔节点与祖父节点的颜色互换,这样就符合了RBTRee的定义。即维持了高度的平衡,修复后颜色也符合RBTree定义的第三条和第四条。下图中,操作完成后A节点变成了新的节点。如果A节点的父节点不是黑色的话,则继续做修复操作。

插入操作-case 2

case 2的操作是将B节点进行右旋操作,并且和父节点A互换颜色。通过该修复操作RBTRee的高度和颜色都符合红黑树的定义。如果B和C节点都是右节点的话,只要将操作变成左旋就可以了。

插入操作-case 3

case 3的操作是将C节点进行左旋,这样就从case 3转换成case 2了,然后针对case 2进行操作处理就行了。case 2操作做了一个右旋操作和颜色互换来达到目的。如果树的结构是下图的镜像结构,则只需要将对应的左旋变成右旋,右旋变成左旋即可。

插入操作的总结

插入后的修复操作是一个向root节点回溯的操作,一旦牵涉的节点都符合了红黑树的定义,修复操作结束。之所以会向上回溯是由于case 1操作会将父节点,叔叔节点和祖父节点进行换颜色,有可能会导致祖父节点不平衡(红黑树定义3)。这个时候需要对祖父节点为起点进行调节(向上回溯)。

祖父节点调节后如果还是遇到它的祖父颜色问题,操作就会继续向上回溯,直到root节点为止,根据定义root节点永远是黑色的。在向上的追溯的过程中,针对插入的3中情况进行调节。直到符合红黑树的定义为止。直到牵涉的节点都符合了红黑树的定义,修复操作结束。

如果上面的3中情况如果对应的操作是在右子树上,做对应的镜像操作就是了。

RBTree的删除操作

删除操作首先需要做的也是BST的删除操作,删除操作会删除对应的节点,如果是叶子节点就直接删除,如果是非叶子节点,会用对应的中序遍历的后继节点来顶替要删除节点的位置。删除后就需要做删除修复操作,使的树符合红黑树的定义,符合定义的红黑树高度是平衡的。

删除修复操作在遇到被删除的节点是红色节点或者到达root节点时,修复操作完毕。

删除修复操作是针对删除黑色节点才有的,当黑色节点被删除后会让整个树不符合RBTree的定义的第四条。需要做的处理是从兄弟节点上借调黑色的节点过来,如果兄弟节点没有黑节点可以借调的话,就只能往上追溯,将每一级的黑节点数减去一个,使得整棵树符合红黑树的定义。

删除操作的总体思想是从兄弟节点借调黑色节点使树保持局部的平衡,如果局部的平衡达到了,就看整体的树是否是平衡的,如果不平衡就接着向上追溯调整。

删除修复操作分为四种情况(删除黑节点后):

  1. 待删除的节点的兄弟节点是红色的节点。
  2. 待删除的节点的兄弟节点是黑色的节点,且兄弟节点的子节点都是黑色的。
  3. 待调整的节点的兄弟节点是黑色的节点,且兄弟节点的左子节点是红色的,右节点是黑色的(兄弟节点在右边),如果兄弟节点在左边的话,就是兄弟节点的右子节点是红色的,左节点是黑色的。
  4. 待调整的节点的兄弟节点是黑色的节点,且右子节点是是红色的(兄弟节点在右边),如果兄弟节点在左边,则就是对应的就是左节点是红色的。

删除操作-case 1

由于兄弟节点是红色节点的时候,无法借调黑节点,所以需要将兄弟节点提升到父节点,由于兄弟节点是红色的,根据RBTree的定义,兄弟节点的子节点是黑色的,就可以从它的子节点借调了。

case 1这样转换之后就会变成后面的case 2,case 3,或者case 4进行处理了。上升操作需要对C做一个左旋操作,如果是镜像结构的树只需要做对应的右旋操作即可。

之所以要做case 1操作是因为兄弟节点是红色的,无法借到一个黑节点来填补删除的黑节点。

删除操作-case 2

case 2的删除操作是由于兄弟节点可以消除一个黑色节点,因为兄弟节点和兄弟节点的子节点都是黑色的,所以可以将兄弟节点变红,这样就可以保证树的局部的颜色符合定义了。这个时候需要将父节点A变成新的节点,继续向上调整,直到整颗树的颜色符合RBTree的定义为止。

case 2这种情况下之所以要将兄弟节点变红,是因为如果把兄弟节点借调过来,会导致兄弟的结构不符合RBTree的定义,这样的情况下只能是将兄弟节点也变成红色来达到颜色的平衡。当将兄弟节点也变红之后,达到了局部的平衡了,但是对于祖父节点来说是不符合定义4的。这样就需要回溯到父节点,接着进行修复操作。

删除操作-case 3

case 3的删除操作是一个中间步骤,它的目的是将左边的红色节点借调过来,这样就可以转换成case 4状态了,在case 4状态下可以将D,E节点都阶段过来,通过将两个节点变成黑色来保证红黑树的整体平衡。

之所以说case-3是一个中间状态,是因为根据红黑树的定义来说,下图并不是平衡的,他是通过case 2操作完后向上回溯出现的状态。之所以会出现case 3和后面的case 4的情况,是因为可以通过借用侄子节点的红色,变成黑色来符合红黑树定义4.

删除操作-case 4

Case 4的操作是真正的节点借调操作,通过将兄弟节点以及兄弟节点的右节点借调过来,并将兄弟节点的右子节点变成红色来达到借调两个黑节点的目的,这样的话,整棵树还是符合RBTree的定义的。

Case 4这种情况的发生只有在待删除的节点的兄弟节点为黑,且子节点不全部为黑,才有可能借调到两个节点来做黑节点使用,从而保持整棵树都符合红黑树的定义。

删除操作的总结

红黑树的删除操作是最复杂的操作,复杂的地方就在于当删除了黑色节点的时候,如何从兄弟节点去借调节点,以保证树的颜色符合定义。由于红色的兄弟节点是没法借调出黑节点的,这样只能通过选择操作让他上升到父节点,而由于它是红节点,所以它的子节点就是黑的,可以借调。

对于兄弟节点是黑色节点的可以分成3种情况来处理,当所以的兄弟节点的子节点都是黑色节点时,可以直接将兄弟节点变红,这样局部的红黑树颜色是符合定义的。但是整颗树不一定是符合红黑树定义的,需要往上追溯继续调整。

对于兄弟节点的子节点为左红右黑或者 (全部为红,右红左黑)这两种情况,可以先将前面的情况通过选择转换为后一种情况,在后一种情况下,因为兄弟节点为黑,兄弟节点的右节点为红,可以借调出两个节点出来做黑节点,这样就可以保证删除了黑节点,整棵树还是符合红黑树的定义的,因为黑色节点的个数没有改变。

红黑树的删除操作是遇到删除的节点为红色,或者追溯调整到了root节点,这时删除的修复操作完毕。

RBTree的Java实现

  1. public class RBTreeNode<T extends Comparable<T>> {
  2. private T value;//node value
  3. private RBTreeNode<T> left;//left child pointer
  4. private RBTreeNode<T> right;//right child pointer
  5. private RBTreeNode<T> parent;//parent pointer
  6. private boolean red;//color is red or not red
  7. public RBTreeNode(){}
  8. public RBTreeNode(T value){this.value=value;}
  9. public RBTreeNode(T value,boolean isRed){this.value=value;this.red = isRed;}
  10. public T getValue() {
  11. return value;
  12. }
  13. void setValue(T value) {
  14. this.value = value;
  15. }
  16. RBTreeNode<T> getLeft() {
  17. return left;
  18. }
  19. void setLeft(RBTreeNode<T> left) {
  20. this.left = left;
  21. }
  22. RBTreeNode<T> getRight() {
  23. return right;
  24. }
  25. void setRight(RBTreeNode<T> right) {
  26. this.right = right;
  27. }
  28. RBTreeNode<T> getParent() {
  29. return parent;
  30. }
  31. void setParent(RBTreeNode<T> parent) {
  32. this.parent = parent;
  33. }
  34. boolean isRed() {
  35. return red;
  36. }
  37. boolean isBlack(){
  38. return !red;
  39. }
  40. /**
  41. * is leaf node
  42. **/
  43. boolean isLeaf(){
  44. return left==null && right==null;
  45. }
  46. void setRed(boolean red) {
  47. this.red = red;
  48. }
  49. void makeRed(){
  50. red=true;
  51. }
  52. void makeBlack(){
  53. red=false;
  54. }
  55. @Override
  56. public String toString(){
  57. return value.toString();
  58. }
  59. }
  60. public class RBTree<T extends Comparable<T>> {
  61. private final RBTreeNode<T> root;
  62. //node number
  63. private java.util.concurrent.atomic.AtomicLong size =
  64. new java.util.concurrent.atomic.AtomicLong(0);
  65. //in overwrite mode,all node's value can not has same value
  66. //in non-overwrite mode,node can have same value, suggest don't use non-overwrite mode.
  67. private volatile boolean overrideMode=true;
  68. public RBTree(){
  69. this.root = new RBTreeNode<T>();
  70. }
  71. public RBTree(boolean overrideMode){
  72. this();
  73. this.overrideMode=overrideMode;
  74. }
  75. public boolean isOverrideMode() {
  76. return overrideMode;
  77. }
  78. public void setOverrideMode(boolean overrideMode) {
  79. this.overrideMode = overrideMode;
  80. }
  81. /**
  82. * number of tree number
  83. * @return
  84. */
  85. public long getSize() {
  86. return size.get();
  87. }
  88. /**
  89. * get the root node
  90. * @return
  91. */
  92. private RBTreeNode<T> getRoot(){
  93. return root.getLeft();
  94. }
  95. /**
  96. * add value to a new node,if this value exist in this tree,
  97. * if value exist,it will return the exist value.otherwise return null
  98. * if override mode is true,if value exist in the tree,
  99. * it will override the old value in the tree
  100. *
  101. * @param value
  102. * @return
  103. */
  104. public T addNode(T value){
  105. RBTreeNode<T> t = new RBTreeNode<T>(value);
  106. return addNode(t);
  107. }
  108. /**
  109. * find the value by give value(include key,key used for search,
  110. * other field is not used,@see compare method).if this value not exist return null
  111. * @param value
  112. * @return
  113. */
  114. public T find(T value){
  115. RBTreeNode<T> dataRoot = getRoot();
  116. while(dataRoot!=null){
  117. int cmp = dataRoot.getValue().compareTo(value);
  118. if(cmp<0){
  119. dataRoot = dataRoot.getRight();
  120. }else if(cmp>0){
  121. dataRoot = dataRoot.getLeft();
  122. }else{
  123. return dataRoot.getValue();
  124. }
  125. }
  126. return null;
  127. }
  128. /**
  129. * remove the node by give value,if this value not exists in tree return null
  130. * @param value include search key
  131. * @return the value contain in the removed node
  132. */
  133. public T remove(T value){
  134. RBTreeNode<T> dataRoot = getRoot();
  135. RBTreeNode<T> parent = root;
  136. while(dataRoot!=null){
  137. int cmp = dataRoot.getValue().compareTo(value);
  138. if(cmp<0){
  139. parent = dataRoot;
  140. dataRoot = dataRoot.getRight();
  141. }else if(cmp>0){
  142. parent = dataRoot;
  143. dataRoot = dataRoot.getLeft();
  144. }else{
  145. if(dataRoot.getRight()!=null){
  146. RBTreeNode<T> min = removeMin(dataRoot.getRight());
  147. //x used for fix color balance
  148. RBTreeNode<T> x = min.getRight()==null ? min.getParent() : min.getRight();
  149. boolean isParent = min.getRight()==null;
  150. min.setLeft(dataRoot.getLeft());
  151. setParent(dataRoot.getLeft(),min);
  152. if(parent.getLeft()==dataRoot){
  153. parent.setLeft(min);
  154. }else{
  155. parent.setRight(min);
  156. }
  157. setParent(min,parent);
  158. boolean curMinIsBlack = min.isBlack();
  159. //inherit dataRoot's color
  160. min.setRed(dataRoot.isRed());
  161. if(min!=dataRoot.getRight()){
  162. min.setRight(dataRoot.getRight());
  163. setParent(dataRoot.getRight(),min);
  164. }
  165. //remove a black node,need fix color
  166. if(curMinIsBlack){
  167. if(min!=dataRoot.getRight()){
  168. fixRemove(x,isParent);
  169. }else if(min.getRight()!=null){
  170. fixRemove(min.getRight(),false);
  171. }else{
  172. fixRemove(min,true);
  173. }
  174. }
  175. }else{
  176. setParent(dataRoot.getLeft(),parent);
  177. if(parent.getLeft()==dataRoot){
  178. parent.setLeft(dataRoot.getLeft());
  179. }else{
  180. parent.setRight(dataRoot.getLeft());
  181. }
  182. //current node is black and tree is not empty
  183. if(dataRoot.isBlack() && !(root.getLeft()==null)){
  184. RBTreeNode<T> x = dataRoot.getLeft()==null
  185. ? parent :dataRoot.getLeft();
  186. boolean isParent = dataRoot.getLeft()==null;
  187. fixRemove(x,isParent);
  188. }
  189. }
  190. setParent(dataRoot,null);
  191. dataRoot.setLeft(null);
  192. dataRoot.setRight(null);
  193. if(getRoot()!=null){
  194. getRoot().setRed(false);
  195. getRoot().setParent(null);
  196. }
  197. size.decrementAndGet();
  198. return dataRoot.getValue();
  199. }
  200. }
  201. return null;
  202. }
  203. /**
  204. * fix remove action
  205. * @param node
  206. * @param isParent
  207. */
  208. private void fixRemove(RBTreeNode<T> node,boolean isParent){
  209. RBTreeNode<T> cur = isParent ? null : node;
  210. boolean isRed = isParent ? false : node.isRed();
  211. RBTreeNode<T> parent = isParent ? node : node.getParent();
  212. while(!isRed && !isRoot(cur)){
  213. RBTreeNode<T> sibling = getSibling(cur,parent);
  214. //sibling is not null,due to before remove tree color is balance
  215. //if cur is a left node
  216. boolean isLeft = parent.getRight()==sibling;
  217. if(sibling.isRed() && !isLeft){//case 1
  218. //cur in right
  219. parent.makeRed();
  220. sibling.makeBlack();
  221. rotateRight(parent);
  222. }else if(sibling.isRed() && isLeft){
  223. //cur in left
  224. parent.makeRed();
  225. sibling.makeBlack();
  226. rotateLeft(parent);
  227. }else if(isBlack(sibling.getLeft()) && isBlack(sibling.getRight())){//case 2
  228. sibling.makeRed();
  229. cur = parent;
  230. isRed = cur.isRed();
  231. parent=parent.getParent();
  232. }else if(isLeft && !isBlack(sibling.getLeft())
  233. && isBlack(sibling.getRight())){//case 3
  234. sibling.makeRed();
  235. sibling.getLeft().makeBlack();
  236. rotateRight(sibling);
  237. }else if(!isLeft && !isBlack(sibling.getRight())
  238. && isBlack(sibling.getLeft()) ){
  239. sibling.makeRed();
  240. sibling.getRight().makeBlack();
  241. rotateLeft(sibling);
  242. }else if(isLeft && !isBlack(sibling.getRight())){//case 4
  243. sibling.setRed(parent.isRed());
  244. parent.makeBlack();
  245. sibling.getRight().makeBlack();
  246. rotateLeft(parent);
  247. cur=getRoot();
  248. }else if(!isLeft && !isBlack(sibling.getLeft())){
  249. sibling.setRed(parent.isRed());
  250. parent.makeBlack();
  251. sibling.getLeft().makeBlack();
  252. rotateRight(parent);
  253. cur=getRoot();
  254. }
  255. }
  256. if(isRed){
  257. cur.makeBlack();
  258. }
  259. if(getRoot()!=null){
  260. getRoot().setRed(false);
  261. getRoot().setParent(null);
  262. }
  263. }
  264. //get sibling node
  265. private RBTreeNode<T> getSibling(RBTreeNode<T> node,RBTreeNode<T> parent){
  266. parent = node==null ? parent : node.getParent();
  267. if(node==null){
  268. return parent.getLeft()==null ? parent.getRight() : parent.getLeft();
  269. }
  270. if(node==parent.getLeft()){
  271. return parent.getRight();
  272. }else{
  273. return parent.getLeft();
  274. }
  275. }
  276. private boolean isBlack(RBTreeNode<T> node){
  277. return node==null || node.isBlack();
  278. }
  279. private boolean isRoot(RBTreeNode<T> node){
  280. return root.getLeft() == node && node.getParent()==null;
  281. }
  282. /**
  283. * find the successor node
  284. * @param node current node's right node
  285. * @return
  286. */
  287. private RBTreeNode<T> removeMin(RBTreeNode<T> node){
  288. //find the min node
  289. RBTreeNode<T> parent = node;
  290. while(node!=null && node.getLeft()!=null){
  291. parent = node;
  292. node = node.getLeft();
  293. }
  294. //remove min node
  295. if(parent==node){
  296. return node;
  297. }
  298. parent.setLeft(node.getRight());
  299. setParent(node.getRight(),parent);
  300. //don't remove right pointer,it is used for fixed color balance
  301. //node.setRight(null);
  302. return node;
  303. }
  304. private T addNode(RBTreeNode<T> node){
  305. node.setLeft(null);
  306. node.setRight(null);
  307. node.setRed(true);
  308. setParent(node,null);
  309. if(root.getLeft()==null){
  310. root.setLeft(node);
  311. //root node is black
  312. node.setRed(false);
  313. size.incrementAndGet();
  314. }else{
  315. RBTreeNode<T> x = findParentNode(node);
  316. int cmp = x.getValue().compareTo(node.getValue());
  317. if(this.overrideMode && cmp==0){
  318. T v = x.getValue();
  319. x.setValue(node.getValue());
  320. return v;
  321. }else if(cmp==0){
  322. //value exists,ignore this node
  323. return x.getValue();
  324. }
  325. setParent(node,x);
  326. if(cmp>0){
  327. x.setLeft(node);
  328. }else{
  329. x.setRight(node);
  330. }
  331. fixInsert(node);
  332. size.incrementAndGet();
  333. }
  334. return null;
  335. }
  336. /**
  337. * find the parent node to hold node x,if parent value equals x.value return parent.
  338. * @param x
  339. * @return
  340. */
  341. private RBTreeNode<T> findParentNode(RBTreeNode<T> x){
  342. RBTreeNode<T> dataRoot = getRoot();
  343. RBTreeNode<T> child = dataRoot;
  344. while(child!=null){
  345. int cmp = child.getValue().compareTo(x.getValue());
  346. if(cmp==0){
  347. return child;
  348. }
  349. if(cmp>0){
  350. dataRoot = child;
  351. child = child.getLeft();
  352. }else if(cmp<0){
  353. dataRoot = child;
  354. child = child.getRight();
  355. }
  356. }
  357. return dataRoot;
  358. }
  359. /**
  360. * red black tree insert fix.
  361. * @param x
  362. */
  363. private void fixInsert(RBTreeNode<T> x){
  364. RBTreeNode<T> parent = x.getParent();
  365. while(parent!=null && parent.isRed()){
  366. RBTreeNode<T> uncle = getUncle(x);
  367. if(uncle==null){//need to rotate
  368. RBTreeNode<T> ancestor = parent.getParent();
  369. //ancestor is not null due to before before add,tree color is balance
  370. if(parent == ancestor.getLeft()){
  371. boolean isRight = x == parent.getRight();
  372. if(isRight){
  373. rotateLeft(parent);
  374. }
  375. rotateRight(ancestor);
  376. if(isRight){
  377. x.setRed(false);
  378. parent=null;//end loop
  379. }else{
  380. parent.setRed(false);
  381. }
  382. ancestor.setRed(true);
  383. }else{
  384. boolean isLeft = x == parent.getLeft();
  385. if(isLeft){
  386. rotateRight(parent);
  387. }
  388. rotateLeft(ancestor);
  389. if(isLeft){
  390. x.setRed(false);
  391. parent=null;//end loop
  392. }else{
  393. parent.setRed(false);
  394. }
  395. ancestor.setRed(true);
  396. }
  397. }else{//uncle is red
  398. parent.setRed(false);
  399. uncle.setRed(false);
  400. parent.getParent().setRed(true);
  401. x=parent.getParent();
  402. parent = x.getParent();
  403. }
  404. }
  405. getRoot().makeBlack();
  406. getRoot().setParent(null);
  407. }
  408. /**
  409. * get uncle node
  410. * @param node
  411. * @return
  412. */
  413. private RBTreeNode<T> getUncle(RBTreeNode<T> node){
  414. RBTreeNode<T> parent = node.getParent();
  415. RBTreeNode<T> ancestor = parent.getParent();
  416. if(ancestor==null){
  417. return null;
  418. }
  419. if(parent == ancestor.getLeft()){
  420. return ancestor.getRight();
  421. }else{
  422. return ancestor.getLeft();
  423. }
  424. }
  425. private void rotateLeft(RBTreeNode<T> node){
  426. RBTreeNode<T> right = node.getRight();
  427. if(right==null){
  428. throw new java.lang.IllegalStateException("right node is null");
  429. }
  430. RBTreeNode<T> parent = node.getParent();
  431. node.setRight(right.getLeft());
  432. setParent(right.getLeft(),node);
  433. right.setLeft(node);
  434. setParent(node,right);
  435. if(parent==null){//node pointer to root
  436. //right raise to root node
  437. root.setLeft(right);
  438. setParent(right,null);
  439. }else{
  440. if(parent.getLeft()==node){
  441. parent.setLeft(right);
  442. }else{
  443. parent.setRight(right);
  444. }
  445. //right.setParent(parent);
  446. setParent(right,parent);
  447. }
  448. }
  449. private void rotateRight(RBTreeNode<T> node){
  450. RBTreeNode<T> left = node.getLeft();
  451. if(left==null){
  452. throw new java.lang.IllegalStateException("left node is null");
  453. }
  454. RBTreeNode<T> parent = node.getParent();
  455. node.setLeft(left.getRight());
  456. setParent(left.getRight(),node);
  457. left.setRight(node);
  458. setParent(node,left);
  459. if(parent==null){
  460. root.setLeft(left);
  461. setParent(left,null);
  462. }else{
  463. if(parent.getLeft()==node){
  464. parent.setLeft(left);
  465. }else{
  466. parent.setRight(left);
  467. }
  468. setParent(left,parent);
  469. }
  470. }
  471. private void setParent(RBTreeNode<T> node,RBTreeNode<T> parent){
  472. if(node!=null){
  473. node.setParent(parent);
  474. if(parent==root){
  475. node.setParent(null);
  476. }
  477. }
  478. }
  479. /**
  480. * debug method,it used print the given node and its children nodes,
  481. * every layer output in one line
  482. * @param root
  483. */
  484. public void printTree(RBTreeNode<T> root){
  485. java.util.LinkedList<RBTreeNode<T>> queue =new java.util.LinkedList<RBTreeNode<T>>();
  486. java.util.LinkedList<RBTreeNode<T>> queue2 =new java.util.LinkedList<RBTreeNode<T>>();
  487. if(root==null){
  488. return ;
  489. }
  490. queue.add(root);
  491. boolean firstQueue = true;
  492. while(!queue.isEmpty() || !queue2.isEmpty()){
  493. java.util.LinkedList<RBTreeNode<T>> q = firstQueue ? queue : queue2;
  494. RBTreeNode<T> n = q.poll();
  495. if(n!=null){
  496. String pos = n.getParent()==null ? "" : ( n == n.getParent().getLeft()
  497. ? " LE" : " RI");
  498. String pstr = n.getParent()==null ? "" : n.getParent().toString();
  499. String cstr = n.isRed()?"R":"B";
  500. cstr = n.getParent()==null ? cstr : cstr+" ";
  501. System.out.print(n+"("+(cstr)+pstr+(pos)+")"+"\t");
  502. if(n.getLeft()!=null){
  503. (firstQueue ? queue2 : queue).add(n.getLeft());
  504. }
  505. if(n.getRight()!=null){
  506. (firstQueue ? queue2 : queue).add(n.getRight());
  507. }
  508. }else{
  509. System.out.println();
  510. firstQueue = !firstQueue;
  511. }
  512. }
  513. }
  514. public static void main(String[] args) {
  515. RBTree<String> bst = new RBTree<String>();
  516. bst.addNode("d");
  517. bst.addNode("d");
  518. bst.addNode("c");
  519. bst.addNode("c");
  520. bst.addNode("b");
  521. bst.addNode("f");
  522. bst.addNode("a");
  523. bst.addNode("e");
  524. bst.addNode("g");
  525. bst.addNode("h");
  526. bst.remove("c");
  527. bst.printTree(bst.getRoot());
  528. }
  529. }

代码调试的时候,printTree输出格式如下:

d(B)

b(B d LE) g(R d RI)

a(R b LE) e(B g LE) h(B g RI)

f(R e RI)

括号左边表示元素的内容。括号内的第一个元素表示颜色,B表示black,R表示red;第二个元素表示父元素的值;第三个元素表示左右,LE表示在父元素的左边。RI表示在父元素的右边。

第一个元素d是root节点,由于它没有父节点,所以括号内只有一个元素。

总结

作为平衡二叉查找树里面众多的实现之一,红黑树无疑是最简洁、实现最为简单的。红黑树通过引入颜色的概念,通过颜色这个约束条件的使用来保持树的高度平衡。作为平衡二叉查找树,旋转是一个必不可少的操作。通过旋转可以降低树的高度,在红黑树里面还可以转换颜色。

红黑树里面的插入和删除的操作比较难理解,这时要注意记住一点:操作之前红黑树是平衡的,颜色是符合定义的。在操作的时候就需要向兄弟节点、父节点、侄子节点借调和互换颜色,要达到这个目的,就需要不断的进行旋转。所以红黑树的插入删除操作需要不停的旋转,一旦借调了别的节点,删除和插入的节点就会达到局部的平衡(局部符合红黑树的定义),但是被借调的节点就不会平衡了,这时就需要以被借调的节点为起点继续进行调整,直到整棵树都是平衡的。在整个修复的过程中,插入具体的分为3种情况,删除分为4种情况。

整个红黑树的查找,插入和删除都是O(logN)的,原因就是整个红黑树的高度是logN,查找从根到叶,走过的路径是树的高度,删除和插入操作是从叶到根的,所以经过的路径都是logN。

参考文献

  1. Cormen T H, Leiserson C E, Rivest R L, 等. 算法导论(第3版). 殷建平, 等. 机械工业出版社, 2012.
  2. Sedgewick R, Wayne K. 算法(第4版). 谢路云 译. 人民邮电出版社, 2012.
  3. Weiss M A. 数据结构与算法分析(第2版). 冯舜玺 译. 机械工业出版社, 2004.
  4. Knuth D E. 计算机程序设计艺术 卷3:排序与查找(英文版 第2版). 人民邮电出版社, 2010.

不想错过技术博客更新?想给文章评论、和作者互动?第一时间获取技术沙龙信息?

请关注我们的官方微信公众号“美团点评技术团队”。

红黑树深入剖析及Java实现(转自知乎美团点评技术团队)的更多相关文章

  1. 红黑树深入剖析及Java实现

    红黑树是平衡二叉查找树的一种.为了深入理解红黑树,我们需要从二叉查找树开始讲起. BST 二叉查找树(Binary Search Tree,简称BST)是一棵二叉树,它的左子节点的值比父节点的值要小, ...

  2. 红黑树插入操作原理及java实现

    红黑树是一种二叉平衡查找树,每个结点上有一个存储位来表示结点的颜色,可以是RED或BLACK.红黑树具有以下性质: (1) 每个结点是红色或是黑色 (2) 根结点是黑色的 (3) 如果一个结点是红色的 ...

  3. Java集合详解6:这次,从头到尾带你解读Java中的红黑树

    <Java集合详解系列>是我在完成夯实Java基础篇的系列博客后准备开始写的新系列. 这些文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查 ...

  4. Java - TreeMap源码解析 + 红黑树

    Java提高篇(二七)-----TreeMap TreeMap的实现是红黑树算法的实现,所以要了解TreeMap就必须对红黑树有一定的了解,其实这篇博文的名字叫做:根据红黑树的算法来分析TreeMap ...

  5. 红黑树(五)之 Java的实现

    概要 前面分别介绍红黑树的理论知识.红黑树的C语言和C++的实现.本章介绍红黑树的Java实现,若读者对红黑树的理论知识不熟悉,建立先学习红黑树的理论知识,再来学习本章.还是那句老话,红黑树的C/C+ ...

  6. 红黑树 Java实现

    概要 前面分别介绍红黑树的理论知识.红黑树的C语言和C++的实现.本章介绍红黑树的Java实现,若读者对红黑树的理论知识不熟悉,建立先学习红黑树的理论知识,再来学习本章.还是那句老话,红黑树的C/C+ ...

  7. Java数据结构和算法(八)--红黑树与2-3树

    红黑树规则: 1.每个节点要么是红色,要么是黑色 2.根节点都是黑色节点 3.每个叶节点是黑色节点 3.每个红色节点的两个子节点都是黑色节点,反之,不做要求,换句话说就是不能有连续两个红色节点 4.从 ...

  8. TreeMap----的实现原理(红黑树)

    TreeMap的实现是红黑树算法的实现,所以要了解TreeMap就必须对红黑树有一定的了解,其实这篇博文的名字叫做:根据红黑树的算法来分析TreeMap的实现,但是为了与Java提高篇系列博文保持一致 ...

  9. 红黑树、B(+)树、跳表、AVL等数据结构,应用场景及分析,以及一些英文缩写

    在网上学习了一些材料. 这一篇:https://www.zhihu.com/question/30527705 AVL树:最早的平衡二叉树之一.应用相对其他数据结构比较少.windows对进程地址空间 ...

随机推荐

  1. 【BZOJ1101】Zap(莫比乌斯反演)

    题意:多组询问,对于给定的整数a,b和d,有多少正整数对x,y,满足x<=a,y<=b,并且gcd(x,y)=d. T,a,b,d,x,y<=50000 思路:下底函数分块+积性函数 ...

  2. 【BZOJ1018】堵塞的交通traffic(线段树,网格图,连通性)

    题意:一个2行C列的矩形网格图,网格上的每个点代表一个城市,相邻的城市之间有一条道路 一开始每条道路都是堵塞的,堵塞即为不可经过.经过一些操作后,可能某些道路通畅了,也可能某些道路堵塞了 多次询问,询 ...

  3. 【HDOJ6217】BBP Formula(公式)

    题意:给定一个无穷项的分式,它的和等于π,问π的十六进制表示的小数点后第n位是多少 1 ≤ n ≤ 100000 思路:From https://blog.csdn.net/meopass/artic ...

  4. BZOJ 4077 Messenger

    Messenger [问题描述] alice和bob各自在两条折线上行进,一个邮递员要从alice那拿一个包裹,并以直线移动到bob处,alice和bob.邮递员的速度均为1单位/s,问邮递员最少要走 ...

  5. 编译 Android 版本的 Opus 音频编解码库的方法

    Opus 音频编解码库是 Speex 音频编解码库的下一代版本,从编解码性能以及质量上来讲都有了长足的进步.Opus 的编译非常简单,但是官方并未给出详细的 Android 版本编译指南,查找了大量资 ...

  6. FMDB使用SQLite事务Save Point

    FMDB使用SQLite事务Save Point   在SQLite中,事务提供了批量处理,批量撤销的功能.当批量操作中有一步无法完成操作,就会把执行过的语句都撤销,恢复到撤销前的状态.但是由于SQL ...

  7. 通配符、正则表达式、python去重

    1.linux通配符 *:代表所有字符(0到多个); ?:代表一个字符; ;:连续不同命令之间的分隔符; #:配置文件注释; |:管道; ~:当前用户的家目录; -:上一次所在的路径; $:变量前面需 ...

  8. Oracle if else 、case when 判断示例

    declare -- 声明奖金的变量 v_comm emp.comm%type; begin -- 查询出员工的奖金 select comm into v_comm from emp where em ...

  9. android权限大全转http://www.cnblogs.com/classic/archive/2011/06/20/2085055.html

    android权限大全转http://www.cnblogs.com/classic/archive/2011/06/20/2085055.html 访问登记属性 android.permission ...

  10. python中a=a+2与a+=2的区别

    1.a=a+2,表示一个新的对象,新的对象名字还是a,但是指向的内存地址已经变了 >>> a=2 >>> id(a) 140406287260016 >> ...