STL之map和set

1. 关联式容器

vector、list、deque、 forward_list(C++11)等,这些容器统称为序列式容器,因为其底层为线性序列的数据结构,里面存储的是元素本身。

关联式容器 也是用来存储数据的,与序列式容器不同的是,其里面存储的是结构的键值对在数据检索时比序列式容器效率更高

2. 键值对

用来表示具有一一对应关系的一种结构,该结构中一般只包含两个成员变量key和value,key代表键值,value表示与key对应的信息。

SGI-STL中关于键值对的定义:

cpp 复制代码
template <class T1, class T2>
struct pair
{
	typedef T1 first_type;
	typedef T2 second_type;
	T1 first;
	T2 second;
	pair() : first(T1()), second(T2())
	{}
	pair(const T1& a, const T2& b) : first(a), second(b)
	{}
};

3. 树形结构的关联式容器

根据应用场景的不同,STL总共实现了两种不同结构的管理式容器:树型结构与哈希结构。树型结构的关联式容器主要有四种:map、set、multimap、multiset。这四种容器的共同点是:使用平衡搜索树(即红黑树)作为其底层结果,容器中的元素是一个有序的序列。下面一依次介绍每一 个容器。

3.1 set

3.1.1 set的介绍

set文档介绍

翻译:

  1. set是按照一定次序存储元素的容器
  2. 在set中,元素的value也标识它(value就是key,类型为T),并且每个value必须是唯一的。 set中的元素不能在容器中修改(元素总是const),但是可以从容器中插入或删除它们。
  3. 在内部,set中的元素总是按照其内部比较对象(类型比较)所指示的特定严格弱排序准则进行 排序。
  4. set容器通过key访问单个元素的速度通常比unordered_set容器慢,但它们允许根据顺序对子集进行直接迭代。
  5. set在底层是用二叉搜索树(红黑树)实现的。

注意:

  1. 与map/multimap不同,map/multimap中存储的是真正的键值对,set中只放 value,但在底层实际存放的是由构成的键值对。
  2. set中插入元素时,只需要插入value即可,不需要构造键值对。
  3. set中的元素不可以重复(因此可以使用set进行去重)。
  4. 使用set的迭代器遍历set中的元素,可以得到有序序列
  5. set中的元素默认按照小于来比较
  6. set中查找某个元素,时间复杂度为:log2n
  7. set中的元素不允许修改(为什么?)
  8. set中的底层使用二叉搜索树(红黑树)来实现。

3.1.2 set的使用

  1. set的模板参数列表

    T: set中存放元素的类型,实际在底层存储的键值对。

    Compare:set中元素默认按照小于来比较

    Alloc:set中元素空间的管理方式,使用STL提供的空间配置器管理

  2. set的构造

    函数声明 功能介绍
    set (const Compare& comp = Compare(), const Allocator& = Allocator() ); 构造空的set
    set (InputIterator first, InputIterator last, const Compare& comp = Compare(), const Allocator& = Allocator() ); 用[first, last)区 间中的元素构造 set
    set ( const set& x); set的拷贝构造
  3. set的迭代器

    注意:无论是否是const迭代器,都不支持修改set中存储的数据元素。

  4. set的容量

    函数声明 功能介绍
    bool empty ( ) const 检测set是否为空,空返回true,否则返回true
    size_type size() const 返回set中有效元素的个数
  5. set修改操作

    函数声明 功能介绍
    pair insert ( const value_type& x ) 在set中插入元素x,实际插入的是构成的 键值对,如果插入成功,返回<该元素在set中的 位置,true>,如果插入失败,说明x在set中已经 存在,返回<x在迭代器中的位置,false>
    void erase ( iterator position ) 删除set中position位置上的元素
    size_type erase ( const key_type& x ) 删除set中值为x的元素,返回删除的元素的个数
    void erase ( iterator first, iterator last ) 删除set中==[first, last)==区间中的元素
    void swap ( set<Key,Compare,Allocator>& st ); 交换set中的元素
    void clear ( ) 将set中的元素清空
    iterator find ( const key_type& x ) const 返回set中值为x的元素的位置
    size_type count ( const key_type& x ) const 返回set中值为x的元素的个数

    问:set中的成员函数find和algorithm中的find有什么区别?

    答:set中的成员函数find的时间复杂度是O(log2N)到O(N)之间,但是algorithm中的成员函数的时间复杂度是O(N)。

  6. set的lower_bound和upper_bound

    lower_bound返回一个大于等于val的节点的迭代器。

    upper_bound返回大于val节点的迭代器。

    注意:lower_bound和upper_bound的作用主要体现在可以确定区间。

3.2 map

3.2.1 map的介绍

map的文档简介

翻译:

  1. map是关联容器,它按照特定的次序(按照key来比较)存储由键值key和值value组合而成的元素。
  2. 在map中,键值key通常用于排序和惟一地标识元素,而值value中存储与此键值key关联的 内容。键值key和值value的类型可能不同,并且在map的内部,key与value通过成员类型 value_type绑定在一起,为其取别名称为pair: typedef pair value_type;
  3. 在内部,map中的元素总是按照键值key进行比较排序的。
  4. map中通过键值访问单个元素的速度通常比unordered_map容器慢,但map允许根据顺序对元素进行直接迭代(即对map中的元素进行迭代时,可以得到一个有序的序列)。
  5. map支持下标访问符,即在[]中放入key,就可以找到与key对应的value。
  6. map通常被实现为二叉搜索树(更准确的说:平衡二叉搜索树(红黑树))。

3.2.2 map的使用

  1. map的模板参数说明

    key: 键值对中key的类型

    T: 键值对中value的类型

    Compare: 比较器的类型,map中的元素是按照key来比较的,缺省情况下按照小于来比较,一般情况下(内置类型元素)该参数不需要传递,如果无法比较时(自定义类型),需要用户自己显式传递比较规则(一般情况下按照函数指针或者仿函数来传递)

    Alloc:通过空间配置器来申请底层空间,不需要用户传递,除非用户不想使用标准库提供的空间配置器

    注意:在使用map时,需要包含头文件#include<map>

    了解pair:

  2. map的构造

    函数声明 功能介绍
    map() 构造一个空的map
  3. map的迭代器

    函数声明 功能介绍
    begin()end() begin:首元素的位置,end最后一个元素的下一个位置
    cbegin()cend() 与begin和end意义相同,但cbegin和cend所指向的元素不 能修改
    rbegin()rend() 反向迭代器,rbegin在end位置,rend在begin位置,其 ++和--操作与begin和end操作移动相反
    crbegin()crend() 与rbegin和rend位置相同,操作相同,但crbegin和crend所 指向的元素不能修改

    map的插入、访问和遍历代码举例:

    cpp 复制代码
    void test_map()
    {
    	map<string, string> m;
    	//三种插入方式
    	//方式一:pair的匿名对象
    	m.insert(pair<string, string>("left", "左边"));
    	//方式二:调用pair的构造函数
    	pair<string, string> p("right","右边");
    	m.insert(p);
    	//方式三:使用make_pair函数让它自己推导类型
    	m.insert(make_pair("hello", "你好"));
    
    	map<string, string>::iterator it = m.begin();
    	while (it != m.end())
    	{
    		cout << it->first << ":" << it->second << endl;
    		//或者也可以像下面这样写:
    		//cout << (*it).first << ":" << (*it).second << endl; 
    		it++;
    	}
    	for (auto& e : m)
    	{
    		cout << e.first << ":" << e.second << endl;
    	}
    	cout << endl;
    }

    常用举例(统计水果次数):

    cpp 复制代码
    void test_map2()
    {
    	map<string, int> m;
    	string str[] = {"苹果","香蕉","梨","火龙果","芒果","苹果"};
    	for (auto& e : str)
    	{
    		map<string, int>::iterator it = m.find(e);
    		if (it != m.end())
    		{
    			it->second++;
    		}
    		else
    		{
    			m.insert(make_pair(e, 1));
    		}
    	}
    	for (auto& e : m)
    	{
    		cout << e.first << ":" << e.second << endl;
    	}
    }

    关于上面代码举例的优化:

    insert函数返回值:

    翻译:如果插入成功返回的迭代器的second是true,如果插入失败(元素已经存在)返回的迭代器的second是false。

    cpp 复制代码
    void test_map2()
    {
    	map<string, int> m;
    	string arr[] = {"苹果","香蕉","梨","火龙果","芒果","苹果"};
    	for (auto& e : arr)
    	{
    		pair<map<string, int>::iterator, bool> p = m.insert(make_pair(e, 1));
    
    		if (p.second == false)//插入失败
    		{
    			p.first->second++;//p.first是一个迭代器,即pair的first
    		}
            //插入成功就不需要再管了,因为已经插入过一次了
    	}
    	for (auto& e : m)
    	{
    		cout << e.first << ":" << e.second << endl;
    	}
    }

    关于上面代码的再优化:

    cpp 复制代码
    mapped_type& operator[](const key_type& k)
    {
    	pair<iterator, bool> ret = this->insert(make_pair(k, mapped_type()))//mapped_type是构造的一个默认对象
        
    	//ret.first的类型:iterator
    
    	return (*(ret.first)).second//等价于(ret.first)->second
    }

    分析:

    情况一:插入失败,ret的second是false,此时ret是k所在的迭代器,return返回的是k节点的second即value值。(查找+修改)

    情况二:插入成功,ret的second是true(插入+修改)

    cpp 复制代码
    void test_map2()
    {
    	map<string, int> m;
    	string arr[] = { "苹果","香蕉","梨","火龙果","芒果","苹果" };
    	for (auto& e : arr)
    	{
    		m[e]++;
    	}
    	for (auto& e : m)
    	{
    		cout << e.first << ":" << e.second << endl;
    	}
    }
  4. map的容量与元素访问

    函数声明 功能简介
    bool empty ( ) const 检测map中的元素是否为空,是返回 true,否则返回false
    size_type size() const 返回map中有效元素的个数
    mapped_type& operator[] (const key_type& k) 返回去key对应的value

    问:当key不在map中时,通过operator获取对应value时会发生什么问题?

    注意:在元素访问时,有一个与operator[]类似的操作at()(该函数不常用)函数,都是通过 key找到与key对应的value然后返回其引用,不同的是:当key不存在时,operator[]用默认 value与key构造键值对然后插入,返回该默认value,at()函数直接抛异常

  5. map中元素的修改

    函数声明 功能简介
    pair insert ( const value_type& x ) 在map中插入键值对x,注意x是一个键值 对,返回值也是键值对:iterator代表新插入 元素的位置,bool代表释放插入成功
    void erase ( iterator position ) 删除position位置上的元素
    size_type erase ( const key_type& x ) 删除键值为x的元素
    void erase ( iterator first, iterator last ) 删除[first, last)区间中的元素
    void swap ( map<Key,T,Compare,Allocator>& map ) 交换两个map中的元素
    void clear ( ) 将map中的元素清空
    iterator find ( const key_type& x ) 在map中插入key为x的元素,找到返回该元 素的位置的迭代器,否则返回end
    const_iterator find ( const key_type& x ) const 在map中插入key为x的元素,找到返回该元 素的位置的const迭代器,否则返回cend
    size_type count ( const key_type& x ) const 返回key为x的键值在map中的个数,注意 map中key是唯一的,因此该函数的返回值 要么为0,要么为1,因此也可以用该函数来 检测一个key是否在map中

总结:

  1. map中的的元素是键值对
  2. map中的key是唯一的,并且不能修改
  3. 默认按照小于的方式对key进行比较
  4. map中的元素如果用迭代器去遍历,可以得到一个有序的序列
  5. map的底层为平衡搜索树(红黑树),查找效率比较高O(log2 N)
  6. 支持[]操作符,operator[]中实际进行插入查找。

3.3 multiset

3.3.1 multiset的介绍

multiset文档介绍

翻译\]: 1. multiset是按照特定顺序存储元素的容器,其中元素是可以重复的。 2. 在multiset中,元素的value也会识别它(因为multiset中本身存储的就是组成 的键值对,因此value本身就是key,key就是value,类型为T). multiset元素的值不能在容器 中进行修改(因为元素总是const的),但可以从容器中插入或删除。 3. 在内部,multiset中的元素总是按照其内部比较规则(类型比较)所指示的特定严格弱排序准则进行排序。 4. multiset容器通过key访问单个元素的速度通常比unordered_multiset容器慢,但当使用迭 代器遍历时会得到一个有序序列。 5. multiset底层结构为二叉搜索树(红黑树)。 注意: 1. multiset中再底层中存储的是的键值对 2. mtltiset的插入接口中只需要插入即可 3. 与set的区别是,multiset中的元素可以重复,set是中value是唯一的 4. 使用迭代器对multiset中的元素进行遍历,可以得到有序的序列 5. multiset中的元素不能修改 6. 在multiset中找某个元素,时间复杂度为O(log~2~ N) 7. multiset的作用:可以对元素进行排序

注意:multiset和set的用法上的主要区别在于find和count,find的会返回中序的第一个x节点的迭代器,count会返回存储这个值的节点的数目。

3.4 multimap

3.4.1 multimap的介绍

multimap文档介绍

翻译:

  1. Multimaps是关联式容器,它按照特定的顺序,存储由key和value映射成的键值对,其中多个键值对之间的key是可以重复的。
  2. 在multimap中,通常按照key排序和惟一地标识元素,而映射的value存储与key关联的内 容。key和value的类型可能不同,通过multimap内部的成员类型value_type组合在一起, value_type是组合key和value的键值对: typedef pair value_type;
  3. 在内部,multimap中的元素总是通过其内部比较对象,按照指定的特定严格弱排序标准对 key进行排序的。
  4. multimap通过key访问单个元素的速度通常比unordered_multimap容器慢,但是使用迭代 器直接遍历multimap中的元素可以得到关于key有序的序列。
  5. multimap在底层用二叉搜索树(红黑树)来实现。

注意:multimap和map的唯一不同就是:map中的key是唯一的,而multimap中key是可以重复的。

3.4.2 multimap的使用

multimap中的接口可以参考map,功能都是类似的。

注意:

  1. multimap中的key是可以重复的。
  2. multimap中的元素默认将key按照小于来比较
  3. multimap中没有重载operator[]操作(因为key和value面临一对多的关系)。
  4. 使用时与map包含的头文件相同
  5. multiset返回的是一个迭代器,而没有pair了。

3.5 在OJ中的使用

  1. 前K个高频单词

    方法一:不适用stable_sort,在仿函数处继续加条件,强制控制条件。

    代码:

    cpp 复制代码
    class Solution {
    public:
        typedef  map<string, int>::iterator CountIter;
        struct IterCompare
        {
            bool operator()(CountIter it1, CountIter it2)
            {
                //因为快排并不稳定,所以要多进行一层排序判定或者使用稳定的排序函数
                if(it1->second > it2->second || 
                (it1->second == it2->second && it1->first < it2->first ))
                {
                    return true;
                }
                else
                {
                    return false;
                }
            }
        };
        vector<string> topKFrequent(vector<string>& words, int k) {
            map<string, int> countMap;
            for(auto str : words)
            {
                countMap[str]++;
            }
            //排序
            vector<CountIter> v;
            CountIter it = countMap.begin();
            while(it != countMap.end())
            {
                v.push_back(it);
                it++;
            }
            sort(v.begin(), v.end(), IterCompare());
            //取前K个
            vector<string> ret;
            for(int i = 0; i < k; i++)
            {
                ret.push_back(v[i]->first);
            }
            return ret;
        }
    };

    此处要了解一下sort和stable_sort:

    sort排序是不稳定的,即对于相等的key值并不能保证之前的相对顺序,但是stable_sort是稳定的,对于相等的key值仍然能够保持之前的相对顺序。

    方法二:使用stable_sort(其实等同于方法一)

    代码:

    cpp 复制代码
    class Solution {
    public:
        typedef  map<string, int>::iterator CountIter;
        struct IterCompare
        {
            bool operator()(CountIter it1, CountIter it2)
            {
                return it1->second > it2->second;
            }
            
        };
        vector<string> topKFrequent(vector<string>& words, int k) {
            map<string, int> countMap;
            for(auto e : words)
            {
                countMap[e]++;
            }
            vector<CountIter> v;
            CountIter it = countMap.begin();
            while(it != countMap.end())
            {
                v.push_back(it);
                it++;
            }
            stable_sort(v.begin(), v.end(), IterCompare());
            vector<string> ret;
            for(int i = 0; i < k; i++)
            {
                ret.push_back(v[i]->first);
            }
            return ret;
        }
    };

    方法三:

    cpp 复制代码
    class Solution {
    public:
        vector<string> topKFrequent(vector<string>& words, int k) {
            //先按照ASCII码进行排序,即以string为键
            map<string, int> countMap;
            for(auto e : words)
            {
                countMap[e]++;
            }
            //再次按照int进行排序(降序),注意此处用multimap
            multimap<int, string, greater<int>> sortMap;
            for(auto kv : countMap)
            {
                sortMap.insert(make_pair(kv.second, kv.first));
            }
            //取出前面排序的结果放入到vector容器中
            vector<string> ret;
            multimap<int, string>::iterator it = sortMap.begin();
            for(int i = 0; i < k; i++)
            {
                ret.push_back(it->second);
                it++;
            }
            return ret;
        }
    };

    注意:此处不能用反向迭代器,比如2 : a、2:b,2:c,用反向迭代器取出的结果就是c、b、a,这和我们想要的规则不符。

  2. 两个数组的交集I

    代码:

    方法一:

    cpp 复制代码
    class Solution {
    public:
        vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
            set<int> s1(nums1.begin(), nums1.end());
            set<int> s2;
            //找交集的同时去重
            for(auto e : nums2)
            {
                if(s1.count(e))
                    s2.insert(e);
            }
            vector<int> v(s2.begin(), s2.end());
            return v;
        }
    };

    方法二:

    cpp 复制代码
    class Solution {
    public:
        vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
            set<int> s1(nums1.begin(), nums1.end());
            set<int> s2(nums2.begin(), nums2.end());
            auto it1 = s1.begin();
            auto it2 = s2.begin();
            vector<int> v;
            while(it1 != s1.end() && it2 != s2.end())
            {
                if(*it1 < *it2)
                {
                    it1++;
                }
                else if(*it1 > *it2)
                {
                    it2++;
                }
                else 
                {
                    v.push_back(*it1);
                    it1++;
                    it2++;
                }
            }
            return v;
        }
    };

    问:给定两个集合,该如何找并集、交集、差集?

    答:

    并集:将两个集合的元素都放入到一个set中即可。

    交集、差集:

4. 底层结构

前面对map/multimap/set/multiset进行了简单的介绍,在其文档介绍中发现,这几个容器有个 共同点是:其底层都是按照二叉搜索树来实现的,但是二叉搜索树有其自身的缺陷,假如往树中 插入的元素有序或者接近有序,二叉搜索树就会退化成单支树,时间复杂度会退化成O(N),因此 map、set等关联式容器的底层结构是对二叉树进行了平衡处理,即采用平衡树来实现。

4.1 AVL 树

4.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树:

完全二叉树:最后一层缺一些节点。

AVL树:最后两层缺一些节点。

4.1.2 AVL树节点的定义

cpp 复制代码
template<class K, class V>
struct AVLTreeNode
{

	pair<K, V> _kv;
	AVLTreeNode<K, V>* _left;
	AVLTreeNode<K, V>* _right;
	AVLTreeNode<K, V>* _parent;


	//右子树 - 左子树的高度差
	int _bf;
	//AVL树并没有规定必须要设计平衡因子
	//只是一个实现的选择,方便控制平衡
	//构造函数
	AVLTreeNode(const pair<K, V>& kv)
		:_kv(kv)
		, _left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _bf(0)
	{}
};

template<class K, class V>
struct AVLTree
{
	typedef AVLTreeNode<K, V> Node;
public:
	AVLTree()
		:_root(nullptr)
	{}
private:
	Node* _root;
};

4.1.3 AVL树的插入

AVL树就是在二叉搜索树的基础上引入了平衡因子,因此AVL树也可以看成是二叉搜索树。那么 AVL树的插入过程可以分为两步:

  1. 按照二叉搜索树的方式插入新节点
  2. 调整节点的平衡因子

代码:

cpp 复制代码
bool Insert(const pair<K, V>& kv)
{
	//1.搜索树的规则插入
	//2.看是否违反平衡规则,如果违反就需要处理:旋转

	//1.插入
	if (_root == nullptr)
	{
		_root = new Node(kv);
		_root->_bf = 0;
		return true;
	}
	Node* parent = nullptr;
	Node* cur = _root;
	while (cur)
	{
		if (cur->_kv.first > kv.first)
		{
			parent = cur;
			cur = cur->_left;
		}
		else if (cur->_kv.first < kv.first)
		{
			parent = cur;
			cur = cur->_right;
		}
		else
		{
			return false;
		}
	}
	cur = new Node(kv);
	if (kv.first < parent->_kv.first)
	{
		parent->_left = cur;
	}
	else
	{
		parent->_right = cur;
	}
	cur->_parent = parent;

	//更新平衡因子
	while (parent)
	{
		if (cur == parent->_right)//插入的节点是父节点的右子树
		{
			parent->_bf++;
		}
		else
		{
			parent->_bf--;
		}

		if (parent->_bf == -1 || parent->_bf == 1)
		{
			//parent->_bf = 0  -------->parent->_bf == -1/1
			//插入节点导致一边变高了
			//高度变了,继续更新
			cur = cur->_parent;
			parent = parent->_parent;
		}
		else if (parent->_bf == -2 || parent->_bf == 2)
		{
			//parent->_bf ==  1/-1 ------->parent->_bf == 2/-2
			//插入节点导致本来高的一边变的更高了
			//子树不平衡,需要旋转处理
			if (parent->_bf == 2 && cur->_bf == 1)//左单旋
			{
				RotateL(parent);
			}
			else if (parent->_bf == -2 && cur->_bf == -1)//右单旋
			{
				RotateR(parent);
			}
			else if (parent->_bf == -2 && cur->_bf == 1)//左右双旋
			{
				RotateLR(parent);
			}
			else if (parent->_bf == 2 && cur->_bf == -1)//右左双旋
			{
				RotateRL(parent);
			}
			else
			{
				assert(false);
			}
			break;
		}
		else if (parent->_bf == 0)//parent->_bf == 1/-1  ------->parent->_bf == 0
		{
			//插入节点填上了矮的那边
			//高度不变,停止更新
			break;
		}
		else
		{
			//插入之前AVL树就存在|平衡因子|>2的节点
			assert(false);
		}
	}

	return true;
}

4.1.4 AVL树的旋转

旋转原则:

  1. 保持搜索树的规则
  2. 子树变平衡

如果在一棵原本是平衡的AVL树中插入一个新节点,可能造成不平衡,此时必须调整树的结构, 使之平衡化。根据节点插入位置的不同,AVL树的旋转分为四种:

  1. 新节点插入较高右子树的右侧---右右:左单旋

左单旋:

代码:

cpp 复制代码
void RotateL(Node* parent)
{
	Node* subR = parent->_right;
	Node* subRL = subR->_left;
	Node* ppNode = parent->_parent;//parent节点的父节点
	parent->_right = subRL;
	//防止subRL为空节点
	if (subRL)
	{
		subRL->_parent = parent;
	}
	subR->_left = parent;
	parent->_parent = subR;
	//两种情况
	if (parent == _root)//1.parent为根节点时
	{
		_root = subR;
		_root->_parent = nullptr;
	}
	else//2.parent是局部子树
	{
		if (parent == ppNode->_left)
		{
			ppNode->_left = subR;
		}
		else
		{
			ppNode->_right = subR;
		}
		subR->_parent = ppNode;
	}
}
  1. 新节点插入较高左子树的左侧---左左:右单旋

右单旋:

代码:

cpp 复制代码
void RotateR(Node* parent)
{
	Node* subL = parent->_left;
	Node* subLR = subL->_right;
	Node* ppNode = parent->_parent;

	subL->_right = parent;
	parent->_parent = subL;

	parent->_left = subLR;
	if (subLR)
	{
		subLR->_parent = parent;
	}
	if (ppNode)//parent只是原来的子树
	{
		if (parent == ppNode->_left)
		{
			ppNode->_left = subL;
		}
		else
		{
			ppNode->_right = subL;
		}
		subL->_parent = ppNode;
	}
	else//parent是原来的根节点
	{
		_root = subL;
		_root->_parent = nullptr;
	}
	subL->_bf = 0;
	parent->_bf = 0;
}
  1. 新节点插入较高左子树的右侧---左右:先左单旋再右单旋(左右单旋)

总结:60最终作了根节点,60的左子树变成了30的右子树,60的右子树变成了90的左子树。

注意下面的这种情况(h = 0):

代码:

cpp 复制代码
void RotateLR(Node* parent)//左右双旋
{
	Node* subL = parent->_left;
	Node* subLR = subL->_right;
	int bf = subLR->_bf;
	//根据60的平衡因子来区分三种情况
	RotateL(parent->_left);
	RotateR(parent);

	//更新平衡因子
	if (bf == 0)
	{
		subL->_bf = parent->_bf = subLR->_bf = 0;
	}
	else if (bf == 1)
	{
		parent->_bf = subLR->_bf = 0;
		subL->_bf = -1;
	}
	else if (bf == -1)
	{
		parent->_bf = 1;
		subLR->_bf = subL->_bf = 0;
	}
	else
	{
		//subLR旋转前就出现了问题
		assert(false);
	}
}
  1. 新节点插入较高右子树的左侧---右左:先右单旋再左单旋(右左单旋)

总结:60最终成了根节点,60的左子树成为了30的右子树,60的右子树成为了90的左子树。

注意下面的这种情况(n=0):

代码:

cpp 复制代码
RotateRL(Node* parent)//右左双旋
{
	Node* subR = parent->_right;
	Node* subRL = subR->_left;
	int bf = subRL->_bf;
	RotateR(subR);
	RotateL(parent);

	if (bf == 0)
	{
		parent->_bf = subR->_bf = subRL->_bf = 0;
	}
	else if (bf == -1)
	{
		subRL->_bf = parent->_bf = 0;
		subR->_bf = 1;
	}
	else if (bf == 1)
	{
		subRL->_bf = subR->_bf = 0;
		parent->_bf = -1;
	}
	else
	{
		assert(false);
	}
}

4.1.5 AVL树的验证

AVL树是在二叉搜索树的基础上加入了平衡性的限制,因此要验证AVL树,可以分两步:

  1. 验证其为二叉搜索树

    如果中序遍历可得到一个有序的序列,就说明为二叉搜索树

    代码:

    cpp 复制代码
    void Inorder()
    {
    	_InOrder(_root);
    }
    void _InOrder(Node* root)
    {
    	if (root == NULL)
    	{
    		return;
    	}
    	_InOrder(root->_left);
    	cout << root->_kv.first << " ";
    	_InOrder(root->_right);
    }
  2. 验证其为平衡树

    • 每个节点子树高度差的绝对值不超过1(注意节点中如果没有平衡因子)
    • 节点的平衡因子是否计算正确

    代码:

    cpp 复制代码
    bool IsBalanceTree()
    {
    	return _IsBalanceTree(_root);
    }
    bool _IsBalanceTree(Node* root)
    {
    	//空树也是AVL树
    	if (root == nullptr)
    		return true;
    	//计算出root节点的平衡因子,即左右子树的高度差
    	int leftHeight = _Height(root->_left);
    	int rightHeight = _Height(root->_right);
    	int diff = rightHeight - leftHeight;
    	//两种情况:1.
    	if (abs(diff) >= 2)
    	{
    		cout << root->_kv.first << "节点的平衡因子异常" << endl;
    		return false;
    	}
    	if (diff != root->_bf)
    	{
    		cout << root->_kv.first << "节点的平衡因子不符合实际" << endl;
    		return false;
    	}
    	return _IsBalanceTree(root->_left) && _IsBalanceTree(root->_right);
    }

4.1.6 AVL树的删除(了解)

因为AVL树也是二叉搜索树,可按照二叉搜索树的方式将节点删除,然后再更新平衡因子,只不 错与删除不同的时,删除节点后的平衡因子更新,最差情况下一直要调整到根节点的位置。

具体实现可参考《算法导论》或《数据结构-用面向对象方法与C++描述》殷人昆版。

4.1.7 AVL树的性能

AVL树是一棵绝对平衡的二叉搜索树,其要求每个节点的左右子树高度差的绝对值都不超过1,这样可以保证查询时高效的时间复杂度,即log2N。但是如果要对AVL树做一些结构修改的操作,性能非常低下,比如:插入时要维护其绝对平衡,旋转的次数比较多,更差的是在删除时, 有可能一直要让旋转持续到根的位置。因此:如果需要一种查询高效且有序的数据结构,而且数 据的个数为静态的(即不会改变),可以考虑AVL树,但一个结构经常修改,就不太适合。

4.1.8 AVL树的模拟实现

cpp 复制代码
using namespace std;
template<class K, class V>
struct AVLTreeNode
{

	pair<K, V> _kv;
	AVLTreeNode<K, V>* _left;
	AVLTreeNode<K, V>* _right;
	AVLTreeNode<K, V>* _parent;


	//右子树 - 左子树的高度差
	int _bf;
	//AVL树并没有规定必须要设计平衡因子
	//只是一个实现的选择,方便控制平衡
	//构造函数
	AVLTreeNode(const pair<K, V>& kv)
		:_kv(kv)
		, _left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _bf(0)
	{}
};


template<class K, class V>
struct AVLTree
{
	typedef AVLTreeNode<K, V> Node;
public:
	AVLTree()
		:_root(nullptr)
	{}
	bool Insert(const pair<K, V>& kv)
	{
		//1.搜索树的规则插入
		//2.看是否违反平衡规则,如果违反就需要处理:旋转

		//1.插入
		if (_root == nullptr)
		{
			_root = new Node(kv);
			_root->_bf = 0;
			return true;
		}
		Node* parent = nullptr;
		Node* cur = _root;
		while (cur)
		{
			if (cur->_kv.first > kv.first)
			{
				parent = cur;
				cur = cur->_left;
			}
			else if(cur->_kv.first < kv.first)
			{
				parent = cur;
				cur = cur->_right;
			}
			else
			{
				return false;
			}
		}
		cur = new Node(kv);
		if (kv.first < parent->_kv.first)
		{
			parent->_left = cur;
		}
		else
		{
			parent->_right = cur;
		}
		cur->_parent = parent;

		//更新平衡因子
		while (parent)
		{
			if (cur == parent->_right)//插入的节点是父节点的右子树
			{
				parent->_bf++;
			}
			else
			{
				parent->_bf--;
			}

			if (parent->_bf == -1 || parent->_bf == 1)
			{
				//parent->_bf = 0  -------->parent->_bf == -1/1
				//插入节点导致一边变高了
				//高度变了,继续更新
				cur = cur->_parent;
				parent = parent->_parent;
			}
			else if (parent->_bf == -2 || parent->_bf == 2)
			{
				//parent->_bf ==  1/-1 ------->parent->_bf == 2/-2
				//插入节点导致本来高的一边变的更高了
				//子树不平衡,需要旋转处理
				if (parent->_bf == 2 && cur->_bf == 1)//左单旋
				{
					RotateL(parent);
				}
				else if (parent->_bf == -2 && cur->_bf == -1)//右单旋
				{
					RotateR(parent);
				}
				else if (parent->_bf == -2 && cur->_bf == 1)//左右双旋
				{
					RotateLR(parent);
				}
				else if (parent->_bf == 2 && cur->_bf == -1)//右左双旋
				{
					RotateRL(parent);
				}
				else
				{
					assert(false);
				}
				break;
			}
			else if(parent->_bf == 0)//parent->_bf == 1/-1  ------->parent->_bf == 0
			{
				//插入节点填上了矮的那边
				//高度不变,停止更新
				break;
			}
			else
			{
				//插入之前AVL树就存在|平衡因子|>2的节点
				assert(false);
			}
		}

		return true;
	}

	vector<vector<int>> levelOrder()
	{
		vector<vector<int>> vv;
		if (_root == nullptr)
			return vv;
		queue<Node*> q;
		q.push(_root);
		int levelSize = 1;//levelSize控制每层节点的个数
		while (!q.empty())
		{
			vector<int> levelV;//存储每一层的节点存储的值
			while (levelSize--)//当levelSize为0时就说明当前这一层已经没有节点了
			{
				Node* front = q.front();
				levelV.push_back(front->_kv.first);
				if (front->_left != nullptr)
				{
					q.push(front->_left);
				}
				if (front->_right != nullptr)
				{
					q.push(front->_right);
				}
				q.pop();
			}
			levelSize = q.size();
			vv.push_back(levelV);
			for (auto e : levelV)
			{
				cout << e << " ";
			}
			cout << endl;
		}
		return vv;
	}
	bool IsBalanceTree()
	{
		return _IsBalanceTree(_root);
	}
	void Inorder()
	{
		_InOrder(_root);
	}
	int Height()
	{
		return _Height(_root);
	}
private:
	void RotateL(Node* parent)//左旋
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		Node* ppNode = parent->_parent;//parent节点的父节点
		parent->_right = subRL;
		//防止subRL为空节点
		if (subRL)
		{
			subRL->_parent = parent;
		}
		subR->_left = parent;
		parent->_parent = subR;
		//两种情况
		if (parent == _root)//1.parent为根节点时
		{
			_root = subR;
			_root->_parent = nullptr;
		}
		else//2.parent是局部子树
		{
			if (parent == ppNode->_left)
			{
				ppNode->_left = subR;
			}
			else
			{
				ppNode->_right = subR;
			}
			subR->_parent = ppNode;
		}
		//更新平衡因子
		parent->_bf = 0;
		subR->_bf = 0;
	}
	void RotateR(Node* parent)//右旋
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;
		Node* ppNode = parent->_parent;

		subL->_right = parent;
		parent->_parent = subL;

		parent->_left = subLR;
		if (subLR)
		{
			subLR->_parent = parent;
		}
		if (ppNode)//parent只是原来的子树
		{
			if (parent == ppNode->_left)
			{
				ppNode->_left = subL;
			}
			else
			{
				ppNode->_right = subL;
			}
			subL->_parent = ppNode;
		}
		else//parent是原来的根节点
		{
			_root = subL;
			_root->_parent = nullptr;
		}
		subL->_bf = 0;
		parent->_bf = 0;
	}
	void RotateLR(Node* parent)//左右双旋
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;
		int bf = subLR->_bf;
		//根据60的平衡因子来区分三种情况
		RotateL(parent->_left);
		RotateR(parent);
		
		//更新平衡因子
		if (bf == 0)
		{
			subL->_bf = parent->_bf = subLR->_bf = 0;
		}
		else if (bf == 1)
		{
			parent->_bf = subLR->_bf = 0;
			subL->_bf = -1;
		}
		else if (bf == -1)
		{
			parent->_bf = 1;
			subLR->_bf = subL->_bf = 0;
		}
		else
		{
			//subLR旋转前就出现了问题
			assert(false);
		}
	}
	void RotateRL(Node* parent)//右左双旋
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		int bf = subRL->_bf;
		RotateR(subR);
		RotateL(parent);

		if (bf == 0)
		{
			parent->_bf = subR->_bf = subRL->_bf = 0;
		}
		else if (bf == -1)
		{
			subRL->_bf = parent->_bf = 0;
			subR->_bf = 1;
		}
		else if (bf == 1)
		{
			subRL->_bf = subR->_bf = 0;
			parent->_bf = -1;
		}
		else
		{
			assert(false);
		}
	}
	int _Height(Node* root)
	{
		if (root == nullptr)
			return 0;

		int lh = _Height(root->_left);//左子树高度
		int rh = _Height(root->_right);
		return lh > rh ? lh + 1 : rh + 1;
	}

	bool _IsBalanceTree(Node* root)
	{
		//空树也是AVL树
		if (root == nullptr)
			return true;
		//计算出root节点的平衡因子,即左右子树的高度差
		int leftHeight = _Height(root->_left);
		int rightHeight = _Height(root->_right);
		int diff = rightHeight - leftHeight;
		//两种情况:1.
		if (abs(diff) >= 2)
		{
			cout << root->_kv.first << "节点的平衡因子异常" << endl;
			return false;
		}
		if (diff != root->_bf)
		{
			cout << root->_kv.first << "节点的平衡因子不符合实际" << endl;
			return false;
		}
		return _IsBalanceTree(root->_left) && _IsBalanceTree(root->_right);
	}
	void _InOrder(Node* root)
	{
		if (root == NULL)
		{
			return;
		}
		_InOrder(root->_left);
		cout << root->_kv.first << " ";
		_InOrder(root->_right);
	}
	Node* _root;
};

4.2 红黑树

4.2.1 红黑树的概念

红黑树 ,是一种二叉搜索树 ,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或 Black 。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出俩倍 ,因而是接近平衡的。

4.2.2 红黑树的性质

  1. 每个结点不是红色就是黑色
  2. 根节点是黑色的
  3. 如果一个节点是红色的,则它的两个孩子结点是黑色的
  4. 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点
  5. 每个叶子结点都是黑色的(此处的叶子结点指的是空结点)

思考:为什么满足上面的性质,红黑树就能保证:其最长路径中节点个数不会超过最短路径节点 个数的两倍?

最短路径:全黑(x)

最长路径:一黑一红间隔(2x)

例如:

4.2.3 红黑树节点的定义

cpp 复制代码
//红黑树的颜色
enum Colour
{
	RED,
	BLACK
};
//红黑树节点的定义
template<class K, class V>
struct RBTreeNode
{
	AVLTreeNode(const pair<K, V>& kv)
		:_kv(kv)
		, _left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _col(RED)
	{}
	//问:为什么一定要插入红色节点?
	//答:红黑树有三个条件:1、根节点为红色 2、红色节点的孩子是黑色 3、每条路径都包含相同数量的黑色节点(很难维护)
	//新增节点为红色,可能破坏条件2,不会破坏条件3
	//新增节点为黑色,可能破坏条件2,一定破坏条件3
	//综上来看,插入节点为红色更好
	RBTreeNode<K, V>* _left;
	RBTreeNode<K, V>* _right;
	RBTreeNode<K, V>* _parent;
	Colour _col;
};
//红黑树的定义
template<class K, class V>
struct RBTree
{
	typedef RBTreeNode<K, V> Node;
private:
	Node* _root;
};

4.2.4 红黑树结构

为了后续实现关联式容器简单,红黑树的实现中增加一个头结点,因为跟节点必须为黑色,为了 与根节点进行区分,将头结点给成黑色,并且让头结点的 _parent 域指向红黑树的根节点,_left 域指向红黑树中最小的节点,_right域指向红黑树中最大的节点,如下:

4.2.5 红黑树的插入操作

红黑树是在二叉搜索树的基础上加上其平衡限制条件,因此红黑树的插入可分为两步:

  1. 按照二叉搜索的树规则插入新节点

  2. 检测新节点插入后,红黑树的性质是否造到破坏

    因为新节点的默认颜色是红色 ,因此:如果其双亲节点的颜色是黑色没有违反红黑树任何性质 ,则不需要调整;但当新插入节点的双亲节点颜色为红色时,就违反了性质三不能有连在一起的红色节点,此时需要对红黑树分情况来讨论:

    约定:cur为当前节点,p为父节点,g为祖父节点,u为叔叔节点

    • 情况一: cur为红,p为红,g为黑,u存在且为红

      注意:在上面的图中,是不关心方向的(因为只变色不旋转),即p、u是g的左和右是无所谓的,cur是p的左或者右都是一样的,如下图所示:

      问:cur和p均为红,违反了性质三,此处能否将p直接改为黑?

      答:不能,因为这样做会改变性质四。

      解决方式:将p,u改为黑,g改为红,然后把g当成cur,继续向上调整。

    • 情况二: cur为红,p为红,g为黑,u不存在/u存在且为黑

      说明:

      1. 如果u节点不存在,则cur一定是新插入节点,因为如果cur不是新插入节点,则cur和p一定有一个节点的颜色是黑色(为了满足每条路径上的黑色节点数相同的规则)。
      2. 如果u节点存在,则其一定是黑色的,那么cur节点原来的颜色一定是黑色的,现在看到红色的原因是因为cur的子树在调整的过程中作为grandfather节点的颜色由黑色变为红色。

      注意:p为g的左孩子,cur为p的左孩子,则进行右单旋转,相反:

      p为g的右孩子,cur为p的右孩子,则进行左单旋转(如下图所示:)。

    • 情况三:cur为红,p为红,g为黑,u不存在/u存在且为黑

      当p在g的右边时:

代码:

cpp 复制代码
bool Insert(const pair<K, V>& kv)
{
	//1.搜索树的规则插入
	//2.看是否违反平衡规则,如果违反就需要处理:旋转

	//1.插入
	if (_root == nullptr)
	{
		_root = new Node(kv);
		_root->_col = BLACK;
		return true;
	}
	Node* parent = nullptr;
	Node* cur = _root;
	while (cur)
	{
		if (cur->_kv.first > kv.first)
		{
			parent = cur;
			cur = cur->_left;
		}
		else if (cur->_kv.first < kv.first)
		{
			parent = cur;
			cur = cur->_right;
		}
		else
		{
			return false;
		}
	}
	cur = new Node(kv);
	cur->_col = RED;
	if (kv.first < parent->_kv.first)
	{
		parent->_left = cur;
	}
	else
	{
		parent->_right = cur;
	}
	cur->_parent = parent;
	//存在连续的红色节点(当父亲不存在的时候说明已经到达了根节点)
	//为什么用parent来进行讨论?因为一次循环之后,cur会从g(祖父)处继续进行新的循环
	while (parent && parent->_col == RED)
	{
		//问:为什么不用判断祖父是否为空?
		//答:因为出现两个连续的红节点了,而红节点必然不可能是根节点,所以上面的条件满足的情况下,祖父节点必然存在
		Node* grandfather = parent->_parent;
		if (parent == grandfather->_left)
		{
			Node* uncle = grandfather->_right;
			if (uncle && uncle->_col == RED)//情况一
			{
				parent->_col = uncle->_col = BLACK;
				grandfather->_col = RED;
				//问:为什么要将祖父节点变红?
				//答:因为不确定此时的祖父节点是否是根节点,变红是为了满足规则(每条路径黑色节点数相同)

				//继续往上处理,因为这可能是一棵局部子树,也违反了规则(两个连续的红色节点)
				cur = grandfather;
				parent = cur->_parent;
			}
			else//叔叔不存在或者叔叔存在且为黑
			{

				if (cur == parent->_left)//cur是父亲的左边:单旋
				{
					//    g
					//  p
					//c
					RotateR(grandfather);
					parent->_col = BLACK;
					grandfather->_col = RED;
				}
				else//cur是父亲的右边:双旋
				{
					//   g
					//p
					//   c
					RotateL(parent);
					RotateR(grandfather);
					cur->_col = BLACK;
					grandfather->_col = RED;
				}
				break;
			}

		}
		else//父亲是祖父的右边
		{
			Node* uncle = grandfather->_left;

			if (uncle && uncle->_col == RED)//情况一
			{
				parent->_col = uncle->_col = BLACK;
				grandfather->_col = RED;

				//继续往上处理,因为这可能是一棵局部子树,也违反了规则
				cur = grandfather;
				parent = cur->_parent;
			}
			else//叔叔不存在或者叔叔存在且为黑色
			{
				if (cur == parent->_right)//cur是父亲的右边
				{
					//g
					//  p
					//    c
					RotateL(grandfather);
					parent->_col = BLACK;
					grandfather->_col = RED;
				}
				else//cur是父亲的左边
				{
					//g
					//   p
					//c
					RotateR(parent);
					RotateL(grandfather);
					cur->_col = BLACK;
					grandfather->_col = RED;
				}

				break;
			}

		}
		_root->_col = BLACK;
	}
	return true;
}

4.2.6 红黑树的验证

红黑树的检测分为两步:

  1. 检测其是否满足二叉搜索树(中序遍历是否为有序序列)
  2. 检测其是否满足红黑树的性质
cpp 复制代码
bool IsRBTree()
{
	//验证是否满足根节点为黑色的规则
	Node* pRoot = _root;
	//空树也是红黑树
	if (nullptr == pRoot)
	{
		return true;
	}
	//检测根节点是否满足的情况
	if (pRoot->_col == RED)
	{
		cout << "违反了红黑树性质二:根节点必须为黑色" << endl;
		return false;
	}
	//问:如何检查不存在连续的红色节点的这个性质?
	//答:两个方案:
	//方案一:遇到红色节点就检查孩子(不好实现)
	//方案二:遇到红色节点就检查父亲

	//获取任意一条路径中黑色节点的个数,用作基准值进行比对
	size_t blackCount = 0;
	Node* cur = pRoot;
	while (cur)
	{
		if (BLACK == cur->_col)
			blackCount++;

		cur = cur->_left;
	}
	size_t k = 0;
	return _IsValidRBTree(pRoot, k, blackCount);
}
bool _IsValidRBTree(Node* root, size_t k, const size_t blackCount)
{
	if (nullptr == root)
	{
		if (k != blackCount)
		{
			cout << "违反性质四:每条路径中黑色节点的个数必须相同" << endl;
			return false;
		}
		return true;
	}
	//统计当前路径中黑色节点的数目
	if (BLACK == root->_col)
	{
		k++;
	}
	//检测当前节点与其双亲是否为红色节点
	Node* parent = root->_parent;
	if (parent && parent->_col == RED && root->_col == RED)
	{
		cout << "违反性质三:没有连在一起的红色节点" << endl;
		return false;
	}
	return _IsValidRBTree(root->_left, k, blackCount) &&
		_IsValidRBTree(root->_right, k, blackCount);
}

4.2.7 红黑树与AVL树的比较

红黑树和AVL树都是高效的平衡二叉树,增删改查的时间复杂度都是O(log_2 N),红黑树不追求绝对平衡,其只需保证最长路径不超过最短路径的2倍,相对而言,降低了插入和旋转的次数, 所以在经常进行增删的结构中性能比AVL树更优,而且红黑树实现比较简单,所以实际运用中红黑树更多。

4.2.8 红黑树的模拟实现

cpp 复制代码
#include<utility>
enum Colour
{
	RED,
	BLACK
};
template<class K, class V>
struct RBTreeNode
{
	RBTreeNode(const pair<K, V>& kv)
		:_kv(kv)
		, _left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _col(RED)
	{}

	//问:为什么一定要插入红色节点?
	//答:红黑树有三个条件:1、根节点为红色 2、红色节点的孩子是黑色 3、每条路径都包含相同数量的黑色节点(很难维护)
	//新增节点为红色,可能破坏条件2,不会破坏条件3
	//新增节点为黑色,可能破坏条件2,一定破坏条件3
	//综上来看,插入节点为红色更好
	RBTreeNode<K, V>* _right;
	RBTreeNode<K, V>* _left;
	RBTreeNode<K, V>* _parent;
	pair<K, V> _kv;
	Colour _col;
};

template<class K, class V>
struct RBTree
{
	typedef RBTreeNode<K, V> Node;
public:
    //构造函数
	RBTree()
		:_root(nullptr)
	{}
	void RotateL(Node* parent)//左旋
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		Node* ppNode = parent->_parent;//parent节点的父节点
		parent->_right = subRL;
		//防止subRL为空节点
		if (subRL)
		{
			subRL->_parent = parent;
		}
		subR->_left = parent;
		parent->_parent = subR;
		//两种情况
		if (parent == _root)//1.parent为根节点时
		{
			_root = subR;
			_root->_parent = nullptr;
		}
		else//2.parent是局部子树
		{
			if (parent == ppNode->_left)
			{
				ppNode->_left = subR;
			}
			else
			{
				ppNode->_right = subR;
			}
			subR->_parent = ppNode;
		}
	}
	void RotateR(Node* parent)//右旋
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;
		Node* ppNode = parent->_parent;

		subL->_right = parent;
		parent->_parent = subL;

		parent->_left = subLR;
		if (subLR)
		{
			subLR->_parent = parent;
		}
		if (ppNode)//parent只是原来的子树
		{
			if (parent == ppNode->_left)
			{
				ppNode->_left = subL;
			}
			else
			{
				ppNode->_right = subL;
			}
			subL->_parent = ppNode;
		}
		else//parent是原来的根节点
		{
			_root = subL;
			_root->_parent = nullptr;
		}
	}
    //红黑树节点的插入
	bool Insert(const pair<K, V>& kv)
	{
		//1.搜索树的规则插入
		//2.看是否违反平衡规则,如果违反就需要处理:旋转

		//1.插入
		if (_root == nullptr)
		{
			_root = new Node(kv);
			_root->_col = BLACK;
			return true;
		}
		Node* parent = nullptr;
		Node* cur = _root;
		while (cur)
		{
			if (cur->_kv.first > kv.first)
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (cur->_kv.first < kv.first)
			{
				parent = cur;
				cur = cur->_right;
			}
			else
			{
				return false;
			}
		}
		cur = new Node(kv);
		cur->_col = RED;
		if (kv.first < parent->_kv.first)
		{
			parent->_left = cur;
		}
		else
		{
			parent->_right = cur;
		}
		cur->_parent = parent;
		//存在连续的红色节点(当父亲不存在的时候说明已经到达了根节点)
		//为什么用parent来进行讨论?因为一次循环之后,cur会从g(祖父)处继续进行新的循环
		while (parent && parent->_col == RED)
		{
			//问:为什么不用判断祖父是否为空?
			//答:因为出现两个连续的红节点了,而红节点必然不可能是根节点,所以上面的条件满足的情况下,祖父节点必然存在
			Node* grandfather = parent->_parent;
			if(parent == grandfather->_left)
			{
				Node* uncle = grandfather->_right;
				if (uncle && uncle->_col == RED)//情况一
				{
					parent->_col = uncle->_col = BLACK;
					grandfather->_col = RED;
					//问:为什么要将祖父节点变红?
					//答:因为不确定此时的祖父节点是否是根节点,变红是为了满足规则(每条路径黑色节点数相同)

					//继续往上处理,因为这可能是一棵局部子树,也违反了规则(两个连续的红色节点)
					cur = grandfather;
					parent = cur->_parent;
				}
				else//叔叔不存在或者叔叔存在且为黑
				{
					
					if (cur == parent->_left)//cur是父亲的左边:单旋
					{
						//    g
						//  p
						//c
						RotateR(grandfather);
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					else//cur是父亲的右边:双旋
					{
						//   g
						//p
						//   c
						RotateL(parent);
						RotateR(grandfather);
						cur->_col = BLACK;
						grandfather->_col = RED;
					}
					break;
				}
				
			}
			else//父亲是祖父的右边
			{
				Node* uncle = grandfather->_left;

				if (uncle && uncle->_col == RED)//情况一
				{
					parent->_col = uncle->_col = BLACK;
					grandfather->_col = RED;

					//继续往上处理,因为这可能是一棵局部子树,也违反了规则
					cur = grandfather;
					parent = cur->_parent;
				}
				else//叔叔不存在或者叔叔存在且为黑色
				{
					if (cur == parent->_right)//cur是父亲的右边
					{
						//g
						//  p
						//    c
						RotateL(grandfather);
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					else//cur是父亲的左边
					{
						//g
						//   p
						//c
						RotateR(parent);
						RotateL(grandfather);
						cur->_col = BLACK;
						grandfather->_col = RED;
					}

					break;
				}
				
			}
			_root->_col = BLACK;
		}
		return true;
	}
    //中序遍历
	void Inorder()
	{
		_InOrder(_root);
	}
    //层序遍历
	vector<vector<int>> levelOrder()
	{
		vector<vector<int>> vv;
		if (_root == nullptr)
			return vv;
		queue<Node*> q;
		q.push(_root);
		int levelSize = 1;//levelSize控制每层节点的个数
		while (!q.empty())
		{
			vector<int> levelV;//存储每一层的节点存储的值
			while (levelSize--)//当levelSize为0时就说明当前这一层已经没有节点了
			{
				Node* front = q.front();
				levelV.push_back(front->_kv.first);
				if (front->_left != nullptr)
				{
					q.push(front->_left);
				}
				if (front->_right != nullptr)
				{
					q.push(front->_right);
				}
				q.pop();
			}
			levelSize = q.size();
			vv.push_back(levelV);
			for (auto e : levelV)
			{
				cout << e << " ";
			}
			cout << endl;
		}
		return vv;
	}
    //求二叉树节点的高度
	int Height()
	{
		return _maxHeight(_root);
	}
	//判断是否为二叉树
	bool IsRBTree()
	{
		//验证是否满足根节点为黑色的规则
		Node* pRoot = _root;
		//空树也是红黑树
		if (nullptr == pRoot)
		{
			return true;
		}
		//检测根节点是否满足的情况
		if (pRoot->_col == RED)
		{
			cout << "违反了红黑树性质二:根节点必须为黑色" << endl;
			return false;
		}
		//问:如何检查不存在连续的红色节点的这个性质?
		//答:两个方案:
		//方案一:遇到红色节点就检查孩子(不好实现)
		//方案二:遇到红色节点就检查父亲

		//获取任意一条路径中黑色节点的个数,用作基准值进行比对
		size_t blackCount = 0;
		Node* cur = pRoot;
		while (cur)
		{
			if (BLACK == cur->_col)
				blackCount++;

			cur = cur->_left;
		}
		size_t k = 0;
		return _IsValidRBTree(pRoot, k, blackCount);
	}
    
	bool _IsValidRBTree(Node* root, size_t k, const size_t blackCount)
	{
		if (nullptr == root)
		{
			if (k != blackCount)
			{
				cout << "违反性质四:每条路径中黑色节点的个数必须相同" << endl;
				return false;
			}
			return true;
		}
		//统计当前路径中黑色节点的数目
		if (BLACK == root->_col)
		{
			k++;
		}
		//检测当前节点与其双亲是否为红色节点
		Node* parent = root->_parent;
		if (parent && parent->_col == RED && root->_col == RED)
		{
			cout << "违反性质三:没有连在一起的红色节点" << endl;
			return false;
		}
		return _IsValidRBTree(root->_left, k, blackCount) &&
			_IsValidRBTree(root->_right, k, blackCount);
	}
private:
	Node* _root;
	void _InOrder(Node* root)
	{
		if (root == NULL)
		{
			return;
		}
		_InOrder(root->_left);
		cout << root->_kv.first << " ";
		_InOrder(root->_right);
	}

	int _maxHeight(Node* root)
	{
		if (root == nullptr)
			return 0;

		int lh = _maxHeight(root->_left);//左子树高度
		int rh = _maxHeight(root->_right);
		return lh > rh ? lh + 1 : rh + 1;
	}
	int _minHeight(Node* root)
	{
		if (root == nullptr)
			return 0;

		int lh = _minHeight(root->_left);//左子树高度
		int rh = _minHeight(root->_right);
		return lh < rh ? lh + 1 : rh + 1;
	}
};

4.2.9 红黑树的应用

  1. C++ STL库 -- map/set、mutil_map/mutil_set
  2. Java 库
  3. linux内核
  4. 其他一些库

http://www.cnblogs.com/yangecnu/p/Introduce-Red-Black-Tree.html

4.3 红黑树模拟实现STL中的map与set

代码:

RBTree.h头文件

cpp 复制代码
#pragma once
#include<utility>
#include<string>
#include<iostream>
using namespace std;

enum Colour
{
	RED,
	BLACK
};

template<class T>
struct RBTreeNode
{
	RBTreeNode(const T& data)
		:_data(data)
		, _left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _col(RED)
	{}

	//问:为什么一定要插入红色节点?
	//答:红黑树有三个条件:1、根节点为红色 2、红色节点的孩子是黑色 3、每条路径都包含相同数量的黑色节点(很难维护)
	//新增节点为红色,可能破坏条件2,不会破坏条件3
	//新增节点为黑色,可能破坏条件2,一定破坏条件3
	//综上来看,插入节点为红色更好
	RBTreeNode<T>* _right;
	RBTreeNode<T>* _left;
	RBTreeNode<T>* _parent;
	T _data;//数据
	Colour _col;
};

template<class T, class Ref, class Ptr>
struct __RBTreeIterator
{
	typedef RBTreeNode<T> Node;
	typedef __RBTreeIterator<T, Ref, Ptr> Self;
	Node* _node;

	__RBTreeIterator(Node* node)
		:_node(node)
	{}

	Ref operator*()
	{
		return _node->_data;
	}

	Ptr operator->()
	{
		return &_node->_data;
	}

	Self& operator++()
	{
		//当前位置的右子树是否是空
		//1.非空:右子树的最左节点
		//2.空:找孩子是祖先左的那个祖先节点
		if (_node->_right == nullptr)
		{
			//找祖先里面,孩子是父亲左的那个
			Node* cur = _node;
			Node* parant = _node->_parent;
			while (parant && cur == parant->_right)
			{
				parant = parant->_parent;
				cur = cur->_parent;
			}
			_node = parant;
		}
		else
		{
			Node* subLeft = _node->_right;
			while (subLeft && subLeft->_left)
			{
				subLeft = subLeft->_left;
			}
			_node = subLeft;
		}
		return *this;
	}
	Self operator++(int)
	{
		Self tmp(*this);
		++(*this);
		return tmp;
	}

	Self& operator--()
	{
		//当前位置的右子树是否是空
		//1.非空:左子树的最右节点
		//2.空:找孩子是祖先右子树的那个祖先节点
		if (_node->_left == nullptr)
		{
			//找祖先里面,孩子是父亲右的那个节点
			Node* cur = _node;
			Node* parent = _node->_parent;
			while (parent && cur == parent->_left)
			{
				parent = parent->_left;
			}
			_node = parent;
		}
		else
		{
			Node* subRight = _node->_left;
			while (subRight && subRight->_right)
			{
				subRight = subRight->_right;
			}
			_node = subRight;
		}
		return *this;
	}

	Self operator--(int)
	{
		Self tmp(*this);
		--(*this);
		return tmp;

	}

	bool operator!=(const Self& s)
	{
		return _node != s._node;
	}

	bool operator==(const Self& s)
	{
		return _node == s._node;
	}
};


//set ----   RBTree<K, K>
//map ----   RBTree<K, pair<K,V>>
template<class K, class T, class KeyOfT>
class RBTree
{
	typedef RBTreeNode<T> Node;

public:
	//构造、拷贝构造、赋值和析构跟搜索树的实现方式是一样的
	typedef __RBTreeIterator<T, T&, T*> iterator;
	typedef __RBTreeIterator<T, const T&, const T*> const_iterator;
	RBTree()
		:_root(nullptr)
	{}

	iterator Begin()
	{
		Node* subLeft = _root;
		while (subLeft && subLeft->_left)
		{
			subLeft = subLeft->_left;
		}
		return iterator(subLeft);
	}
	
	const_iterator Begin()const
	{
		Node* subLeft = _root;
		while (subLeft && subLeft->_left)
		{
			subLeft = subLeft->_left;
		}
		return const_iterator(subLeft);
	}
	
	iterator End()
	{
		return iterator(nullptr);
	}
	
	const_iterator End()const
	{
		return const_iterator(nullptr);
	}
	iterator Find(const K& key)
	{
		Node* cur = _root;
		KeyOfT kot;
		while (cur)
		{
			if (key < kot(cur->_data))
			{
				cur = cur->_left;
			}
			else if(key > kot(cur->_data))
			{
				cur = cur->_right;
			}
			else
			{
				return iterator(cur);

			}
		}
		return iterator(nullptr);
	}

	pair<iterator, bool> Insert(const T& data)
	{
		//1.搜索树的规则插入
		//2.看是否违反平衡规则,如果违反就需要处理:旋转

		//1.插入
		if (_root == nullptr)
		{
			_root = new Node(data);
			_root->_col = BLACK;
			return make_pair(iterator(_root), true);
		}
		KeyOfT kot;
		Node* parent = nullptr;
		Node* cur = _root;
		while (cur)
		{
			if (kot(cur->_data) > kot(data))
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (kot(cur->_data) < kot(data))
			{
				parent = cur;
				cur = cur->_right;
			}
			else
			{
				return make_pair(iterator(cur), false);
			}
		}
		cur = new Node(data);
		Node* newNode = cur;
		cur->_col = RED;
		if (kot(data) < kot(parent->_data))
		{
			parent->_left = cur;
		}
		else
		{
			parent->_right = cur;
		}
		cur->_parent = parent;
		//存在连续的红色节点(当父亲不存在的时候说明已经到达了根节点)
		//为什么用parent来进行讨论?因为一次循环之后,cur会从g(祖父)处继续进行新的循环
		while (parent && parent->_col == RED)
		{
			//问:为什么不用判断祖父是否为空?
			//答:因为出现两个连续的红节点了,而红节点必然不可能是根节点,所以上面的条件满足的情况下,祖父节点必然存在
			Node* grandfather = parent->_parent;
			if (parent == grandfather->_left)
			{
				Node* uncle = grandfather->_right;
				if (uncle && uncle->_col == RED)//情况一
				{
					parent->_col = uncle->_col = BLACK;
					grandfather->_col = RED;
					//问:为什么要将祖父节点变红?
					//答:因为不确定此时的祖父节点是否是根节点,变红是为了满足规则(每条路径黑色节点数相同)

					//继续往上处理,因为这可能是一棵局部子树,也违反了规则(两个连续的红色节点)
					cur = grandfather;
					parent = cur->_parent;
				}
				else//叔叔不存在或者叔叔存在且为黑
				{

					if (cur == parent->_left)//cur是父亲的左边:单旋
					{
						//    g
						//  p
						//c
						RotateR(grandfather);
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					else//cur是父亲的右边:双旋
					{
						//   g
						//p
						//   c
						RotateL(parent);
						RotateR(grandfather);
						cur->_col = BLACK;
						grandfather->_col = RED;
					}
					break;
				}

			}
			else//父亲是祖父的右边
			{
				Node* uncle = grandfather->_left;

				if (uncle && uncle->_col == RED)//情况一
				{
					parent->_col = uncle->_col = BLACK;
					grandfather->_col = RED;

					//继续往上处理,因为这可能是一棵局部子树,也违反了规则
					cur = grandfather;
					parent = cur->_parent;
				}
				else//叔叔不存在或者叔叔存在且为黑色
				{
					if (cur == parent->_right)//cur是父亲的右边
					{
						//g
						//  p
						//    c
						RotateL(grandfather);
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					else//cur是父亲的左边
					{
						//g
						//   p
						//c
						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;
		Node* ppNode = parent->_parent;//parent节点的父节点
		parent->_right = subRL;
		//防止subRL为空节点
		if (subRL)
		{
			subRL->_parent = parent;
		}
		subR->_left = parent;
		parent->_parent = subR;
		//两种情况
		if (parent == _root)//1.parent为根节点时
		{
			_root = subR;
			_root->_parent = nullptr;
		}
		else//2.parent是局部子树
		{
			if (parent == ppNode->_left)
			{
				ppNode->_left = subR;
			}
			else
			{
				ppNode->_right = subR;
			}
			subR->_parent = ppNode;
		}
	}
	void RotateR(Node* parent)//右旋
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;
		Node* ppNode = parent->_parent;

		subL->_right = parent;
		parent->_parent = subL;

		parent->_left = subLR;
		if (subLR)
		{
			subLR->_parent = parent;
		}
		if (ppNode)//parent只是原来的子树
		{
			if (parent == ppNode->_left)
			{
				ppNode->_left = subL;
			}
			else
			{
				ppNode->_right = subL;
			}
			subL->_parent = ppNode;
		}
		else//parent是原来的根节点
		{
			_root = subL;
			_root->_parent = nullptr;
		}
	}
private:
	Node* _root;
};

Map.h

cpp 复制代码
#pragma once
#include"RBTree.h"

namespace Test
{
	template<class K, class V>
	class map
	{
		struct MapKeyOfT
		{
			const K& operator()(const pair<K, V>& kv)
			{
				return kv.first;
			}
		};
	public:
		typedef typename RBTree<K, pair<K, V>, MapKeyOfT>::iterator iterator;
		typedef typename RBTree<K, pair<K, V>, MapKeyOfT>::const_iterator const_iterator;
		pair<iterator, bool> insert(const pair<K, V>& kv)
		{
			return _t.Insert(kv);
		}
		iterator begin()
		{
			return _t.Begin();
		}
		iterator end()
		{
			return _t.End();
		}
		iterator find(const K& key)
		{
			return _t.Find(key);
		}
		V& operator[](const K& key)
		{
			pair<iterator, bool> ret = _t.Insert(make_pair(key, V()));
			return ret.first->second;
		}

	private:
		RBTree<K, pair<K, V>, MapKeyOfT> _t;


		//问:能否不用第一个模板参数K?
		//答:不能。因为里面实现的其它函数接口里面会用到key,比如find时要使用到key。
	};
}

Set.h

cpp 复制代码
#pragma once
#include"RBTree.h"
namespace Test
{
	template<class K>
	class set
	{
	public:

		struct SetKeyOfT
		{
			const K& operator()(const K& k)
			{
				return k; 
			}
		};
		typedef typename RBTree<K, K, SetKeyOfT>::const_iterator iterator;
		typedef typename RBTree<K, K, SetKeyOfT>::const_iterator const_iterator;

		//无论对象是否是const类型的对象,调用迭代器之后都将具有const属性
		//调用的都是const的迭代器(const_iterator Begin()const),都无法修改set元素
		iterator begin()const
		{
			return _t.Begin();
		}
		iterator end()const
		{
			return _t.End();
		}
		pair<iterator, bool> insert(const K& key)
		{
			auto ret = _t.Insert(key);
			return pair<iterator, bool>(iterator(ret.first._node), ret.second);
		}

		iterator find(const K& key)
		{
			return _t.Find(key);
		}
	private:
		RBTree<K, K, SetKeyOfT> _t;
	};
}
相关推荐
lsx2024068 小时前
Django 视图详解
开发语言
h***06658 小时前
【JSqlParser】Java使用JSqlParser解析SQL语句总结
java·开发语言·sql
代码or搬砖8 小时前
Java Lambda 表达式全面详解
java·开发语言·python
biter00889 小时前
Ubuntu 22.04 有线网络时好时坏?最终解决方案
linux·网络·ubuntu
这周也會开心9 小时前
JDK1.8新增语法
java·开发语言
心随雨下9 小时前
TypeScript泛型开发常见错误解析
java·开发语言·typescript
德育处主任9 小时前
『NAS』轻松获取群晖自带的壁纸
服务器·docker
zzzsde9 小时前
【Linux】基础开发工具(3):编译器
linux·运维·服务器
郝学胜-神的一滴9 小时前
现代OpenGL窗口管理:GLFW从入门到实战
开发语言·c++·程序人生·图形渲染·个人开发
Bona Sun10 小时前
单片机手搓掌上游戏机(十六)—pico运行fc模拟器之程序修改烧录
c语言·c++·单片机·游戏机