算法导论例程——红黑树
2016-02-14 00:57
381 查看
红黑树是比较重要的数据结构,作为一个典型的平衡二叉树(没有一条简单路径是其他路径的二倍)。它与二叉搜索树的区别是它的结点多了一个属性——color,color有两种值,red和black,颜色的选取遵循以下原则:
1)每个节点是红色的,或是黑色的;
2)根节点是黑色的;(松弛红黑树 relaxed red-black tree的根节点可以是红色)
3)每个叶节点(NIL)是黑色的;
4)如果一个节点是红色的,那么他的两个子节点都是黑色的;
5)对每个节点,从该节点到其所有后代节点的简单路径上,均包含相同数目的黑色节点;
可见性质中有很多都是用黑节点来定义的,所以为了描述方便还引进了一个概念——黑高bh(x),指的是从节点x到叶节点的任一简单路径的黑节点的数目。
由此可以推得,以x为根的子树至少包含2^(bh(x)) - 1个内部节点,推广到整棵树,得到有n个节点的红黑树其高度不超过2lg(n+1)。
搜索树的操作insert和delete在红黑树上的最坏运行时间为o(lgn),由于这两种操作对树的结构做出了改变,我们需要一个机制来维护红黑树的性质,由于红黑树是有color属性的,所以简单的trans操作不适用,我们引进新的基本操作——旋转。
红黑树的旋转分为左旋和右旋,以左旋为例:对节点x的左旋,假设x有右孩子y,那么旋转之后x成为y的左孩子,y的左孩子成为x的右孩子,y代替了x的位置。这样做不会改变二叉搜索树的性质。
要提示一点的是,红黑树中不允许出现“没有颜色”的空节点,因此本节代码中所有的NULL都应替换为以下声明的一个NIL哨兵节点,它是一种哑元节点,对速度没有什么影响。
有了左旋和右旋作为基础,我们就可以考虑对红红黑树的插入操作了。首先我们要明确的一点是——红黑树依然是二叉搜索树,因此我们可以仿照二叉搜索树的插入方法来写出 红黑树的插入,这样可以保证我们的插入操作不会使树违反二叉搜索树的性质。
唯一的不同就是我们把新插入的节点着色为红色,之所以着为红色是因为这样只会使它违反红黑树的性质2和性质4,方便我们后期的维护。
接下来呢我们讨论关键字节点的父节点、祖父节点和叔叔节点的颜色对其进行讨论,其中case2和case3是具有关联性的,case2完成旋转之后一定会成为case3。具体的讨论参见书,这里就不画图了。
case1: key的叔叔节点是红色
case2:key的叔叔节点是黑色的且key是一个右孩子
case3:key的叔叔节点是黑色且key是一个左孩子
红黑树放弃了追求完全平衡,追求大致平衡,在与平衡二叉树的时间复杂度相差不大的情况下,保证每次插入最多只需要三次旋转就能达到平衡,实现起来也更为简单。
1)每个节点是红色的,或是黑色的;
2)根节点是黑色的;(松弛红黑树 relaxed red-black tree的根节点可以是红色)
3)每个叶节点(NIL)是黑色的;
4)如果一个节点是红色的,那么他的两个子节点都是黑色的;
5)对每个节点,从该节点到其所有后代节点的简单路径上,均包含相同数目的黑色节点;
#define BLACK 0; #define RED 1; using namespace std; typedef struct red_black_tree { int num; red_black_tree* p; red_black_tree* left; red_black_tree* right; int color; }RBT;
可见性质中有很多都是用黑节点来定义的,所以为了描述方便还引进了一个概念——黑高bh(x),指的是从节点x到叶节点的任一简单路径的黑节点的数目。
由此可以推得,以x为根的子树至少包含2^(bh(x)) - 1个内部节点,推广到整棵树,得到有n个节点的红黑树其高度不超过2lg(n+1)。
搜索树的操作insert和delete在红黑树上的最坏运行时间为o(lgn),由于这两种操作对树的结构做出了改变,我们需要一个机制来维护红黑树的性质,由于红黑树是有color属性的,所以简单的trans操作不适用,我们引进新的基本操作——旋转。
红黑树的旋转分为左旋和右旋,以左旋为例:对节点x的左旋,假设x有右孩子y,那么旋转之后x成为y的左孩子,y的左孩子成为x的右孩子,y代替了x的位置。这样做不会改变二叉搜索树的性质。
要提示一点的是,红黑树中不允许出现“没有颜色”的空节点,因此本节代码中所有的NULL都应替换为以下声明的一个NIL哨兵节点,它是一种哑元节点,对速度没有什么影响。
RBT NIL; { NIL.left = NULL; NIL.right = NULL; NIL.color = BLACK; }
void left_rotate(RBT* root, RBT* key) { RBT* key_r; if (key->right != NULL) key_r = key->right; else return -1; //没有右孩子无法左旋 if(key_r->left != NULL) key_r->left->p = key; key->right = key_r->left; if (key->p == NULL) { key_r->p = NULL; root = key_r; } else if (key == key->p->left) key->p->left = key_r; else key->p->right = key_r; key_r->p = key->p; key_r->left = key; key->p = key_r; }
void rigth_rotate(RBT* root, RBT* key) { RBT* key_l; if (key->left != NULL) key_l = key->left; else return -1; //没有左孩子无法右旋 if (key_l->right != NULL) key_l->right->p = key; key->left = key_l->right; if (key->p == NULL) { key_l->p = NULL; root = key_l; } else if (key == key->p->left) key->p->left = key_l; else key->p->right = key_l; key_l->p = key->p; key_l->right = key; key->p = key_l; }
有了左旋和右旋作为基础,我们就可以考虑对红红黑树的插入操作了。首先我们要明确的一点是——红黑树依然是二叉搜索树,因此我们可以仿照二叉搜索树的插入方法来写出 红黑树的插入,这样可以保证我们的插入操作不会使树违反二叉搜索树的性质。
void RB_insert(RBT* root, RBT* key) { RBT *x, *y; x = root; while (x != NULL) { y = x; if (key->num < x->num) x = x->left; else x = x->right; } key->p = y; if (y == NULL) root = key; else if (key->num < y->num) y->left = key; else y->right = key; key->left = NULL; key->right = NULL; key->color = RED; RB_insert_fix(root, key); }
唯一的不同就是我们把新插入的节点着色为红色,之所以着为红色是因为这样只会使它违反红黑树的性质2和性质4,方便我们后期的维护。
接下来呢我们讨论关键字节点的父节点、祖父节点和叔叔节点的颜色对其进行讨论,其中case2和case3是具有关联性的,case2完成旋转之后一定会成为case3。具体的讨论参见书,这里就不画图了。
case1: key的叔叔节点是红色
case2:key的叔叔节点是黑色的且key是一个右孩子
case3:key的叔叔节点是黑色且key是一个左孩子
void RB_insert_fix(RBT* root, RBT* key) { RBT *y; while (key->p->color == RED) { if (key->p == key->p->p->left) { y = key->p->p->right; //uncle node if (y->color == RED) { key->p->color = BLACK; y->color = BLACK; key->p->p->color = RED; key = key->p->p; //case 1: father node is RED,uncle node is RED } else if (key == key->p->right) { key = key->p; left_rotate(root, key); //case 2: father node is RED,but uncle node is black } key->p->color = BLACK; key->p->p->color = RED; right_rotate(root, key->p->p); //case 3: father node is RED,uncle node is BLACK,key is the left child } else { y = key->p->p->left; if (y->color == RED) { key->p->color = BLACK; y->color = BLACK; key->p->p->color = RED; key = key->p->p; } else if (key == key->p->left) { key = key->p; right_rotate(root, key); } key->p->color = BLACK; key->p->p->color = RED; left_rotate(root, key->p->p); } } root->color = BLACK; }
红黑树放弃了追求完全平衡,追求大致平衡,在与平衡二叉树的时间复杂度相差不大的情况下,保证每次插入最多只需要三次旋转就能达到平衡,实现起来也更为简单。
相关文章推荐
- HUST 1010 The Minimum Length(最小循环节)
- 【ZOJ 1221】Risk
- MOOC Machine Learning 作业交流帖4
- 数组中添加数据后并排序显示出来
- 最短路算法floyd
- js验证输入,自定义错误提示
- [回文自动机]bzoj2342: [Shoi2011]双倍回文
- 集群监控_Ganglia使用入门
- 机器学习实践-Ch02 kNN分类算法
- MOOC Machine Learning 作业交流帖3
- Amazon coding 题解答
- QQ中对话框图片的拉伸问题
- 1038. Recover the Smallest Number (30)
- NodeJS 断言的使用
- html背景图片定位方法
- leetcode 83. Remove Duplicates from Sorted List
- Bestcoder Round 72# div2
- MOOC Machine Learning 作业交流帖2
- HDU5627--Clarke and MST (bfs+位运算)
- 有限状态机