1、二叉查找树的不足
二叉查找树的基本操作包含搜索、插入、删除、取最大和最小值等都可以在O(h)(h为树的高度)时间复杂度内实现。因此能在期望时间O(lgn)下实现。可是二叉查找树的平衡性在这些操作中并没有得到维护,其高度可能会变得非常高,当其高度较高时。二叉查找树的性能就未必比链表好了,所以二叉查找树的集合操作是期望时间O(lgn),最坏情况下为O(n)。
因为二叉查找树的高度问题,因而出现了红黑树,红黑树可以保证在最坏情况下的集合操作时间都是O(lgn),红黑树因其性能较好,有非常多的应用。比如在C++STL中的一些性能较好的容器(如:set。map)都是利用红黑树作为底层数据结构来支撑的。
2、红黑树
红黑树也是一种二叉查找树,它拥有二叉查找树的性质。同一时候红黑树还有其他一些特殊性质。这使得红黑树的动态集合基本操作在最坏情况下也为O(lgn)。红黑树通过给节点添加颜色和其他限制条件。使得红黑树中的某一节点到叶节点的任一路径不会比其他路径长两倍。因此能够保持树的平衡性。
红黑树中节点包括五个域:颜色、keyword、左节点、右节点和父节点,并将没有子节点或父节点的指针指向一个 nil 哨兵节点,这个nil节点称着叶节点。也是外节点,其他包括keyword的节点叫着内节点。
之所以要添加这样一个为空的外节点,是为了方便红黑树上的一些边界操作,特别是在删除节点的时候,这个nil节点就非常实用了。
红黑树的节点定义为:
color | key | left | right | parent |
2.1、红黑树的性质
红黑树的五个性质:
1、每一个节点或者是红的,或者是黑的。
2、根节点是黑的。
3、叶节点(nil节点)是黑的。
4、假设一个节点是红的,则其孩子节点都是黑的。
5、对于随意节点。从该节点到其子孙叶节点(nil节点)的全部路径上包括同样数目的黑节点。
例如以下图:
那么为什么红黑树要定义这五个性质呢?其根本原因就是为了让红黑树可以自平衡,保证红黑树的高度为O(lgn),以下分别解释一下这五条性质:
性质1:定义了节点的颜色,是为后面的性质做铺垫的,不用细说。
性质2:一定要定义根节点为黑色。由于后面有性质4。定义根节点为黑色。则根节点的子节点的颜色就能够为红,也能够为黑。没有受到限制。否则假设定义根节点为红,则其子节点就仅仅能为黑。就是实说红黑树的上两层节点颜色就定死了,这样显然让树不够灵活。
性质3:定义外节点nil节点为黑色,这时为红黑树操作边界情况时提供方便,大家在看到后面的删除节点的时候就会发现,假设定义为红色,若删除的节点在树的最底层,边界处理就能回考虑得更麻烦。
性质4:非常重要。它说明了红黑树中假设有红节点。其子孙一定为黑节点。可是有黑节点,不一定要出现红节点,也就保证了整个红黑树中的随意路径上黑节点的个数至少是路径上节点总个数的一半。这为保证红黑树的高度不论什么时刻都为O(lgn)提供了重要根据。
性质5:就是为了让红黑树保持平衡。不出现普通二叉查找树可能出现的一边倒的情况。
因此红黑树的每一个性质都是有特别意义的。
黑高度:从某个节点(不包含该节点)出发到达叶节点(nil节点)的随意一条路径上,黑节点的个数称为该节点的黑高度bh(x)。红黑树的黑高度就定义为根节点的黑高度。
引理:一颗有n个内节点的红黑树的高度至多为2lg(n+1)(能够利用节点的黑高度来证明)。这也就保证了最坏情况下查找操作 O(lgn) 的时间复杂度。
这个引理非常重要。它的得出就是由上面的五条性质支持的。
以下我给出简单的证明:
设根节点为x,其黑高度为bh(x),则这棵红黑树节点最少的情况就是:高度h = bh(x) - 1,由于 bh(x) 包括了nil节点(为黑),所以路径上黑节点的实际数目应该是bh(x) - 1 。在最长路径上没有红节点,全是黑节点,这时的节点总数是2^(h+1) - 1。也就是2^bh(x) - 1,所以红黑树的总结点数 n >= 2^bh(x) - 1,又由于红黑树的随意路径上黑节点个数至少是总结点个数的一半。所以bh(x) >= h/2(h为树的实际高度),所以n >= 2^(h/2) - 1,从而有h <= 2*lg(n+1),所以h = O(lgn)。
当然,这个引理也能够通过数学归纳法来证明。算法导论中就是如此。
红黑树在运行一些搜索。取最大最小值,取前驱后继节点等操作没有改动红黑树,因此跟二叉查找树上的操作同样,可是对于插入和删除操作,红黑树因为被改动了,必须运行一些额外的操作来维护其红黑树的性质。实际上这些额外的操作也不会改变插入和删除操作的时间复杂度,仍然为O(lgn)。
2.2、红黑树的旋转
为了维护红黑树的性质,须要运行一些红黑树特有的操作:旋转,包含左旋和右旋。
左旋:对节点x做左旋,必须保证它的右孩子y 不为nil节点,旋转以x到y之间的支轴进行,让y代替x的位置成为该子树新的根,而x成为y的左孩子,y原来的左孩子就成为x的右孩 子。时间复杂度为O(1)。
右旋:与左旋相反。对节点x做右旋,必须保证它的左孩子y不为nil节点。旋转以x到y的支轴进行。让y代替x的位置成为该子树新的根,而x成为y的右孩子,y原来的右孩子就成为 x的左孩子。时间复杂度为O(1)。
2.3、红黑树的插入和删除
在对红黑树运行插入和删除之后。须要运行额外的维护操作。这些操作就是建立在旋转之上的。
以下分析一下在红黑树中插入和删除节点之后可能出现的问题。及解决思路:在插入或者删除节点之后。维护红黑树的性质的时候。都是仅仅有一种情形能够直接通过变换来保持红黑树的性质。其他情形就必须通过变换转化到这一种能够直接解决的出口情形。
1、插入节点
向红黑树中插入一个新的节点,为了不添加黑节点的个数(从而影响黑高度),我们将新插入的节点涂为红色,这样插入之后它可能会违反性质2和4,在未付性质2和4的同一时候还得注意不能违反性质5。主要考虑这三个性质就可以。
因此维护的主要任务就是通过改动红黑树中节点的结构和颜色让根节点为黑色,或者让插入的红节点的父节点变成黑色(假设不是黑色)。
1)将新插入节点定为红色(由于这样不会添加黑节点。不会违反性质5)。若违反红黑树的其他性质。则运行变换。
2)假设插入节点为根节点,则直接将其变为黑色就可以;若不是根节点。则逐步修正,将违反规定的位置上移直到变成出口情形来直接解决。共分为三种情况:
由于当前插入节点为红,若父节点也为红。则违反性质4
(1)当前节点的叔叔节点为红。则祖父节点必定为黑色。能够将父节点和叔叔节点都涂黑,祖父节点涂红,然后将祖父节点变成当前节点,这样违规位置就上升了,循环算法。
(祖父节点必须涂红,是为了保证黑高度保持不变,否则对上面的节点会产生影响)
(2)当前节点的叔叔节点为黑
(2.1)当前节点为其父节点的左孩子(出口情形)
能够将父节点涂黑。祖父节点涂红,然后沿祖父节点右旋。则当前节点的父节点变成了黑色,性质4满足了,同一时候性质5没有被破坏,维护完毕,算法结束。
(2.2)当前节点为其父节点的右孩子
这样的情形须要通过变换转换成情形2.1。以方便解决。
即:将当前节点的父节点作为新的当前节点,以新的当前节点为支点左旋,从而转换成了情形2.1。
实际上。维护插入节点后红黑树的性质共分为三种情形:主要考虑插入节点的叔叔节点。假设叔叔节点为红。则通过不断上移违规位置直到叔叔节点为黑来解决。或者直接变成了根节点,根节点直接就涂黑了;假设叔叔节点为黑,这时候又分两种情形,假设当前节点是其父节点的左孩子,则能够直接解决,假设是右孩子,则变换成是左孩子的情形在解决。
2、删除节点
从红黑树中删除一个节点之后。假设这个节点是红的,则红黑性质没有被破坏。由于被删除的是红节点。所以不是根节点。性质2没问题。同一时候黑节点没有变,所以性质5没问题。由于被删除节点的父节点是黑,所以不管替补上去的节点是什么颜色都不会破坏性质4。性质1和3没有被影响,所以全部性质都没有破坏。
假设被删除的节点是黑色的,则节点的黑高度变化了,性质5被破坏了。维护的主要任务就是通过改动节点的结构和颜色从而添加该子树一个黑高度。
在删除节点之后。主要考虑替补节点的兄弟节点。把替补节点作为当前节点。假设当前节点为红,则直接涂黑就可以保持性质;假设当前节点为黑色,若被删除的节点为黑色,则当前节点所在子树的黑高度少了1,假设当前节点为根节点,则整个树的黑高度少1,没有影响,否则就要变换调整,同插入修正一样。将黑高度少1的违规位置上升,直到进入出口情形,直接解决。共同拥有4种情形。假设删除的节点没有孩子,则替补节点就是nil外节点。保证了替补节点不为NULL,同一时候为黑色。方便操作。
1)当前节点为黑,兄弟节点为红
将这样的情形转换成兄弟节点为黑的情形来处理,即:将当前节点的父节点涂红,兄弟节点涂黑,。以父节点为支点左旋,则当前节点的兄弟节点就为原来兄弟节点的左黑孩子,变成了兄弟节点为黑的情形。
2)当前节点的兄弟节点为黑
(2.1)兄弟节点的两个孩子全为黑
把兄弟节点涂红,让父节点成为新的当前节点。使使父节点为根的整个子树黑高度少1,违规位置上移。(蓝色代表随意颜色)
(2.2)兄弟节点的左孩子为红。右孩子为黑
将这样的情形转换成兄弟节点的右孩子为红的情形来解决,即:将兄弟节点涂红。并将其左孩子涂黑,然后以兄弟节点为支点右旋。则当前节点的新的兄弟节点为元兄弟节点的左孩子,且想在兄弟节点的右孩子为红了。
(2.3)兄弟节点的右孩子为红,左孩子随意(出口情形)
这时出口情形。能够直接解决,能够先把兄弟节点涂成父节点的颜色,再把父节点和兄弟节点的右孩子涂黑,然后以父节点为支点左旋,此时全部性质都满足了。由于原本是当前节点的黑高度比兄弟节点少1,仅仅与兄弟节点的孩子同样,经过这次变换,当前节点所在的位置变成了原来的黑父节点,黑高度添加了1。所以保持了性质5,而其他节点的黑高度也没有受到影响,依旧没变。
所以维护完毕。算法结束。
/* * 算法导论 第十三章 红黑树 */ #include <iostream> using namespace std; //定义颜色 enum Color { BLACK = 0, RED = 1 }; //定义红黑树节点 typedef struct RBTNode { Color color; int key; RBTNode *left, *right, *parent; RBTNode() { this->color = Color::RED; this->key = 0; this->left = this->right = this->parent = NULL; } RBTNode(Color c, int k, RBTNode* l, RBTNode* r, RBTNode* p) { this->color = c; this->key = k; this->left = l; this->parent = p; this->right = r; } }RBTNode, RBTree; //定义一个哨兵nilNode,以方便处理边界问题 //特别是在删除红黑树元素时非常实用 // 假设删除节点没有孩子,nilNode作为其孩子处理起来就方便多了 RBTNode* nilNode = NULL; /* * 中序遍历 * 递归 */ void inOrderTreeWalkRecursion(RBTree* tree) { if (tree && tree != nilNode) { inOrderTreeWalkRecursion(tree->left); cout<<tree->key<<" "; inOrderTreeWalkRecursion(tree->right); } } /* * 查找二叉排序树中的最小元素 * 即为最左元素 * 时间复杂度为O(lgn) */ RBTNode* rbTreeMinimum(RBTree* tree) { if (! tree || tree == nilNode) return NULL; while (tree->left != nilNode) { tree = tree->left; } return tree; } /* * 求二叉排序树中指定节点node的中序遍历后继 * 假设node右子树不为空,则后继则为node右子树中的最小节点 * 否则node右子树为空。必须向上回溯找到第一个节点:该节点为其父节点的左孩子 * 后继即为其父节点,假设不存在这样的节点,说明node为最右节点。后继为空 * 时间复杂度为O(lgn) */ RBTNode* rbTreeSuccessor(RBTNode* node) { if (! node || node == nilNode) return NULL; if (node->right != nilNode) { return rbTreeMinimum(node->right); } RBTNode* temp = node->parent; while (temp != nilNode && node == temp->right) { node = temp; temp = node->parent; } return temp; } /* * 红黑树节点左旋 */ void leftRotate(RBTree* &tree, RBTNode* node) { //假设node为空或者其右节点为空,就返回 if (! node || node == nilNode || node->right == nilNode) return; //让node右节点的左孩子成为node的右孩子 RBTNode* temp = node->right; node->right = temp->left; if (temp->left != nilNode) { temp->left->parent = node; } //让node的右孩子代替node的位置 temp->parent = node->parent; if (node->parent == nilNode) {//说明node是根节点,改动根节点的指针 tree = temp; } else { if (node == node->parent->left) { node->parent->left = temp; } else { node->parent->right = temp; } } //让node成为其右孩子的左孩子 temp->left = node; node->parent = temp; } /* * 红黑树节点右旋 */ void rightRotate(RBTree* &tree, RBTNode* node) { //假设node为空或者其左节点为空。就返回 if (! node || node == nilNode || node->left == nilNode) return; //让node左节点的右孩子成为node的左孩子 RBTNode* temp = node->left; node->left = temp->right; if (temp->right != nilNode) { temp->right->parent = node; } //让node的右左孩子代替node的位置 temp->parent = node->parent; if (node->parent == nilNode) {//说明node是根节点,改动根节点的指针 tree = temp; } else { if (node == node->parent->left) { node->parent->left = temp; } else { node->parent->right = temp; } } //让node成为其左孩子的右孩子 temp->right = node; node->parent = temp; } /* * 红黑树插入元素后,对树进行调整以保持红黑树的性质 * 主要耗时在将违规的位置上升,由于高度为O(lgn)。所以其时间复杂度依旧为O(lgn) * 而旋转一共不超过2次,每次时间复杂度为O(1) * 所以总的时间复杂度为O(lgn) */ void rbInsertFixup(RBTree* &tree, RBTNode* node) { if (! tree || ! node) return; // 由于新插入的node颜色为红,仅仅可能违反性质2(根节点为黑色)和性质4(红节点的孩子节点仅仅能为黑) // 所以假设node的父节点不是nilNode,则仅仅有在其父节点也为红的时候才违反红黑树的性质。须要调整 // 否则不用调整 while (node->parent->color == Color::RED) { //主要考虑node的叔叔节点 RBTNode* uncleNode = NULL; if(node->parent == node->parent->parent->left) { uncleNode = node->parent->parent->right; if (uncleNode->color == Color::RED) { // 情形1:node的叔叔节点为红,则将违规位置上移 // 由于node的父节点和叔叔节点均为红,则node的祖父节点必定为黑 // 因此能够将node的父节点和叔叔节点均变成黑。而祖父节点变成红 // 这样依旧保持了总体的黑高度没变,保持了性质5(随意节点到其子孙节点的路径上黑节点个数同样) node->parent->color = Color::BLACK; uncleNode->color = Color::BLACK; node->parent->parent->color = Color::RED; node = node->parent->parent; } else { // 情形2:node的叔叔节点为黑 // 情形2.1:node为其父节点的右孩子,将情形2.1转化成2.2以便处理,由于node和其父节点都是红的,所以演父节点旋转没有<span style="white-space:pre"> </span> 影响 if (node == node->parent->right) { node = node->parent; leftRotate(tree, node);//从2.1变成了2.2 } // 情形2.2:node为其父节点的左孩子。这样的情形为出口情形 // 由于父节点为红。叔叔节点为黑,则祖父节点一定为黑,所以将父节点变为黑 // 祖父节点变为红,再沿祖父节点右旋则黑高度没变。同一时候node的父节点变黑了 // 达到主要目的:让node的父节点变成黑色。以保持性质4 // 在这个过程中又不能改变黑高度,由于要保持性质5 node->parent->color = Color::BLACK; node->parent->parent->color = Color::RED; rightRotate(tree, node->parent->parent); } } else { //叔叔节点的位置相反,处理方法同上,仅仅是旋转时候的方向相反 uncleNode = node->parent->parent->left; if (uncleNode->color == Color::RED) { // 情形1:node的叔叔节点为红,则将违规位置上移 node->parent->color = Color::BLACK; uncleNode->color = Color::BLACK; node->parent->parent->color = Color::RED; node = node->parent->parent; } else { // 情形2:node的叔叔节点为黑 // 情形2.1:node为其父节点的左孩子 if (node == node->parent->left) { node = node->parent; rightRotate(tree, node);//从2.1变成了2.2 } // 情形2.2:node为其父节点的右孩子,这样的情形为出口情形 node->parent->color = Color::BLACK; node->parent->parent->color = Color::RED; leftRotate(tree, node->parent->parent); } } } //保持性质2,让根节点为黑 tree->color = Color::BLACK; } /* * 插入元素 * 搜索插入点时间为O(lgn) * 维护红黑树的性质时间为O(lgn) * 所以总的时间复杂度为O(lgn) */ void rbInsert(RBTree* &tree, RBTNode* node) { if (! tree || ! node) return; RBTNode* posNode = nilNode; RBTNode* t = tree; while (t != nilNode) { posNode = t; if (node->key < t->key) { t = t->left; } else { t = t->right; } } node->parent = posNode; if (posNode == nilNode) { tree = node; } else { if (node->key < posNode->key) { posNode->left = node; } else { posNode->right = node; } } //不同于二叉排序树的地方 node->left = node->right = nilNode; node->color = Color::RED;//插入红节点以保证黑高度不变 //维护红黑树性质 rbInsertFixup(tree, node); } /* * 删除红黑树节点后维护红黑树的性质 * 主要耗时仍然是将违规位置上升,时间复杂度为O(lgn) * 旋转最多3次,共O(1) * 所以时间复杂度为O(lgn) */ void rbDeleteFixup(RBTree* &tree, RBTNode* node) { //假设node是根节点,则整个树的黑高度都减一。没有影响 //假设node的颜色是红色,则直接涂黑就能够了 // 否则就要维护了,红黑树节点删除之后。维护性质主要与node的兄弟节点有关 while (node != tree && node->color == Color::BLACK) { //当前节点node为非根黑节点,考虑其兄弟节点 RBTNode* brotherNode = NULL; if (node == node->parent->left) { brotherNode = node->parent->right; //情形1:兄弟节点为红,将其转换成兄弟节点为黑的情形2 //应为node节点为黑,兄弟节点为红,则其父节点必定为黑 // 且兄弟节点的子节点必定为黑,因此能够将父节点变红。兄弟节点变黑 // 然后沿父节点左旋。则黑高度没有被影响。同一时候node的新的兄弟节点变成了原兄弟节点的左黑孩子了 // 因此node的兄弟节点变为黑节点了 if (brotherNode->color == Color::RED) { node->parent->color = Color::RED; brotherNode->color = Color::BLACK; leftRotate(tree, node->parent); brotherNode = node->parent->right; } //情形2:兄弟节点为黑 if (brotherNode->right->color == Color::BLACK && brotherNode->left->color == Color::BLACK) { //情形2.1:兄弟节点的右孩子为黑。且其左孩子为黑 //这时node为黑。其兄弟节点和两个孩子节点也均为黑 //由于此时node子树比其兄弟节点的子树黑高度小1,所以能够把兄弟节点变红,从而二者黑高度同样 // 从而让node的父节点子树整个黑高度减少1(违反规定5)。让node的父节点变成新的node,使违规的位置上升 // 继续循环 brotherNode->color = Color::RED; node = node->parent; } else { if (brotherNode->right->color == Color::BLACK) { //情形2.2:兄弟节点的右孩子为黑,且其左孩子为红,将这样的情形转换成2.3 //由于兄弟节点为黑,其左孩子为红,右孩子为黑,因此能够将兄弟节点的左孩子变黑 //兄弟节点变红,然后沿兄弟节点右旋,从而进入情形2.3,且为改变黑高度 brotherNode->left->color = Color::BLACK; brotherNode->color = Color::RED; rightRotate(tree, brotherNode); brotherNode = node->parent->right; } //情形2.3:兄弟节点的右孩子为红。左孩子随意,这时出口情形,经过下面变换,算法结束 // 由于兄弟节点为黑,但其右孩子为红,能够把兄弟节点变成父节点的颜色,把其右孩子和node父节点变黑 // 然后沿node父节点左旋。则node的兄弟节点的位置仍然是黑色(为brother的右孩子),该子树的黑高度不变 // 而node节点所在子树由于添加了node父节点这个黑节点而黑高度添加1,所以两边的黑高度同样了 // (原来node子树比其兄弟子树黑高度少1)。从而保持了性质5 brotherNode->color = node->parent->color; node->parent->color = Color::BLACK; brotherNode->right->color = Color::BLACK; leftRotate(tree, node->parent); //问题已解决,让node指向根节点,从而退出循环 node = tree; } } else {//兄弟节点的位置相反,原理同上 brotherNode = node->parent->left; //情形1:兄弟节点为红,将其转换成兄弟节点为黑的情形2 if (brotherNode->color == Color::RED) { node->parent->color = Color::RED; brotherNode->color = Color::BLACK; rightRotate(tree, node->parent); brotherNode = node->parent->left; } //情形2:兄弟节点为黑 if (brotherNode->left->color == Color::BLACK && brotherNode->right->color == Color::BLACK) { //情形2.1:兄弟节点的左孩子为黑,且其右孩子为黑 brotherNode->color = Color::RED; node = node->parent; } else { if (brotherNode->left->color == Color::BLACK) { //情形2.2:兄弟节点的左孩子为黑。且其右孩子为红,将这样的情形转换成2.3 brotherNode->right->color = Color::BLACK; brotherNode->color = Color::RED; leftRotate(tree, brotherNode); brotherNode = node->parent->left; } //情形2.3:兄弟节点的左孩子为红,右孩子随意。这时出口情形,经过下面变换,算法结束 brotherNode->color = node->parent->color; node->parent->color = Color::BLACK; brotherNode->left->color = Color::BLACK; rightRotate(tree, node->parent); //问题已解决。让node指向根节点,从而退出循环 node = tree; } } } node->color = Color::BLACK; } /* * 删除红黑树节点 * 主要耗时是在获取删除节点的中序遍历后继节点,时间复杂度为O(lgn) * 而维护红黑树性质的时间复杂度为O(lgn) * 所以总的时间复杂度为O(lgn) */ RBTNode* rbDelete(RBTree* &tree, RBTNode* node) { if (! tree || ! node) return; RBTNode* delNode = NULL; if (node->left == nilNode || node->right == nilNode) { delNode = node; } else { delNode = rbTreeSuccessor(node); } RBTNode* fillNode = NULL; if (delNode->left != nilNode) { fillNode = delNode->left; } else { fillNode = delNode->right; } fillNode->parent = delNode->parent; if (delNode->parent == nilNode) { tree = fillNode; } else { if (fillNode == fillNode->parent->left) { fillNode->parent->left = fillNode; } else { fillNode->parent->right = fillNode; } } if (delNode != node) { node->key = delNode->key; } //假设被删除节点是黑色,则树中某些的黑高度被减一,必须维护性质5 //将该节点相关路径上添加一个黑节点 if (delNode->color == Color::BLACK) { rbDeleteFixup(tree, fillNode); } return delNode; }