通过对map/multimap/set/multiset的学习,在其文档介绍中发现,这几个容器有个共同点是:其底层都是按照二叉搜索树来实现的,但是二叉搜索树有其自身的缺陷,假如往树中插入的元素有序或者接近有序,二叉搜索树就会退化成单支树,时间复杂度会退化成O(N),因此map、set等关联式容器的底层结构是对二叉树进行了平衡处理,即采用平衡树来实现。
1.1 AVL 树
1.1.1 AVL树的概念
二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查找元素相当于在顺序表中搜索元素,效率低下。因此,两位俄罗斯的数学家G.M.Adelson-Velskii和E.M.Landis在1962年发明了一种解决上述问题的方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度。
一棵AVL树或者是空树,或者是具有以下性质的二叉搜索树:·它的左右子树都是AVL树
·左右子树高度之差(简称平衡因子:|右子树的高度-左子树的高度|)的绝对值不超过1(-1/0/1)
如果一棵二叉搜索树是高度平衡的,它就是AVL树。如果它有n个结点,其高度可保持在O(log2N),搜索时间复杂度O(log2N)
接下来先对AVL各功能的实现进行分部分析,最后会附上完整的实现代码.
1.1.2 AVL树节点的定义
AVL树节点的定义:
template<class K,class V> struct AVLTreeNode { AVLTreeNode<K, V>* _left; AVLTreeNode<K, V>* _right; AVLTreeNode<K, V>* _parent;//定义成三叉链的形式 int _bf;//balance factor平衡因子 pair<K, V> _kv;//用pair同时存K和V两个数据 AVLTreeNode(const pair<K,V>& kv)//节点构造函数 :_left(nullptr) ,_right(nullptr) ,_parent(nullptr) ,_bf(0)//平衡因子初始给0 ,_kv(kv) {} };
1.1.3 AVL树的插入
AVL树就是在二叉搜索树的基础上引入了平衡因子,因此AVL树也可以看成是二叉搜索树。那么AVL树的插入过程可以分为两步:
1. 按照二叉搜索树的方式找到空位置然后插入新节点
2. 插入新节点后需要调整节点的平衡因子3.插入元素后可能导致AVL树左右子树高度不符合条件,需要完成旋转(后面解答)
下面用图来具体解析插入节点到不同位置的情况:
pair<Node*,bool> Insert(const pair<K, V>& kv) { if (_root == nullptr)//根节点为空时先new一个新节点 { _root = new Node(kv); return make_pair(_root, true); } Node* cur = _root; Node* parent = nullptr; //先利用while循环去找cur的空位 while (cur) { if (kv.first > cur->_kv.first) { parent = cur; cur = cur->_right; } else if (kv.first < cur->_kv.first) { parent = cur; cur = cur->_left; } else { return make_pair(cur, false); } } //将cur插入到相应位置 cur = new Node(kv); Node* newnode = cur;//用一个newnode记录一下新节点用以返回 if (kv.first > parent->_kv.first) { parent->_right = cur;//注意三叉链的链接逻辑顺序,等号左右方向不能反,先把cur链接到父节点的右边 cur->_parent = parent;//然后再去把父指针知道父节点 } else { parent->_left = cur; cur->_parent = parent; } //进行旋转调整 //while(cur!=_root) while (parent) { //1.进入循环先对平衡因子进行调整 if (cur == parent->_right) { parent->_bf++; } else { parent->_bf--; } //分三种情况向上走 if (parent->_bf == 0)//平衡因子等于0不需要调整 { //为什么不需调整 //因为等于0的话,说明底层子树高度不平衡,添加进入新元素后平衡了,只要平衡了高度并没发生变化,不会影响上面的父节点 break; } else if (parent->_bf == -1 || parent->_bf == 1) { //平衡因子等于-1,说明插入新节点后子树的高度不平衡了,需要继续往上迭代查看父节点是否还满足平衡节点 cur = parent; parent = parent->_parent; } else if (parent->_bf == 2 || parent->_bf == -2) { if (parent->_bf == -2)//父节点等于-2,说明左边高,触发右旋的情况 { if (cur->_bf == -1)//cur节点等于-1,说明在cur的左边更高,触发右单旋的情况 { RotateR(parent); } else//cur等于-1,说明在cur的右边更高,触发左右双旋 { RotateLR(parent); } } else//父节点等于1,说明右边更高,触发左旋的情况 { if (cur->_bf == 1)//cur节点等于1时,说明在cur的右边更高,触发右单旋的情况 { RotateL(parent); } else//cur等于-1,说明在cur的左边更高,触发右左双旋 { RotateRL(parent); } } //思考:为什么上面在传参数的时候,都是传parent的节点呢?这样的好处是什么呢 break;//调整完成后break退出循环 //这里为什么调整完成过后就可以退出,通过旋转调整平衡因子后,parent节点的平衡因子都为0了,调整过后不需要再向上继续查找了 } else { assert(false); } } return make_pair(newnode,true); }
1.1.4 AVL树的旋转
如果在一棵原本是平衡的AVL树中插入一个新节点,可能造成不平衡,此时必须调整树的结构,使之平衡化。根据节点插入位置的不同,AVL树的旋转分为四种:
1. 新节点插入较高左子树的左侧---左左:右单旋
先来观察一下右单旋的抽象图:(抽象图是代表无数种场景的,我们在具体分析问题的时候乳如果不方便入手,可以采用先画具象图举例来思考)
对高度h分别以不同的数字进行举例,画出具象图:
分别对上面三种情况进行旋转后的分析,并且一定要注意四点。
·右单旋的部分可能是独立树,也有可能是子树,所以最后在链接关系上还需处理.先附上右单旋的代码.
·旋转后的平衡因子的修改为0
·由于我们设计的是三叉链结构,所以每一次处理完左右节点过后还要记得处理parent节点的关系
·h如果等于0的情况,也就是subLR不存在的情况,需要做特殊判断
void RotateR(Node* parent)//右单旋 { Node* subL = parent->_left; Node* subLR = subL->_right; parent->_left = subLR; if (subLR != nullptr)//注意:这里一定要判断不为空的,因为下面可能会出现空指针的解引用 { subLR->_parent = parent; } subL->_right = parent; Node* parentParent = parent->_parent;//一定要在改变链接关系之前把这个指针存下来 parent->_parent = subL; //if (parentParent == nullptr)或者采用这个条件也是可以的 if(parent==_root) { _root = subL; _root->_parent = nullptr; } else { //这里注意:parent还有父母时,链接之前需要注意判断到底是右孩子还是左孩子 if (parentParent->_left == parent) parentParent->_left = subL; else parentParent->_right = subL; subL->_parent = parentParent;//最后还要把父指针关系链接上 } parent->_bf = subL->_bf = 0;//最后右单旋完成后平衡因子都要修改成0 }
2. 新节点插入较高右子树的右侧---右右:左单旋
左单旋的情况和上面右单旋的情况类似,下面直接附上代码:
void RotateL(Node* parent) { Node* subR = parent->_right; Node* subRL = subR->_left; //先把subR的左孩子赋值给parent的右节点 parent->_right = subRL; if (subRL != nullptr)//注意一定要判断是否为空的情况 { subRL->_parent = parent;//然后链接parent指针 } //然后subR的左节点链接上parent subR->_left = parent; Node* parentParent = parent->_parent;//提前记录 parent->_parent = subR; //if (parentParent == nullptr) if (parent == _root) { _root = subR; _root->_parent = nullptr; } else { if (parentParent->_left == parent) parentParent->_left = subR; else parentParent->_right = subR; subR->_parent = parentParent; } parent->_bf = subR->_bf = 0; }
3. 新节点插入较高左子树的右侧---左右双旋:先左单旋再右单旋
将双旋变成单旋后再旋转,即:先对30进行左单旋,然后再对90进行右单旋,旋转完成后再考虑平衡因子的更新
上面是抽象图的模型,下面我们举例几种情况进行分析:
通过举例分析,双旋的平衡因子需要分三种不同情况分析:
void RotateLR(Node* parent) { Node* subL = parent->_left; Node* subLR = subL->_right; int bf = subLR->_bf; RotateL(parent->_left);//先进行左旋,并注意旋转点为父节点的左节点 RotateR(parent);//再进行右旋,此时旋转点为父节点 if (bf == 0) { parent->_bf = 0; subL->_bf = 0; subLR->_bf = 0; } else if (bf == 1) { parent->_bf = 0; subL->_bf = -1; subLR->_bf = 0; } else if (bf == -1) { parent->_bf = 1; subL->_bf = 0; subLR->_bf = 0; }//注意这里处理完成过后sunRL的平衡因子一定都是等于0的 else { assert(false); } }
4. 新节点插入较高右子树的左侧---右左双旋:先右单旋再左单旋
右左双旋跟上面类似,但是平衡因子的情况还需要分三种情况进行具体的分析,下面直接附上代码:
void RotateRL(Node* parent) { Node* subR = parent->_right; Node* subRL = subR->_left; int bf = subRL->_bf;//注意:需要提前存subRL的平衡因子,因为旋转可能引起改变 //subRL的平衡因子是双旋的关键节点 RotateR(parent->_right);//先进行右旋,并注意旋转点为父节点的右节点 RotateL(parent);//再进行左旋,此时旋转点为父节点 if (bf == 0) { parent->_bf = 0; subR->_bf = 0; subRL->_bf = 0; } else if (bf == 1) { parent->_bf = -1; subR->_bf = 0; subRL->_bf = 0; } else if (bf == -1) { parent->_bf = 0; subR->_bf = 1; subRL->_bf = 0; }//注意这里处理完成过后sunRL的平衡因子一定都是等于0的 else { assert(false); } }
总结:
假如以Parent为根的子树不平衡,即Parent的平衡因子为2或者-2,分以下情况考虑
1. Parent的平衡因子为2,说明Parent的右子树高,需要向左边把高度压一压,设Parent的右子树的根为SubR
当SubR的平衡因子为1时,执行左单旋
当SubR的平衡因子为-1时,执行右左双旋
2. Parent的平衡因子为-2,说明Parent的左子树高,需要向右边把高度压一压,设Parent的左子树的根为SubL
当SubL的平衡因子为-1是,执行右单旋
当SubL的平衡因子为1时,执行左右双旋3.旋转完成后,原Parent为根的子树个高度降低,已经平衡,不需要再向上更新直接break退出循环即可.
1.1.5 AVL树的验证
AVL树是在二叉搜索树的基础上加入了平衡性的限制,因此要验证AVL树,可以分两步:
1. 验证其为二叉搜索树
如果中序遍历可得到一个有序的序列,就说明为二叉搜索树
2. 验证其为平衡树
·每个节点子树高度差的绝对值不超过1(注意节点中如果没有平衡因子)
·节点的平衡因子是否计算正确下面附上代码:
void _Inorder(Node* root)//中序遍历打印每个节点 { if (root == nullptr) return; _Inorder(root->_left); cout << root->_kv.first << ":" << root->_kv.second << endl; _Inorder(root->_right); } void Inorder() { _Inorder(_root); cout << endl; } //验证是否为平衡二叉树 //1.左子树高度与右子树高度差必须小于1 int _Height(Node* root)//求树的高度函数 { if (root == nullptr) { return 0; } int leftHeight = _Height(root->_left);//递归去子问题求解 int rightHeight = _Height(root->_right); return rightHeight > leftHeight ? rightHeight + 1 : leftHeight + 1; } bool _IsBalance(Node* root) { if (root == nullptr) { return true; } int leftHeight = _Height(root->_left); int rightHeight = _Height(root->_right); // 2.检查一下每颗树的平衡因子是否正确 if (rightHeight - leftHeight != root->_bf) { cout << "平衡因子异常:" << root->_kv.first << endl; return false; } return abs(rightHeight - leftHeight) < 2 && _IsBalance(root->_left) && _IsBalance(root->_right);//分别递归到各自的左右子树再去检查 } bool IsAVLTree() { return _IsBalance(_root); }
1.1.6 AVL树的删除
因为AVL树也是二叉搜索树,可按照二叉搜索树的方式将节点删除,然后再更新平衡因子,只不过与删除不同的是,删除节点后的平衡因子更新,最差情况下一直要调整到根节点的位置。
下面对删除的大致步骤进行分析:
1.1.7 AVL树的性能
AVL树是一棵绝对平衡的二叉搜索树,其要求每个节点的左右子树高度差的绝对值都不超过1,这样可以保证查询时高效的时间复杂度O(log2N)。但是如果要对AVL树做一些结构修改的操作,性能非常低下。
比如:插入时要维护其绝对平衡,旋转的次数比较多,更差的是在删除时,有可能一直要让旋转持续到根的位置。因此:如果需要一种查询高效且有序的数据结构,而且数据的个数为静态的(即不会改变),可以考虑AVL树,但一个结构经常修改,就不太适合。
1.1.8 AVL树的模拟实现
#pragma once #include<iostream> #include<assert.h> #include<string> using namespace std; template<class K,class V> struct AVLTreeNode { AVLTreeNode<K, V>* _left; AVLTreeNode<K, V>* _right; AVLTreeNode<K, V>* _parent;//定义成三叉链的形式 int _bf;//balance factor平衡因子 pair<K, V> _kv;//用pair同时存K和V两个数据 AVLTreeNode(const pair<K,V>& kv)//节点构造函数 :_left(nullptr) ,_right(nullptr) ,_parent(nullptr) ,_bf(0)//平衡因子初始给0 ,_kv(kv) {} }; template<class K,class V> class AVLTree { typedef AVLTreeNode<K, V> Node; public: AVLTree() :_root(nullptr) {} //拷贝构造和赋值拷贝也需要自己实现 AVLTree(const AVLTree<K,V>& kv) { _root = Copy(kv._root); } AVLTree<K, V>& operator=(AVLTree<K,V> kv) { swap(_root, kv._root); return *this; } ~AVLTree() { Destroy(_root); _root = nullptr; } Node* Copy(Node* root) { if (root == nullptr) return nullptr; Node* newroot = new Node(root->_key);//建立新节点 newroot->_left = Copy(root->_left);//新节点的左右节点再去转换成子问题 newroot->_right = Copy(root->_right); return newroot;//最后返回新节点 } void Destroy(Node* root) { //利用后序遍历去释放节点 if (root == nullptr) { return; } Destroy(root->_left); Destroy(root->_right); delete root; } V& operator[](const K& key)//重载operator[] { //operator[]的原则是: //如果插入成功返回插入都value的引用 //如果插入失败则返回V类型默认缺省值 pair<Node*, bool> ret = Insert(make_pair(key, V()));//V采用传匿名对象的方式 return ret.first->_kv.second; } Node* Find(const pair<K, V>& kv)//查找函数 { Node* cur = _root; while (cur) { if (kv.first > cur->_kv.first) { cur = cur->_right; } else if (kv.first < cur->_kv.first) { cur = cur->_left; } else { return cur; } } return nullptr; } void RotateR(Node* parent)//右单旋 { Node* subL = parent->_left; Node* subLR = subL->_right; parent->_left = subLR; if (subLR != nullptr)//注意:这里一定要判断不为空的,因为下面可能会出现空指针的解引用 { subLR->_parent = parent; } subL->_right = parent; Node* parentParent = parent->_parent;//一定要在改变链接关系之前把这个指针存下来 parent->_parent = subL; //if (parentParent == nullptr)或者采用这个条件也是可以的 if(parent==_root) { _root = subL; _root->_parent = nullptr; } else { //这里注意:parent还有父母时,链接之前需要注意判断到底是右孩子还是左孩子 if (parentParent->_left == parent) parentParent->_left = subL; else parentParent->_right = subL; subL->_parent = parentParent;//最后还要把父指针关系链接上 } parent->_bf = subL->_bf = 0;//最后右单旋完成后平衡因子都要修改成0 } void RotateL(Node* parent) { Node* subR = parent->_right; Node* subRL = subR->_left; //先把subR的左孩子赋值给parent的右节点 parent->_right = subRL; if (subRL != nullptr)//注意一定要判断是否为空的情况 { subRL->_parent = parent;//然后链接parent指针 } //然后subR的左节点链接上parent subR->_left = parent; Node* parentParent = parent->_parent;//提前记录 parent->_parent = subR; //if (parentParent == nullptr) if (parent == _root) { _root = subR; _root->_parent = nullptr; } else { if (parentParent->_left == parent) parentParent->_left = subR; else parentParent->_right = subR; subR->_parent = parentParent; } parent->_bf = subR->_bf = 0; } void RotateRL(Node* parent) { Node* subR = parent->_right; Node* subRL = subR->_left; int bf = subRL->_bf;//注意:需要提前存subRL的平衡因子,因为旋转可能引起改变 //subRL的平衡因子是双旋的关键节点 RotateR(parent->_right);//先进行右旋,并注意旋转点为父节点的右节点 RotateL(parent);//再进行左旋,此时旋转点为父节点 if (bf == 0) { parent->_bf = 0; subR->_bf = 0; subRL->_bf = 0; } else if (bf == 1) { parent->_bf = -1; subR->_bf = 0; subRL->_bf = 0; } else if (bf == -1) { parent->_bf = 0; subR->_bf = 1; subRL->_bf = 0; }//注意这里处理完成过后sunRL的平衡因子一定都是等于0的 else { assert(false); } } void RotateLR(Node* parent) { Node* subL = parent->_left; Node* subLR = subL->_right; int bf = subLR->_bf; RotateL(parent->_left);//先进行左旋,并注意旋转点为父节点的左节点 RotateR(parent);//再进行右旋,此时旋转点为父节点 if (bf == 0) { parent->_bf = 0; subL->_bf = 0; subLR->_bf = 0; } else if (bf == 1) { parent->_bf = 0; subL->_bf = -1; subLR->_bf = 0; } else if (bf == -1) { parent->_bf = 1; subL->_bf = 0; subLR->_bf = 0; }//注意这里处理完成过后sunRL的平衡因子一定都是等于0的 else { assert(false); } } pair<Node*,bool> Insert(const pair<K, V>& kv) { if (_root == nullptr)//根节点为空时先new一个新节点 { _root = new Node(kv); return make_pair(_root, true); } Node* cur = _root; Node* parent = nullptr; //先利用while循环去找cur的空位 while (cur) { if (kv.first > cur->_kv.first) { parent = cur; cur = cur->_right; } else if (kv.first < cur->_kv.first) { parent = cur; cur = cur->_left; } else { return make_pair(cur, false); } } //将cur插入到相应位置 cur = new Node(kv); Node* newnode = cur;//用一个newnode记录一下新节点用以返回 if (kv.first > parent->_kv.first) { parent->_right = cur;//注意三叉链的链接逻辑顺序,等号左右方向不能反,先把cur链接到父节点的右边 cur->_parent = parent;//然后再去把父指针知道父节点 } else { parent->_left = cur; cur->_parent = parent; } //进行旋转调整 //while(cur!=_root) while (parent) { //1.进入循环先对平衡因子进行调整 if (cur == parent->_right) { parent->_bf++; } else { parent->_bf--; } //分三种情况向上走 if (parent->_bf == 0)//平衡因子等于0不需要调整 { //为什么不需调整 //因为等于0的话,说明底层子树高度不平衡,添加进入新元素后平衡了,只要平衡了高度并没发生变化,不会影响上面的父节点 break; } else if (parent->_bf == -1 || parent->_bf == 1) { //平衡因子等于-1,说明插入新节点后子树的高度不平衡了,需要继续往上迭代查看父节点是否还满足平衡节点 cur = parent; parent = parent->_parent; } else if (parent->_bf == 2 || parent->_bf == -2) { if (parent->_bf == -2)//父节点等于-2,说明左边高,触发右旋的情况 { if (cur->_bf == -1)//cur节点等于-1,说明在cur的左边更高,触发右单旋的情况 { RotateR(parent); } else//cur等于-1,说明在cur的右边更高,触发左右双旋 { RotateLR(parent); } } else//父节点等于1,说明右边更高,触发左旋的情况 { if (cur->_bf == 1)//cur节点等于1时,说明在cur的右边更高,触发右单旋的情况 { RotateL(parent); } else//cur等于-1,说明在cur的左边更高,触发右左双旋 { RotateRL(parent); } } //思考:为什么上面在传参数的时候,都是传parent的节点呢?这样的好处是什么呢 break;//调整完成后break退出循环 //这里为什么调整完成过后就可以退出,通过旋转调整平衡因子后,parent节点的平衡因子都为0了,调整过后不需要再向上继续查找了 } else { assert(false); } } return make_pair(newnode,true); } void _Inorder(Node* root)//中序遍历打印每个节点 { if (root == nullptr) return; _Inorder(root->_left); cout << root->_kv.first << ":" << root->_kv.second << endl; _Inorder(root->_right); } void Inorder() { _Inorder(_root); cout << endl; } //验证是否为平衡二叉树 //1.左子树高度与右子树高度差必须小于1 int _Height(Node* root)//求树的高度函数 { if (root == nullptr) { return 0; } int leftHeight = _Height(root->_left);//递归去子问题求解 int rightHeight = _Height(root->_right); return rightHeight > leftHeight ? rightHeight + 1 : leftHeight + 1; } bool _IsBalance(Node* root) { if (root == nullptr) { return true; } int leftHeight = _Height(root->_left); int rightHeight = _Height(root->_right); // 2.检查一下每颗树的平衡因子是否正确 if (rightHeight - leftHeight != root->_bf) { cout << "平衡因子异常:" << root->_kv.first << endl; return false; } return abs(rightHeight - leftHeight) < 2 && _IsBalance(root->_left) && _IsBalance(root->_right);//分别递归到各自的左右子树再去检查 } bool IsAVLTree() { return _IsBalance(_root); } private: Node* _root; };
1.1.9 AVL树的测试用例:
#define _CRT_SECURE_NO_WARNINGS #include"AVLTree.h" void TestAVLTree() { //int a[] = { 1, 3, 5, 7, 6 }; //int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 }; int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };//三个数组测试平衡二叉树是否正确 AVLTree<int, int> t; for (auto e : a) { t.Insert(make_pair(e, e));//依次插入元素 } t.Inorder();//用中序遍历验证二叉树是否正确 cout << t.IsAVLTree() << endl;//检查是否为平衡二叉搜索树 t[3] *= 10; t[4] *= 10; t[5] *= 10; //利用opertor[]如果key值存在则修改这个位置的value值;如果不存在则插入到该树中,则以v类型的默认缺省值赋值给value t.Inorder(); AVLTree<string, string> dict; dict.Insert(make_pair("sort", " ")); dict.Insert(make_pair("left", " ")); dict.Inorder(); dict["left"] = "左边,留下"; dict["string"] = " ";//利用operator[]对值进行修改或者插入新元素 dict.Inorder(); } int main() { TestAVLTree(); return 0; }
1.2 红黑树
1.2.1 红黑树的概念
红黑树,是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出俩倍(最短路径:全部由黑节点构成;最长路径:一黑一红,黑节点数量和红节点数量相等),因而是接近平衡的。
1.2.2 红黑树的性质
首先来熟悉一下红黑树的性质定义:
1. 每个结点不是红色就是黑色
2. 根节点是黑色的
3. 如果一个节点是红色的,则它的两个孩子结点是黑色的(没有连续的红节点)
4. 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点(每条路径上包含相同数量的黑节点)
5. 每个叶子结点都是黑色的(此处的叶子结点指的是空结点,即NIL节点)思考:为什么满足上面的性质,红黑树就能保证:其最长路径中节点个数不会超过最短路径节点个数的两倍?
答:假设黑色节点数量有N个,最短路径为:logN,最长路径为:2*logN,因此最长路径中节点个数不会超过最短路径中节点个数的两倍,所以如果用红黑树来完成查找效率也是非常高的.
1.2.3 红黑树节点的定义
enum Colour
{
RED,
BLACK
};//节点的颜色
template<class K, class V>
struct RBTreeNode
{
RBTreeNode<K, V>* _left;//节点左孩子
RBTreeNode<K, V>* _right;//节点右孩子
RBTreeNode<K, V>* _parent;//节点的父亲
pair<K, V> _kv;//节点中存放的键值对
Colour _col;//节点的颜色
RBTreeNode(const pair<K,V> kv)
:_left(nullptr)
,_right(nullptr)
,_parent(nullptr)
,_kv(kv)
,_col(RED)
{}
};
思考:在节点的定义中,为什么要将节点的默认颜色给成红色的?
答:在上面的性质中第三条和第四条是最重要的两点性质。假设我们默认颜色给黑色,那么必然会破环性质4(每条路径上包含相同数目的黑节点),如果破坏性质4代价会非常大,这是一种全局的破坏,其他每条路径的节点会因该路径增加了一个黑节点而不满足红黑树性质,调整起来非常麻烦;那么如果我们默认颜色给红色呢,必然会破坏性质3(没有连续的红节点),但是破坏这条性质只会影响当前路径,并不会对其他路径造成影响,影响面小。所以综上所述我们不难发现,默认给成红色是更好的。
1.2.4 红黑树结构
STL库中红黑树的实现增加一个头结点,因为根节点必须为黑色,为了与根节点进行区分,将头结点给成黑色,并且让头结点的 Parent 域指向红黑树的根节点,Left域指向红黑树中最小的节点,_Right域指向红黑树中最大的节点,如下:
下面我们不采用STL中带头节点的实现方式,我们采用不带头节点的方式来模拟实现红黑树.
1.2.5 红黑树的插入操作
红黑树是在二叉搜索树的基础上加上其平衡限制条件,因此红黑树的插入可分为两步:
1. 按照二叉搜索树规则插入新节点
2. 检测新节点插入后,红黑树的性质是否造到破坏
因为新节点的默认颜色是红色,因此:如果其父节点的颜色是黑色,没有违反红黑树任何性质,则不需要调整;但当新插入节点的父节点颜色为红色时,就违反了性质三不能有连在一起的红色节点,此时需要对红黑树分情况来讨论:
约定:cur为当前节点,p为父节点,g为祖父节点,u为叔叔节点
在分情况讨论之前记住一句话:关键看叔叔的脸色!
·情况一: cur为红,p为红,g为黑,u存在且为红
·情况二: cur为红,p为红,g为黑,u不存在/u为黑
·情况三: cur为红,p为红,g为黑,u不存在/u为黑
总结一下以上三种情况:
附上插入节点的代码:
pair<Node*, bool> Insert(const pair<K, V> kv) { if (_root == nullptr) { _root = new Node(kv); _root->_col = BLACK;//根节点给黑色 return make_pair(_root, true); } Node* cur = _root; Node* parent = nullptr; while (cur)//循环去找空位 { if (kv.first > cur->_kv.first) { parent = cur; cur = cur->_right; } else if(kv.first < cur->_kv.first) { parent = cur; cur = cur->_left; } else { return make_pair(cur, false); } } Node* newnode = new Node(kv); newnode->_col = RED;//链接上新节点 if (kv.first > parent->_kv.first) { parent->_right = newnode; newnode->_parent = parent; } else { parent->_left = newnode; newnode->_parent = parent; } cur = newnode; //父节点存在且父节点为红色时需要继续处理 while (parent && parent->_col == RED) { Node* grandfather = parent->_parent; //关键看叔叔的脸色 if (parent == grandfather->_left) { Node* uncle = grandfather->_right; if (uncle && uncle->_col == RED) { //具体变色的情况需要画图分析: grandfather->_col = RED; parent->_col = uncle->_col = BLACK; //注意需要继续向上处理,容易忘记 cur = grandfather; parent = cur->_parent; } else { if (cur == parent->_left) { RotateR(grandfather);//右单旋 parent->_col = BLACK; grandfather->_col = RED; } else { RotateL(parent);//先以父节点为旋转点左旋 RotateR(grandfather);//再以g为旋转点右旋 cur->_col = BLACK; grandfather->_col = RED; } break;//旋转+变色过后一定是满足所有性质的直接退出循环 } } else { Node* uncle = grandfather->_left; if (uncle && uncle->_col == RED) { uncle->_col = parent->_col = BLACK; grandfather->_col = RED; cur = grandfather; parent = cur->_parent; } else // 情况2:+ 情况3: { if (cur == parent->_right) { RotateL(grandfather); parent->_col = BLACK; grandfather->_col = RED; } else // cur == parent->_left { RotateR(parent); RotateL(grandfather); cur->_col = BLACK; grandfather->_col = RED; } break; } } } _root->_col = BLACK; return make_pair(newnode, true); }
1.2.6 红黑树的验证
红黑树的检测分为两步:
1. 检测其是否满足二叉搜索树(中序遍历是否为有序序列)
2. 检测其是否满足红黑树的性质bool _CheckBlance(Node* root, int blackNum, int count)//balckNum相当于一个标尺,count就是用来记录每条路径的黑节点数目 { if (root == nullptr)//如果root走到空节点 { if (count != blackNum)//count不等于最左路径的黑色节点数 { cout << "黑色节点的数量不相等" << endl; return false;//返回假 } return true;//否则返回真 } if (root->_col == RED && root->_parent->_col == RED) { cout << "存在连续的红色节点" << endl; return false; } if (root->_col == BLACK) { count++; } return _CheckBlance(root->_left, blackNum, count) && _CheckBlance(root->_right, blackNum, count);//再递归到各子树的子问题 } bool CheckBlance() { if (_root == nullptr) { return true; } if (_root->_col == RED) { cout << "根节点是红色的" << endl; return false; } // 找最左路径做黑色节点数量参考值 int blackNum = 0; Node* left = _root; while (left) { if (left->_col == BLACK) { blackNum++; } left = left->_left; } int count = 0; return _CheckBlance(_root, blackNum, count); }
1.2.7 红黑树与AVL树的比较
红黑树和AVL树都是高效的平衡二叉树,增删改查的时间复杂度都是O( log2N),红黑树不追求绝对平衡,其只需保证最长路径不超过最短路径的2倍,相对而言,降低了插入和旋转的次数,所以在经常进行增删的结构中性能比AVL树更优,而且红黑树实现比较简单,所以实际运用中红黑树更多。
1.2.8红黑树的模拟实现
#pragma once #include<iostream> using namespace std; enum Colour { RED, BLACK };//节点的颜色 template<class K,class V> struct RBTreeNode { RBTreeNode<K,V>* _left;//节点左孩子 RBTreeNode<K,V>* _right;//节点右孩子 RBTreeNode<K,V>* _parent;//节点的父亲 pair<K,V> _kv;//节点中存放的T类型的数据 Colour _col;//节点的颜色 RBTreeNode(const pair<K,V> kv) :_left(nullptr) ,_right(nullptr) ,_parent(nullptr) ,_kv(kv) ,_col(RED) {} }; template<class K,class V> class RBTree { typedef RBTreeNode<K, V> Node; public: RBTree() :_root(nullptr) {} //拷贝构造和赋值拷贝也需要自行实现这里不做赘述 ~RBTree() { Destory(_root); _root = nullptr; } void Destory(Node* root) { if (root == nullptr) { return; } //利用后序遍历释放节点 Destory(root->_left); Destory(root->_right); delete root; } void RotateR(Node* parent)//右单旋 { Node* subL = parent->_left; Node* subLR = subL->_right; parent->_left = subLR; if (subLR != nullptr)//注意:这里一定要判断不为空的,因为下面可能会出现空指针的解引用 { subLR->_parent = parent; } subL->_right = parent; Node* parentParent = parent->_parent;//一定要在改变链接关系之前把这个指针存下来 parent->_parent = subL; //if (parentParent == nullptr)或者采用这个条件也是可以的 if (parent == _root) { _root = subL; _root->_parent = nullptr; } else { //这里注意:parent还有父母时,链接之前需要注意判断到底是右孩子还是左孩子 if (parentParent->_left == parent) parentParent->_left = subL; else parentParent->_right = subL; subL->_parent = parentParent;//最后还要把父指针关系链接上 } } void RotateL(Node* parent) { Node* subR = parent->_right; Node* subRL = subR->_left; //先把subR的左孩子赋值给parent的右节点 parent->_right = subRL; if (subRL != nullptr)//注意一定要判断是否为空的情况 { subRL->_parent = parent;//然后链接parent指针 } //然后subR的左节点链接上parent subR->_left = parent; Node* parentParent = parent->_parent;//提前记录 parent->_parent = subR; //if (parentParent == nullptr) if (parent == _root) { _root = subR; _root->_parent = nullptr; } else { if (parentParent->_left == parent) parentParent->_left = subR; else parentParent->_right = subR; subR->_parent = parentParent; } } pair<Node*, bool> Insert(const pair<K, V> kv) { if (_root == nullptr) { _root = new Node(kv); _root->_col = BLACK;//根节点给黑色 return make_pair(_root, true); } Node* cur = _root; Node* parent = nullptr; while (cur)//循环去找空位 { if (kv.first > cur->_kv.first) { parent = cur; cur = cur->_right; } else if(kv.first < cur->_kv.first) { parent = cur; cur = cur->_left; } else { return make_pair(cur, false); } } Node* newnode = new Node(kv); newnode->_col = RED;//链接上新节点 if (kv.first > parent->_kv.first) { parent->_right = newnode; newnode->_parent = parent; } else { parent->_left = newnode; newnode->_parent = parent; } cur = newnode; //父节点存在且父节点为红色时需要继续处理 while (parent && parent->_col == RED) { Node* grandfather = parent->_parent; //关键看叔叔的脸色 if (parent == grandfather->_left) { Node* uncle = grandfather->_right; if (uncle && uncle->_col == RED) { //具体变色的情况需要画图分析: grandfather->_col = RED; parent->_col = uncle->_col = BLACK; //注意需要继续向上处理,容易忘记 cur = grandfather; parent = cur->_parent; } else { if (cur == parent->_left) { RotateR(grandfather);//右单旋 parent->_col = BLACK; grandfather->_col = RED; } else { RotateL(parent);//先以父节点为旋转点左旋 RotateR(grandfather);//再以g为旋转点右旋 cur->_col = BLACK; grandfather->_col = RED; } break;//旋转+变色过后一定是满足所有性质的直接退出循环 } } else { Node* uncle = grandfather->_left; if (uncle && uncle->_col == RED) { uncle->_col = parent->_col = BLACK; grandfather->_col = RED; cur = grandfather; parent = cur->_parent; } else // 情况2:+ 情况3: { if (cur == parent->_right) { RotateL(grandfather); parent->_col = BLACK; grandfather->_col = RED; } else // cur == parent->_left { RotateR(parent); RotateL(grandfather); cur->_col = BLACK; grandfather->_col = RED; } break; } } } _root->_col = BLACK; return make_pair(newnode, true); } void _InOrder(Node* root)//中序遍历递归打印 { if (root == nullptr) { return; } _InOrder(root->_left); cout << root->_kv.first << ":"<<root->_kv.second<<endl; _InOrder(root->_right); } void InOrder() { _InOrder(_root); cout << endl; } bool _CheckBlance(Node* root, int blackNum, int count)//balckNum相当于一个标尺,count就是用来记录每条路径的黑节点数目 { if (root == nullptr)//如果root走到空节点 { if (count != blackNum)//count不等于最左路径的黑色节点数 { cout << "黑色节点的数量不相等" << endl; return false;//返回假 } return true;//否则返回真 } if (root->_col == RED && root->_parent->_col == RED) { cout << "存在连续的红色节点" << endl; return false; } if (root->_col == BLACK) { count++; } return _CheckBlance(root->_left, blackNum, count) && _CheckBlance(root->_right, blackNum, count);//再递归到各子树的子问题 } bool CheckBlance() { if (_root == nullptr) { return true; } if (_root->_col == RED) { cout << "根节点是红色的" << endl; return false; } // 找最左路径做黑色节点数量参考值 int blackNum = 0; Node* left = _root; while (left) { if (left->_col == BLACK) { blackNum++; } left = left->_left; } int count = 0; return _CheckBlance(_root, blackNum, count); } private: Node* _root; };
1.2.9红黑树模拟实现的测试
#define _CRT_SECURE_NO_WARNINGS #include"RBTree.h" #include<stdlib.h> void TestRBTree() { //int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 }; int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 }; RBTree<int, int> t; for (auto& e: a) { t.Insert(make_pair(e, e)); } t.InOrder(); cout << t.CheckBlance() << endl; } int main() { TestRBTree(); return 0; }
1.3 红黑树模拟实现STL中的map与set
1.3.1 改造红黑树
迭代器的好处是可以方便遍历,是数据结构的底层实现与用户透明。
#pragma once #include<iostream> using namespace std; enum Colour { RED, BLACK };//节点的颜色 template<class T> struct RBTreeNode { RBTreeNode<T>* _left; RBTreeNode<T>* _right; RBTreeNode<T>* _parent; T _data; Colour _col; RBTreeNode(const T& data) :_left(nullptr) , _right(nullptr) , _parent(nullptr) , _data(data) , _col(RED) {} }; template<class T, class Ref, class Ptr> struct __TreeIterator//封装迭代器 { typedef RBTreeNode<T> Node;//迭代器就是节点的指针 typedef __TreeIterator<T, Ref, Ptr> Self; Node* _node;// __TreeIterator(Node* node) :_node(node) {} Ref operator*() { return _node->_data; } Ptr operator->() { return &_node->_data; } bool operator != (const Self& s) const { return _node != s._node; } bool operator == (const Self& s) const { return _node == s._node; } // 迭代器的难点 Self& operator++() { //++注意画图分析 if (_node->_right)//节点的右子树不为空 { // 下一个访问就是右树中,中序的第一个节点 Node* left = _node->_right; while (left->_left)//循环去找最左节点 { left = left->_left; } _node = left; } else { // 找祖先里面不是父亲的右的那个节点 // 因为 cur 右为空,说明cur所在的子树已经访问完了 // cur是parent的右的,说明parent包括其所在子树都访问完了,循环往上去找 Node* cur = _node; Node* parent = cur->_parent; while (parent && cur == parent->_right)//如果父亲不为空或者cur为父亲的右节点 { cur = cur->_parent; parent = parent->_parent; } _node = parent; } return *this; } Self& operator--() { if (_node->_left) { // 下一个需要访问左子树的最右节点 Node* right = _node->_left; while (right->_right) { right = right->_right; } _node = right; } else { Node* cur = _node; Node* parent = cur->_parent; //如果父节点存在且cur一直为父节点的左节点循环继续向上找 while (parent && cur == parent->_left) { cur = parent; parent = parent->_parent; } _node = parent; } return *this; } }; template<class K, class T, class KeyOfT>//多添加一个模板参数用以map和set进行封装 class RBTree { typedef RBTreeNode<T> Node; public: typedef __TreeIterator < T, T&, T* > iterator; typedef __TreeIterator < T, const T&, const T* > const_iterator; iterator begin() { Node* left = _root; while (left && left->_left) { left = left->_left; } return iterator(left); } iterator end() { return iterator(nullptr); } RBTree() :_root(nullptr) {} void Destory(Node* root) { if (root == nullptr) { return; } Destory(root->_left); Destory(root->_right); delete root; } ~RBTree() { Destory(_root); _root = nullptr; } Node* Find(const K& key) { KeyOfT kot; Node* cur = _root; while (cur) { if (kot(cur->_data) > key) { cur = cur->_left; } else if (kot(cur->_data) < key) { cur = cur->_right; } else { return cur; } } return nullptr; } pair<iterator, bool> Insert(const T& data) { if (_root == nullptr) { _root = new Node(data); _root->_col = BLACK; return make_pair(iterator(_root), true); } KeyOfT kot;//仿函数对象,重载operator()即可 Node* parent = nullptr; Node* cur = _root; while (cur) { if (kot(cur->_data) < kot(data)) { parent = cur; cur = cur->_right; } else if (kot(cur->_data) > kot(data)) { parent = cur; cur = cur->_left; } else { return make_pair(iterator(cur), false); } } Node* newnode = new Node(data); newnode->_col = RED; if (kot(parent->_data) < kot(data)) { parent->_right = newnode; newnode->_parent = parent; } else { parent->_left = newnode; newnode->_parent = parent; } cur = newnode; // 如果父亲存在,且颜色为红色就需要处理 while (parent && parent->_col == RED) { Node* grandfather = parent->_parent; // 关键是看叔叔 if (parent == grandfather->_left) { Node* uncle = grandfather->_right; // 情况1:uncle存在且为红 if (uncle && uncle->_col == RED) { parent->_col = uncle->_col = BLACK; grandfather->_col = RED; // 继续往上处理 cur = grandfather; parent = cur->_parent; } else // 情况2+3:uncle不存在 uncle存在且为黑 { // 情况2:单旋 if (cur == parent->_left) { RotateR(grandfather); grandfather->_col = RED; parent->_col = BLACK; } else // 情况3:双旋 { RotateL(parent); RotateR(grandfather); cur->_col = BLACK; grandfather->_col = RED; } break; } } else // parent == grandfather->_right { Node* uncle = grandfather->_left; // 情况1: if (uncle && uncle->_col == RED) { uncle->_col = parent->_col = BLACK; grandfather->_col = RED; cur = grandfather; parent = cur->_parent; } else // 情况2:+ 情况3: { if (cur == parent->_right) { RotateL(grandfather); parent->_col = BLACK; grandfather->_col = RED; } else // cur == parent->_left { RotateR(parent); RotateL(grandfather); cur->_col = BLACK; grandfather->_col = RED; } // 插入结束 break; } } } _root->_col = BLACK; return make_pair(iterator(newnode), true); } void RotateL(Node* parent) { Node* subR = parent->_right; Node* subRL = subR->_left; parent->_right = subRL; if (subRL) { subRL->_parent = parent; } subR->_left = parent; Node* parentParent = parent->_parent; parent->_parent = subR; if (parent == _root) { _root = subR; _root->_parent = nullptr; } else { if (parentParent->_left == parent) { parentParent->_left = subR; } else { parentParent->_right = subR; } subR->_parent = parentParent; } } void RotateR(Node* parent) { Node* subL = parent->_left; Node* subLR = subL->_right; parent->_left = subLR; if (subLR) subLR->_parent = parent; subL->_right = parent; Node* parentParent = parent->_parent; parent->_parent = subL; if (parent == _root) { _root = subL; _root->_parent = nullptr; } else { if (parentParent->_left == parent) parentParent->_left = subL; else parentParent->_right = subL; subL->_parent = parentParent; } } bool _CheckBlance(Node* root, int blackNum, int count) { if (root == nullptr) { if (count != blackNum) { cout << "黑色节点的数量不相等" << endl; return false; } return true; } if (root->_col == RED && root->_parent->_col == RED) { cout << "存在连续的红色节点" << endl; return false; } if (root->_col == BLACK) { count++; } return _CheckBlance(root->_left, blackNum, count) && _CheckBlance(root->_right, blackNum, count); } bool CheckBlance() { if (_root == nullptr) { return true; } if (_root->_col == RED) { cout << "根节点是红色的" << endl; return false; } // 找最左路径做黑色节点数量参考值 int blackNum = 0; Node* left = _root; while (left) { if (left->_col == BLACK) { blackNum++; } left = left->_left; } int count = 0; return _CheckBlance(_root, blackNum, count); } void _InOrder(Node* root) { if (root == nullptr) { return; } _InOrder(root->_left); cout << root->_kv.first << ":"<<root->_kv.second<<endl; _InOrder(root->_right); } void InOrder() { _InOrder(_root); cout << endl; } private: Node* _root; };
1.3.2 map的模拟实现
#pragma once
#include"RBTree.h"
namespace Bernard
{
template<class K, class V>
class map
{
struct MapKeyOfT//定义map的仿函数内部类
{
//map的V类型为pair键值对
const K& operator()(const pair<const K, V>& kv)
{
return kv.first;//返回pair中的第一个值
}
};
public:
typedef typename RBTree<K, pair<const K, V>, MapKeyOfT>::iterator iterator;
iterator begin()
{
return _t.begin();
}
iterator end()
{
return _t.end();
}
pair<iterator, bool> insert(const pair<const K, V>& kv)
{
return _t.Insert(kv);
}
V& operator[](const K& key)
{
pair<iterator, bool> ret = insert(make_pair(key, V()));
return ret.first->second;
}
private:
RBTree<K, pair<const K, V>, MapKeyOfT> _t;
};
}
1.3.3 set的模拟实现
#pragma once
#include"RBTree.h"
namespace Bernard
{
template<class K>
class set
{
//set里面定义内部类重载operator()
//set的V类型为K
struct SetKeyOfT
{
const K& operator()(const K& key)
{
return key;//直接返回key
}
};
public:
typedef typename RBTree<K, K, SetKeyOfT>::iterator iterator;
iterator begin()
{
return _t.begin();
}
iterator end()
{
return _t.end();
}
pair<iterator, bool> insert(const K& k)
{
return _t.Insert(k);
}
private:
RBTree<K, K, SetKeyOfT> _t;
};
}
map和set的测试用例:
#include "RBTree.h" #include "map.h" #include "set.h" int main() { Bernard::map<int, int> m; m.insert(make_pair(1, 1)); m.insert(make_pair(3, 3)); m.insert(make_pair(0, 0)); m.insert(make_pair(9, 9)); Bernard::map<int, int>::iterator it = m.begin(); while (it != m.end()) { cout << (*it).first << ":" << (*it).second << endl; cout << it->first << ":" << it->second << endl; ++it; } Bernard::set<int> s; s.insert(1); s.insert(5); s.insert(2); s.insert(1); s.insert(13); s.insert(0); s.insert(15); s.insert(18); Bernard::set<int>::iterator sit = s.begin(); while (sit != s.end()) { cout << *sit << " "; ++sit; } cout << endl; return 0; }