红黑树算法原理(从二叉搜索树讲起)
2017-08-05 17:06
615 查看
原文:红黑树深入剖析及Java实现,本文修改了原文的一些小错误,如果想看红黑树的Java实现可以到原文去看。
红黑树是平衡二叉查找树的一种。为了深入理解红黑树,我们需要从二叉查找树开始讲起。
![](http://img.blog.csdn.net/20170804182917968?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvbG9naW5fc29uYXRh/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
从程序中可以看出,当BST查找的时候,先与当前节点进行比较:
如果相等的话就返回当前节点;
如果小于当前节点则继续查找当前节点的左节点;
如果大于当前节点则继续查找当前节点的右节点。
直到当前节点指针为空或者查找到对应的节点,程序查找结束。
插入操作先通过循环查找到待插入的节点的父节点,和查找父节点的逻辑一样,都是比大小,小的往左,大的往右。找到父节点后,对比父节点,小的就插入到父节点的左节点,大就插入到父节点的右节点上。
结点p无左右孩子,则直接删除该结点,修改父节点相应指针。
结点p只有左孩子(右孩子),则把p的父节点和p的左孩子(右孩子)相连,然后删除p。
左右孩子同时存在,找到p的中序直接前驱s,也就是以p的左孩子为根结点的子树中最右的结点,把s的右孩子和p的右孩子相连,p的父节点和p的左孩子相连,然后删除p。
基于BST存在的问题,一种新的树——平衡二叉查找树(Balanced BST)产生了。平衡树在插入和删除的时候,会通过旋转操作将高度保持在logN。其中两款具有代表性的平衡树分别为AVL树和红黑树。AVL树追求全局平衡,导致插入和删除性能差,在实际应用中不如追求局部平衡的红黑树(Red-Black Tree,简称RBTree),比如Linux内核中的完全公平调度器、高精度计时器、ext3文件系统等,各种语言的函数库如Java的TreeMap和TreeSet,C++ STL的map、multimap、multiset等,Java 8中的HashMap也用到了RBTree取代过长的链表。
任何一个节点都有颜色,黑色或者红色;
根节点是黑色的;
父子节点之间不能出现两个连续的红节点;
任何一个节点向下遍历到其子孙的叶子节点,所经过的黑节点个数必须相等;
空节点被认为是黑色的。
数据结构表示如下:
RBTree在理论上还是一棵BST树,但是它在对BST的插入和删除操作时会维持树的平衡,即保证树的高度在[logN,logN+1](极端的情况下可以出现RBTree的高度达到2*logN,但实际上很难遇到)。这样RBTree的查找时间复杂度始终保持在O(logN)从而接近于理想的BST。RBTree的删除和插入操作的时间复杂度也是O(logN)。
RBTree的查找操作就是BST的查找操作。
新插入的节点是红色的,插入修复操作如果遇到父节点的颜色为黑则修复操作结束。也就是说,只有在父节点为红色节点的时候是需要插入修复操作的。
插入修复操作分为以下的三种情况:
此时操作是将父节点和叔叔节点与祖父节点的颜色互换,这样就符合了RBTRee的定义。下图中,操作完成后A节点变成了新的节点。如果A节点的父节点不是黑色的话,则继续做修复操作。
![](http://img.blog.csdn.net/20170805145845794?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvbG9naW5fc29uYXRh/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
此时操作是将B节点进行右旋操作,并且和父节点A互换颜色。通过该修复操作RBTRee的高度和颜色都符合红黑树的定义。如果B和C节点都是右节点的话,只要将操作变成左旋就可以了。
![](http://img.blog.csdn.net/20170805150044276?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvbG9naW5fc29uYXRh/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
此时操作是将C节点进行左旋,这样就从case 3转换成case 2了,然后针对case 2进行操作处理就行了。case 2操作做了一个右旋操作和颜色互换来达到目的。如果树的结构是下图的镜像结构,则只需要将对应的左旋变成右旋,右旋变成左旋即可。
![](http://img.blog.csdn.net/20170805150237859?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvbG9naW5fc29uYXRh/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
删除修复操作是针对删除黑色节点才有的,当黑色节点被删除后会让整个树不符合RBTree的定义的第四条。需要做的处理是从兄弟节点上借调黑色的节点过来,如果兄弟节点没有黑节点可以借调的话,就只能往上追溯,将每一级的黑节点数减去一个,使得整棵树符合红黑树的定义。
删除修复操作在遇到被删除的节点是红色节点或者到达root节点时,修复操作完毕。
删除修复操作分为四种情况:
由于兄弟节点是红色节点的时候,无法借调黑节点,所以需要将兄弟节点上升到父节点,由于兄弟节点是红色的,根据RBTree的定义,兄弟节点的子节点是黑色的,就可以从它的子节点借调了。
case 1这样转换之后就会变成后面的case 2,case 3,或者case 4中的一种了。上升操作需要对兄弟节点做一个左旋或右旋操作,然后和父结点变换颜色,兄弟节点的左孩子旋转后作为它原本父结点的右孩子。
![](http://img.blog.csdn.net/20170805152153839?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvbG9naW5fc29uYXRh/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
因为兄弟节点和兄弟节点的子节点都是黑色的,所以可以将兄弟节点变红,但因为兄弟节点的父结点可红可黑,这个时候需要将父节点A变成新的节点,继续向上调整,直到整颗树的颜色符合RBTree的定义为止。
![](http://img.blog.csdn.net/20170805153112874?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvbG9naW5fc29uYXRh/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
此时操作是对这个红色的结点做旋转上升,然后变换它和它原本父结点的颜色,转换为case-4。
![](http://img.blog.csdn.net/20170805153407504?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvbG9naW5fc29uYXRh/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
如下图A是待删除节点,此时操作是对兄弟节点D做旋转上升,变换D和父结点B的颜色,D的左孩子C现在变为B的右孩子,D的红色右孩子变为黑色。
![](http://img.blog.csdn.net/20170805164800423?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvbG9naW5fc29uYXRh/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
对于兄弟节点是黑色节点的可以分成3种情况来处理,第1种情况是:当兄弟节点的子节点都是黑色节点时,可以直接将兄弟节点变红,这样局部的红黑树颜色是符合定义的。但是整颗树不一定是符合红黑树定义的,需要往上追溯继续调整。
第2种情况是:兄弟节点的子节点为左红右黑。第3种情况是:兄弟节点的右子结点是红的(左子结点随意)。我们可以先将第2情况通过旋转变为第3种情况,这时兄弟节点为黑,兄弟节点的右节点为红,可以借调出两个节点出来做黑节点,这样就可以保证删除了黑节点,整棵树还是符合红黑树的定义的,因为黑色节点的个数没有改变。
红黑树是平衡二叉查找树的一种。为了深入理解红黑树,我们需要从二叉查找树开始讲起。
BST
二叉查找树(Binary Search Tree,简称BST)是一棵二叉树,它的左子节点的值比父节点的值要小,右节点的值要比父节点的值大。它的高度决定了它的查找效率。在理想的情况下,二叉查找树增删查改的时间复杂度为O(logN)(其中N为节点数),最坏的情况下为O(N)。当它的高度为logN+1时,我们就说二叉查找树是平衡的。BST的查找操作
T key = a search key Node root = point to the root of a BST while(true){ if(root==null){ break; } if(root.value.equals(key)){ return root; }else if(key.compareTo(root.value)<0){ root = root.left; }else{ root = root.right; } } return null;
从程序中可以看出,当BST查找的时候,先与当前节点进行比较:
如果相等的话就返回当前节点;
如果小于当前节点则继续查找当前节点的左节点;
如果大于当前节点则继续查找当前节点的右节点。
直到当前节点指针为空或者查找到对应的节点,程序查找结束。
BST的插入操作
Node node = create a new node with specify value Node root = point the root node of a BST Node parent = null; //find the parent node to append the new node while(true){ if(root==null)break; parent = root; if(node.value.compareTo(root.value)<=0){ root = root.left; }else{ root = root.right; } } if(parent!=null){ if(node.value.compareTo(parent.value)<=0){//append to left parent.left = node; }else{//append to right parent.right = node; } }
插入操作先通过循环查找到待插入的节点的父节点,和查找父节点的逻辑一样,都是比大小,小的往左,大的往右。找到父节点后,对比父节点,小的就插入到父节点的左节点,大就插入到父节点的右节点上。
BST的删除操作
在二叉查找树中删除一个给定的结点p有三种情况:结点p无左右孩子,则直接删除该结点,修改父节点相应指针。
结点p只有左孩子(右孩子),则把p的父节点和p的左孩子(右孩子)相连,然后删除p。
左右孩子同时存在,找到p的中序直接前驱s,也就是以p的左孩子为根结点的子树中最右的结点,把s的右孩子和p的右孩子相连,p的父节点和p的左孩子相连,然后删除p。
//从二叉查找树中删除指针p所指向的结点 if(p.right == null) //p的右子树为空 { p = p.left; } else if(p.left == null) //p的左子树为空 { p = p.right; } else //左右子树均不空 { BSTNode s = p.left; //左孩子 while(s.right != null) //寻找结点p的中序前驱结点, { //也就是以s为根结点的子树中最右的结点 s = s.right; } s.right = p.right; //p的右孩子和s的右孩子相连 p = p.left; //p的左孩子和p的父节点相连 }
RBTree
然而BST的问题是:数在插入的时候会导致树倾斜,不同的插入顺序会导致树的高度不一样,而树的高度直接的影响了树的查找效率。理想的高度是logN,最坏的情况是所有的节点都在一条斜线上,这样的树的高度为N。基于BST存在的问题,一种新的树——平衡二叉查找树(Balanced BST)产生了。平衡树在插入和删除的时候,会通过旋转操作将高度保持在logN。其中两款具有代表性的平衡树分别为AVL树和红黑树。AVL树追求全局平衡,导致插入和删除性能差,在实际应用中不如追求局部平衡的红黑树(Red-Black Tree,简称RBTree),比如Linux内核中的完全公平调度器、高精度计时器、ext3文件系统等,各种语言的函数库如Java的TreeMap和TreeSet,C++ STL的map、multimap、multiset等,Java 8中的HashMap也用到了RBTree取代过长的链表。
RBTree的定义
RBTree的定义如下:任何一个节点都有颜色,黑色或者红色;
根节点是黑色的;
父子节点之间不能出现两个连续的红节点;
任何一个节点向下遍历到其子孙的叶子节点,所经过的黑节点个数必须相等;
空节点被认为是黑色的。
数据结构表示如下:
class Node<T>{ public T value; public Node<T> parent; public boolean isRed; public Node<T> left; public Node<T> right; }
RBTree在理论上还是一棵BST树,但是它在对BST的插入和删除操作时会维持树的平衡,即保证树的高度在[logN,logN+1](极端的情况下可以出现RBTree的高度达到2*logN,但实际上很难遇到)。这样RBTree的查找时间复杂度始终保持在O(logN)从而接近于理想的BST。RBTree的删除和插入操作的时间复杂度也是O(logN)。
RBTree的查找操作就是BST的查找操作。
RBTree的插入操作
RBTree的插入与BST的插入方式是一致的,只不过是在插入过后,可能会导致树的不平衡,这时就需要对树进行旋转操作和颜色修复(在这里简称插入修复),使得它符合RBTree的定义。新插入的节点是红色的,插入修复操作如果遇到父节点的颜色为黑则修复操作结束。也就是说,只有在父节点为红色节点的时候是需要插入修复操作的。
插入修复操作分为以下的三种情况:
插入操作-case 1
case-1:新插入节点的叔叔节点为红色。此时操作是将父节点和叔叔节点与祖父节点的颜色互换,这样就符合了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节点永远是黑色的。RBTree的删除操作
删除操作首先需要做的也是BST的删除操作,删除后就需要做删除修复操作,使的树符合红黑树的定义,符合定义的红黑树高度是平衡的。删除修复操作是针对删除黑色节点才有的,当黑色节点被删除后会让整个树不符合RBTree的定义的第四条。需要做的处理是从兄弟节点上借调黑色的节点过来,如果兄弟节点没有黑节点可以借调的话,就只能往上追溯,将每一级的黑节点数减去一个,使得整棵树符合红黑树的定义。
删除修复操作在遇到被删除的节点是红色节点或者到达root节点时,修复操作完毕。
删除修复操作分为四种情况:
删除操作-case 1
case-1:待删除的节点的兄弟节点是红色的节点。由于兄弟节点是红色节点的时候,无法借调黑节点,所以需要将兄弟节点上升到父节点,由于兄弟节点是红色的,根据RBTree的定义,兄弟节点的子节点是黑色的,就可以从它的子节点借调了。
case 1这样转换之后就会变成后面的case 2,case 3,或者case 4中的一种了。上升操作需要对兄弟节点做一个左旋或右旋操作,然后和父结点变换颜色,兄弟节点的左孩子旋转后作为它原本父结点的右孩子。
删除操作-case 2
case-2:待删除的节点的兄弟节点是黑色的节点,且兄弟节点的子节点都是黑色的。因为兄弟节点和兄弟节点的子节点都是黑色的,所以可以将兄弟节点变红,但因为兄弟节点的父结点可红可黑,这个时候需要将父节点A变成新的节点,继续向上调整,直到整颗树的颜色符合RBTree的定义为止。
删除操作-case 3
case-3:待删除节点的兄弟节点是黑色的节点,且兄弟节点的左子节点是红色的,右子节点是黑色的(这是兄弟节点在右边的情况),如果兄弟节点在左边的话,就是兄弟节点的右子节点是红色的,左子节点是黑色的。我理解是和待删除节点离得近的是红的。此时操作是对这个红色的结点做旋转上升,然后变换它和它原本父结点的颜色,转换为case-4。
删除操作-case 4
case-4:待删除节点的兄弟节点是黑色的节点,且兄弟节点的右子节点是红色的(兄弟节点在右边的情况),如果兄弟节点在左边,就是左子节点是红色的。现在是离得远的是红的。如下图A是待删除节点,此时操作是对兄弟节点D做旋转上升,变换D和父结点B的颜色,D的左孩子C现在变为B的右孩子,D的红色右孩子变为黑色。
删除操作的总结
红黑树的删除操作是最复杂的操作,复杂的地方就在于当删除了黑色节点的时候,如何从兄弟节点去借调黑节点,以保证树的颜色符合定义。由于红色的兄弟节点是没法借调出黑节点的,这样只能通过旋转操作让他上升到父节点,而由于它是红节点,所以它的子节点就是黑的,可以借调。对于兄弟节点是黑色节点的可以分成3种情况来处理,第1种情况是:当兄弟节点的子节点都是黑色节点时,可以直接将兄弟节点变红,这样局部的红黑树颜色是符合定义的。但是整颗树不一定是符合红黑树定义的,需要往上追溯继续调整。
第2种情况是:兄弟节点的子节点为左红右黑。第3种情况是:兄弟节点的右子结点是红的(左子结点随意)。我们可以先将第2情况通过旋转变为第3种情况,这时兄弟节点为黑,兄弟节点的右节点为红,可以借调出两个节点出来做黑节点,这样就可以保证删除了黑节点,整棵树还是符合红黑树的定义的,因为黑色节点的个数没有改变。
相关文章推荐
- 红黑树之 原理和算法详细介绍
- 【转】红黑树(一)原理与算法详细介绍
- 红黑树的原理分析和算法设计
- 红黑树(一)之 原理和算法详细介绍
- 红黑树(一)之 原理和算法详细介绍
- 红黑树(一)之 原理和算法详细介绍
- 红黑树(一)之 原理和算法详细介绍
- 红黑树(一)之 原理和算法详细介绍
- 【算法】红黑树的原理分析和算法设计
- 算法原理系列:红黑树
- 红黑树之 原理和算法详细介绍
- 红黑树 原理和算法详细介绍(Java)
- 红黑树的原理分析和算法设计
- 【算法】红黑树的讲解及插入删除算法实现原理
- 红黑树(一)之原理和算法的详细分析【转】
- 二叉搜索树(BST)的删除算法原理解析
- 红黑树(一)之 原理和算法详细介绍
- 【算法】 红黑树(一)之 原理和算法详细介绍
- 红黑树(一)之 原理和算法详细介绍
- 红黑树原理和算法详细介绍