红黑树详解
2015-11-01 02:08
316 查看
一、红黑树的由来
因为数组、链表都不能在数据增加、查询上稳定到lgN的时间复杂度,而平衡二叉搜索树的查询可以稳定到lgN,而红黑树和AVL就在此基础上诞生
二、红黑树的性质
红黑树是指在二叉搜索树的基础上再对每个节点加上一个颜色属性,分为红色和黑色。
1.根节点是黑色
2.红色节点的子节点必须是黑色节点
3.每个节点到它的每个叶子的黑色节点路径相等,每个为NIL(null)的叶子的颜色为黑色
时间复杂度分析:
设树的节点总数为n,整棵树的高度为h,黑色节点树的高度为bh。
由于性质三,所以黑色节点数是平衡二叉树,那么黑色节点树的节点总数应该为2^bh-1;
由于性质二,所以黑色节点数的高度bh应该小于整棵树的高度h,就有
2h/2−1<=2bh−1<=n,即h<2(lgn+1)=O(lgn)
三、查询操作
与二叉搜索树查询操作一样,伪代码如下
四、遍历操作
与二叉搜索遍历操作一样,伪代码如下
五、旋转操作
由红黑树的性质可以看到插入一个新的节点后,无论插入的是红色还是黑色都有可能改变它的性质,就需要用到二叉搜索树的旋转操作来修正。
右旋:x和y互换位置,由于y>x所以x成为y的右子节点,由于y
六、插入
插入操作首先遵循二叉搜索树插入操作,伪代码如下:
单纯遵循二叉树插入后,可能会违背红黑树性质:
如果插入的节点颜色是黑色,那么一定违背每个节点到它的每个叶子的黑色节点路径相等;
如果插入的节点颜色是红色,那么有可能违背红色节点的子节点必须是黑色节点,还可能不违背任何性质。
综合考虑将插入节点的颜色设置成为红色后再进行修复,存在以下情况:
1.插入节点的父节点的颜色是黑色,那么不违背任何性质
2.插入节点的父节点是红色,这时候如果父节点的另外一个子节点是红色,那么违背红色节点的子节点必须是黑色节点;如果你节点的另外一个子节点是黑色,那么违背每个节点到它的每个叶子的黑色节点路径相等。都不予以考虑
3.插入节点的爷爷节点的颜色是红色,那么违背红色节点的子节点必须是黑色节点,不予以考虑
4.插入节点的爷爷节点颜色是黑色,但是爷爷节点除插入节点父亲节点外的另一个节点颜色为黑色,那么违背每个节点到它的每个叶子的黑色节点路径相等,不予以考虑
5.插入节点的爷爷节点颜色是黑色,但是爷爷节点除插入节点父亲节点外的另一个节点为NIL(null)。
1)插入节点的父节点为爷爷节点的左子节点并且插入节点为父节点的左节点:
这时候可以将爷爷节点右旋转,并且把父节点和爷爷节点的颜色互换,就符合红黑树的性质了:
2)当插入节点的父节点为爷爷节点的左子节点并且插入节点为父节点的右节点时,可以通过左旋转插入节点的父节点来转换为上面那种情况
6.当插入节点的爷爷节点颜色是黑色,但是爷爷节点除插入节点父亲节点外的另一个节点为红色:
这时候可以将父节点和叔叔节点都改成黑色,但是多添加了一个黑色节点会违背每个节点到它的每个叶子的黑色节点路径相等,这是时候可以再将爷爷节点改为红色,改红色后可能会违背红色节点的子节点必须是黑色节点,这时候可以用递归对把爷爷节点当成插入节点进行修复
7.当插入节点的爷爷节点颜色是黑色,但是爷爷节点除插入节点父亲节点外的另一个节点为黑色时,如果是在插入节点进行判断,这种情况是违背每个节点到它的每个叶子的黑色节点路径相等的,但是第六种情况可能会递归把其它节点当成插入节点,这时候这种情况就是存在的,处理方式和第五种相同
伪代码如下:
七、删除
相对于插入操作而言,删除操作更为复杂。
1.删除节点没有子节点
a.删除节点颜色为红色,不需要修复
b.删除节点颜色为黑色,按照下面第四种情况中的b进行修复
2.删除节点只有左节点,那么删除节点只能是黑色,右节点颜色肯定是红色,只需要将要删除节点替换为右节点(最后改变颜色为删除节点颜色)即可
3.删除节点只有右节点,那么删除节点只能是黑色,右节点颜色肯定是红色,只需要将要删除节点替换为左节点即可
4.删除节点有两个或以上节点:那么为了不让删除一个节点后直接把一棵树分成了两半,而方便后续修复,考虑找一个下面的节点来替换掉这个节点,这个和二叉搜索树的删除操作思想基本一样。可以找删除节点中右子树的最小值节点来替换,当然,也可以找左子树的最大值节点来替换,这里以找右子树的最小值节点为例,并且要修复的节点为父节点的左节点。
替换操作将右子树最小值节点替换成右节点,然后将删除节点替换成右子树最小值节点(最后改变颜色为删除节点颜色)就完成了。替换后可能存在以下情况:
a.替换节点的颜色是红色,那么不需要进行修复
b.替换节点(修复节点)的颜色是黑色,那么在替换后就少了一个黑色节点,需要通过旋转和改变颜色对替换节点的右节点进行修复
1)当替换节点(修复节点)兄弟节点为黑色并且没有子节点(或者两个子节点颜色都为黑色,原因是由于下面情况的出现会更换修复节点),替换节点的父节点为红色的时候,通过改变叔叔节点颜色为红色,父亲节点颜色为黑色就能够修复成功
2)当替换节点(修复节点)兄弟节点为黑色并且没有子节点(或者两个子节点颜色都为黑色,原因是由于此种情况的出现会更换修复节点),替换节点的父节点为黑色的时候,改变叔叔节点颜色为红色,把需要修复的节点换成替换节点父亲节点递归进行修复
3)当替换节点(修复节点)兄弟节点为黑色并且右节点为黑色或者NIL而左节点为红色的时候,可以通过将兄弟节点和兄弟节点左节点颜色互换后对兄弟节点进行右旋转,而达到下面4)中能够完成修复的情况。
4)当替换节点(修复节点)兄弟节点为黑色并且右节点为红色的时候,可以通过将兄弟节点和父亲节点颜色互换后对父亲节点进行左旋转,从而达到修复结果。
5)当替换节点(修复节点)兄弟节点为红色时,可以通过将父亲节点和兄弟节点颜色互换后对父亲节点进行左旋转来把情况转换为上述的1)情况。
伪代码如下:
如果有写得不好、错误的请指正,不喜勿喷。
因为数组、链表都不能在数据增加、查询上稳定到lgN的时间复杂度,而平衡二叉搜索树的查询可以稳定到lgN,而红黑树和AVL就在此基础上诞生
二、红黑树的性质
红黑树是指在二叉搜索树的基础上再对每个节点加上一个颜色属性,分为红色和黑色。
1.根节点是黑色
2.红色节点的子节点必须是黑色节点
3.每个节点到它的每个叶子的黑色节点路径相等,每个为NIL(null)的叶子的颜色为黑色
时间复杂度分析:
设树的节点总数为n,整棵树的高度为h,黑色节点树的高度为bh。
由于性质三,所以黑色节点数是平衡二叉树,那么黑色节点树的节点总数应该为2^bh-1;
由于性质二,所以黑色节点数的高度bh应该小于整棵树的高度h,就有
2h/2−1<=2bh−1<=n,即h<2(lgn+1)=O(lgn)
三、查询操作
与二叉搜索树查询操作一样,伪代码如下
search(node,value) if(value = node.value) return node else if(value < node.value) return search(node.left,value) else return search(node.right,value)
四、遍历操作
与二叉搜索遍历操作一样,伪代码如下
trasverse(node) output(node.value)//中 if(node.left != null) trasverse(node.left)//前 else trasverse(node.right)//后
五、旋转操作
由红黑树的性质可以看到插入一个新的节点后,无论插入的是红色还是黑色都有可能改变它的性质,就需要用到二叉搜索树的旋转操作来修正。
右旋:x和y互换位置,由于y>x所以x成为y的右子节点,由于y
rightRotate(x) y = x.left //使x父节点的子节点为y if(x == root) root = y else if(x == x.parent.right) x.parent.right = y else x.parent.left = y //使b成为x的左节点 x.left = y.right if(x.left != null) x.left.parent = x //使x成为y的右节点 y.right = x x.parent = y
六、插入
插入操作首先遵循二叉搜索树插入操作,伪代码如下:
insert(x) y = root if(y == null) root = x else while(y != null) z = y if(x.value < y.value) y = y.left else y = y.right if(x.value < z) z.left = x else z.right = x x.p = z
单纯遵循二叉树插入后,可能会违背红黑树性质:
如果插入的节点颜色是黑色,那么一定违背每个节点到它的每个叶子的黑色节点路径相等;
如果插入的节点颜色是红色,那么有可能违背红色节点的子节点必须是黑色节点,还可能不违背任何性质。
综合考虑将插入节点的颜色设置成为红色后再进行修复,存在以下情况:
1.插入节点的父节点的颜色是黑色,那么不违背任何性质
2.插入节点的父节点是红色,这时候如果父节点的另外一个子节点是红色,那么违背红色节点的子节点必须是黑色节点;如果你节点的另外一个子节点是黑色,那么违背每个节点到它的每个叶子的黑色节点路径相等。都不予以考虑
3.插入节点的爷爷节点的颜色是红色,那么违背红色节点的子节点必须是黑色节点,不予以考虑
4.插入节点的爷爷节点颜色是黑色,但是爷爷节点除插入节点父亲节点外的另一个节点颜色为黑色,那么违背每个节点到它的每个叶子的黑色节点路径相等,不予以考虑
5.插入节点的爷爷节点颜色是黑色,但是爷爷节点除插入节点父亲节点外的另一个节点为NIL(null)。
1)插入节点的父节点为爷爷节点的左子节点并且插入节点为父节点的左节点:
这时候可以将爷爷节点右旋转,并且把父节点和爷爷节点的颜色互换,就符合红黑树的性质了:
2)当插入节点的父节点为爷爷节点的左子节点并且插入节点为父节点的右节点时,可以通过左旋转插入节点的父节点来转换为上面那种情况
6.当插入节点的爷爷节点颜色是黑色,但是爷爷节点除插入节点父亲节点外的另一个节点为红色:
这时候可以将父节点和叔叔节点都改成黑色,但是多添加了一个黑色节点会违背每个节点到它的每个叶子的黑色节点路径相等,这是时候可以再将爷爷节点改为红色,改红色后可能会违背红色节点的子节点必须是黑色节点,这时候可以用递归对把爷爷节点当成插入节点进行修复
7.当插入节点的爷爷节点颜色是黑色,但是爷爷节点除插入节点父亲节点外的另一个节点为黑色时,如果是在插入节点进行判断,这种情况是违背每个节点到它的每个叶子的黑色节点路径相等的,但是第六种情况可能会递归把其它节点当成插入节点,这时候这种情况就是存在的,处理方式和第五种相同
伪代码如下:
insertFixup(x) while(x.parent.color == red) if(x == x.parent.left) y = x.parent.right if(y.color = red)//第六种情况 y.color = black x.parent.color = black x.parent.parent.color = red x = x.parent.parent else if(x == x.parent.right)//第五种和第七种情况中的第二点 leftRotate(x.parent) else//第五种和第七种情况中的第一点 x.parent.color = black x.parent.parent.color = red rightRotate(x.parent.parent) else//与上面代码类似 root.color = black
七、删除
相对于插入操作而言,删除操作更为复杂。
1.删除节点没有子节点
a.删除节点颜色为红色,不需要修复
b.删除节点颜色为黑色,按照下面第四种情况中的b进行修复
2.删除节点只有左节点,那么删除节点只能是黑色,右节点颜色肯定是红色,只需要将要删除节点替换为右节点(最后改变颜色为删除节点颜色)即可
3.删除节点只有右节点,那么删除节点只能是黑色,右节点颜色肯定是红色,只需要将要删除节点替换为左节点即可
4.删除节点有两个或以上节点:那么为了不让删除一个节点后直接把一棵树分成了两半,而方便后续修复,考虑找一个下面的节点来替换掉这个节点,这个和二叉搜索树的删除操作思想基本一样。可以找删除节点中右子树的最小值节点来替换,当然,也可以找左子树的最大值节点来替换,这里以找右子树的最小值节点为例,并且要修复的节点为父节点的左节点。
替换操作将右子树最小值节点替换成右节点,然后将删除节点替换成右子树最小值节点(最后改变颜色为删除节点颜色)就完成了。替换后可能存在以下情况:
a.替换节点的颜色是红色,那么不需要进行修复
b.替换节点(修复节点)的颜色是黑色,那么在替换后就少了一个黑色节点,需要通过旋转和改变颜色对替换节点的右节点进行修复
1)当替换节点(修复节点)兄弟节点为黑色并且没有子节点(或者两个子节点颜色都为黑色,原因是由于下面情况的出现会更换修复节点),替换节点的父节点为红色的时候,通过改变叔叔节点颜色为红色,父亲节点颜色为黑色就能够修复成功
2)当替换节点(修复节点)兄弟节点为黑色并且没有子节点(或者两个子节点颜色都为黑色,原因是由于此种情况的出现会更换修复节点),替换节点的父节点为黑色的时候,改变叔叔节点颜色为红色,把需要修复的节点换成替换节点父亲节点递归进行修复
3)当替换节点(修复节点)兄弟节点为黑色并且右节点为黑色或者NIL而左节点为红色的时候,可以通过将兄弟节点和兄弟节点左节点颜色互换后对兄弟节点进行右旋转,而达到下面4)中能够完成修复的情况。
4)当替换节点(修复节点)兄弟节点为黑色并且右节点为红色的时候,可以通过将兄弟节点和父亲节点颜色互换后对父亲节点进行左旋转,从而达到修复结果。
5)当替换节点(修复节点)兄弟节点为红色时,可以通过将父亲节点和兄弟节点颜色互换后对父亲节点进行左旋转来把情况转换为上述的1)情况。
伪代码如下:
transplant(x,y)//使x的父亲成为y的父亲 if(x == root) root = y else if(x == x.parent.left) x.parent.left = y else x.parent.right = y y.parent = x.parent delete(x) color = x.color;//判断是否需要进行修复 f = new node();//记录要修复节点(包含为NIL的需要修复的) if(x.left == null && x.right == null)//第一种情况 transplant(x,f) else if(x.left != null && x.right == null)//第二种情况 f = x.left transplant(x,x.left) else if(x.right != null && x.left == null)//第三种情况 f = x.right transplant(x,x.right) else y = searchMinimumNode(x.right)//找到右子树中最小值节点 color = y.color if(y.right == null) transplant(y,f) else f = y.right transplant(y,y.right) transplant(x,y) y.right = x.right y.right.parent = y y.left = x.left y.left.parent = y y.c = x.c if(color == black) deleteFixup(x) else if(x.parent.left != null)//删除f x.parent.left = null else x.parent.right = null //删除f deleteFixup(x) while(x != root && x.color == black)//第四种情况中的所有修复 if(x == x.parent.left) y = x.parent.right if(y.color == red) //5)种情况 y.color = black y.parent.color = red leftRotate(y.parent) else if((y.left == null && y.right == null) || (y.left.color == black && y.right.color == black)) //1)种情况 y.color = red if(x.left == null && y.right == null)//2)、3)种情况 x.p.right = null y = y.parent else if(y.right.color == black)//4)种情况 y.color = red y.left.color = black rightRotate(y) else //5)种情况 y.color = y.parent.color y.parent.co aab2 lor = black y.right.color = black leftRotate(y) if(x.left == null && y.right == null) x.p.left = null x = root else 参照上面的代码 root.color = black//设置树的根颜色为黑色
如果有写得不好、错误的请指正,不喜勿喷。
相关文章推荐
- 动易2006序列号破解算法公布
- Ruby实现的矩阵连乘算法
- C#插入法排序算法实例分析
- Lua教程(七):数据结构详解
- 解析从源码分析常见的基于Array的数据结构动态扩容机制的详解
- 超大数据量存储常用数据库分表分库算法总结
- C#数据结构与算法揭秘二
- C#冒泡法排序算法实例分析
- C#数据结构揭秘一
- 算法练习之从String.indexOf的模拟实现开始
- C#算法之关于大牛生小牛的问题
- C#实现的算24点游戏算法实例分析
- c语言实现的带通配符匹配算法
- 数据结构之Treap详解
- 浅析STL中的常用算法
- 算法之排列算法与组合算法详解
- C++实现一维向量旋转算法
- Ruby实现的合并排序算法
- C#折半插入排序算法实现方法
- 基于C++实现的各种内部排序算法汇总