红黑树的算法实现
来源:互联网 发布:想做淘宝客怎么申请 编辑:程序博客网 时间:2024/06/05 05:26
在搜索树的大家族里,红黑树算是用途最广泛的一个代表了。在我们使用的C++STL库中,set、map都是以它作为底层去实现的。当然在一些其他的地方,比如Java集合中的TreeSet和TreeMap,还有Linux虚拟内存的管理,也是通过红黑树去实现的。所以,今天我们来分析一下红黑树是怎样实现的。
红黑树是一棵二叉搜索树,它在每个节点上增加了一个存储位来表示节点的颜色,可以是Red或Black。通过对任何一条从根到叶子简单 路径上的颜色来约束,红黑树保证最长路径不超过最短路径的两倍,因而近似于平衡。
所以我们就把满足下面性质的二叉搜索树称为红黑树:
1. 每个节点不是红色就是黑色
2. 根节点是黑色的
3. 如果有一个节点是红的,那么它的子节点一定是黑的(不存在连续的红节点)
4. 每条路径下存在相同数目的黑节点
5. 每个叶子(NIL)节点是黑色的(这是的叶子节点指的是为(NIL/NULL)的叶子结点)
1.插入
红黑树的插入情况是比较多的,最开始创建一个红黑树,它的根节点一定是黑,插入的第二个节点为了满足黑色节点数目相同的性质,肯定是红的,以此类推,当节点较多时我们可以将插入情况分为以下几类:
第一种:祖父节点是黑的,父亲和叔叔节点是红的,现在我们插入一个cur红节点(方框节点可存在,可不存在)
分析:这个时候,这棵红黑树已经不满足性质“不存在连续红节点”,所以这个时候需要对树进行调整。这种情况我们可以直接将父亲和叔叔节点的颜色进行改变为黑色,改变后又满足了红黑树的性质。
第二种:祖父节点是黑的,父亲节点是红的,叔叔节点不存在或者是黑色的,此时cur红节点(方框节点可存在,可不存在)
分析:同理,这种情况也不满足黑色节点数量相同这一性质。这个时候简单的改变颜色肯定不能解决问题,在这种情况下往往是因为cur子节点的调整,引起上面节点也需要调整。所以这个时候,我们需要对这棵树进行旋转。
第三种:祖父节点是黑的,父亲节点是红的,叔叔节点不存在或者是黑色的,此时cur红节点是父亲的右孩子(方框节点可存在,可不存在)
分析:现在对树做调整,可能情况会比较复杂,如果我们把cur节点变成父亲的左孩子,那剩下的问题就和第二种情况一样了。所以我们对父亲子树进行右旋即可。
pair<Node*, bool> Insert(const K& key, const V& value) { if (_root == NULL) { _root = new Node(key, value); _root->_color = BLACK; return make_pair(_root, true); } Node* parent = NULL; Node* cur = _root; while (cur) { if (cur->_key < key) { parent = cur; cur = cur->_right; } else if (cur->_key > key) { parent = cur; cur = cur->_left; } else return make_pair(cur,false); } cur = new Node(key, value); if (parent->_key > key) parent->_left = cur; else parent->_right = cur; cur->_parent = parent; while (parent && parent->_color == RED) { Node* grandfather = parent->_parent; //1.判断父亲和叔叔的关系 if (parent == grandfather->_left) //父亲在左,叔叔在右 { Node* uncle = grandfather->_right; //2.判断叔叔的情况 if (uncle != NULL && uncle->_color == RED) //a.叔叔存在且为红 -> 变色 { parent->_color = BLACK; uncle->_color = BLACK; grandfather->_color = RED; cur = grandfather; parent = cur->_parent; } else //b.叔叔不存在或者叔叔为黑 { if (cur == parent->_right) { RotateL(parent); swap(cur, parent); } RotateR(grandfather); parent->_color = BLACK; grandfather->_color = RED; break; } } else //叔叔在左,父亲在右 { Node* uncle = grandfather->_left; if (uncle && uncle->_color == RED) //叔叔存在且为红 ,变色 { parent->_color = BLACK; uncle->_color = BLACK; grandfather->_color = RED; cur = grandfather; parent = cur->_parent; } else //叔叔不存在或为黑 { if (cur == parent->_left) { RotateR(parent); swap(cur, parent); } RotateL(grandfather); parent->_color = BLACK; grandfather->_color = RED; break; } } } _root->_color = BLACK; return make_pair(_root, true); }
总结:关于红黑树的插入,我们是从插入的节点开始,依次向上调整,直到这棵树的节点符合红黑树的性质。
2.红黑树的左右旋
对于左右旋这个概念,其实在学习查找树这一个系列的时候并不陌生,很多时候树的插入问题,我们都会用到这个方法来使它达到一种平衡。
对红黑树也是如此。首先,我们借助一个动画来看看右旋是怎么个旋法。
根据这个动画再去实现,就比较容易了。代码实现如下。
void RotateR(Node* parent) //右旋 { assert(parent); Node* child = parent->_left; Node* grandfather = parent->_parent; Node* Rchild = child->_right; if (Rchild) Rchild->_parent =parent ; parent->_left = Rchild; parent->_parent = child; child->_right = parent; if (grandfather == NULL) { child->_parent = NULL; _root = child; } else { if (grandfather->_left == parent) grandfather->_left = child; else grandfather->_right = child; child->_parent = grandfather; } }
同理,左旋的实现也可以借助动画来理解。
void RotateL(Node* parent) //左旋 { assert(parent); Node* child = parent->_right; Node* Lchild = child->_left; Node* grandfather = parent->_parent; if (Lchild) Lchild->_parent = parent; parent->_right = Lchild; parent->_parent = child; child->_left = parent; if (grandfather == NULL) { _root = child; child->_parent = NULL; } else { if (parent == grandfather->_left) grandfather->_left = child; else grandfather->_right = child; child->_parent = grandfather; } }
总结:在查找树中,合理的使用左右旋会使整个问题化繁为简。
3.如何判断当前的红黑树是红黑树
在我们完成插入算法后,这个时候就有了一个新问题,我们如何判断一棵树是不是没有问题的红黑树?要解决这个问题,就得从红黑树的性质入手了。
1. 如果根节点是红的,肯定不是红黑树;
2. 如果有连续的红节点,肯定不是红黑树;
3. 如果黑色节点的路径长度不一样,肯定不是红黑树。
这三条性质就够我们去判断一个树是不是红黑树了。第一二条性质比好判断,如何实现第三个?这时候我们就需要去任意路径count一次黑色节点的路径长度,然后再用这个长度和每一条路径的长度进行比较,只要有一条路径不一样,那就可以直接得出这不是一颗红黑树了。直到判断到每一条路径的叶子节点后,它都相等,那么此时这个性质就满足了。
bool IsBalance() { if (_root == NULL) return true; if (_root->_color == RED) return false; int BlackNum = 0; //统计一条路径的黑色节点数量 Node* left = _root; while (left) { if (left->_color == BLACK) BlackNum++; left = left->_left; } int count = 0; return _IsBalance(_root,BlackNum,count); }bool _IsBalance(Node* root, const int BlackNum, int count) { if (root == NULL) return true; if (root->_color == RED && root->_parent->_color == RED) { cout << "存在连续红节点" << endl; return false; } if (root->_color == BLACK) ++count; if (root->_left == NULL && root->_right == NULL) { if (count != BlackNum) { cout << "黑节点数目不同" << endl; return false; } else return true; } return _IsBalance(root->_left, BlackNum, count) && _IsBalance(root->_right, BlackNum, count); }
其实,相对比其他查找树,红黑树的查找时间复杂度一直控制在 O(lgn),这就要求它一直处于近似平衡状态。也正是因为这一点,红黑树是一种非常高效的平衡查找树,所以在很多语言的内部都会或多或少使用它来作为底层实现。
- 红黑树的算法实现
- 红黑树插入算法的实现
- 红黑树算法的Java实现
- 【算法】红黑树的讲解及插入删除算法实现原理
- 红黑树----红黑树算法的实现与剖析
- 红黑树算法的实现与剖析
- 根据算法导论实现的红黑树
- 红黑树的节点插入算法实现
- 详解Linux内核红黑树算法的实现
- 算法导论红黑树的C++实现
- 详解Linux内核红黑树算法的实现
- 详解Linux内核红黑树算法的实现
- 详解Linux内核红黑树算法的实现
- 详解Linux内核红黑树算法的实现
- 详解Linux内核红黑树算法的实现
- 详解Linux内核红黑树算法的实现
- 详解Linux内核红黑树算法的实现
- 详解Linux内核红黑树算法的实现
- mysql索引操作
- jsonp跨域的安全问题
- CMS垃圾回收器详解
- html标签注意点
- 以列表ListView显示购物车的商品
- 红黑树的算法实现
- 天线设计(1)
- Mac下如何安装破解WebStorm
- [Leetcode] 104. Maximum Depth of Binary Tree 解题报告
- MySQL 主从复制的几种方式
- Elasticsearch使用总结
- OC数组排序
- FxOS RIL
- mac删除带有多级目录或文件的文件夹命令