• map && set


    目录

    1. 序列式容器和关联式容器

    2. 键值对

    2.1. make_pair

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

    3.1. set(Key模型)

    3.1.1. std::set::find && std::set:count

    3.2. map(Key-Value模型)

    3.2.1. std::map::insert 

    3.2.2. operator[]

    3.3. multimap

    3.4. multiset

    3.4.1. std::multiset::find

    3.4.2. std::multiset::count

    3.5. 关于map和set的练习题

    3.5.1. 前K个高频单词:

    3.5.2.  两个数组的交集

    4. 底层结构 

    4.1. AVL树

    4.1.1 AVL树的插入

    4.1.1.1 平衡因子的更新

    4.1.1.2. AVL树的旋转

    4.1.1.2.1. 左单旋

    4.1.1.2.2. 右单旋

    4.1.1.2.3. 左右双旋

    ​编辑

    4.1.1.2.4. 右左双旋

     4.1.1.3. 验证AVL树的插入

    4.1.1.4. AVL树插入的完整实现

    4.2. 红黑树

    4.2.1. 红黑树的插入

    4.2.1.1. 情况一:新增节点的父亲为空

    4.2.1.2. 情况二:新增节点的父亲非空且为黑色节点

    4.2.1.3. 情况三:当父亲为红节点,叔叔存在且为红

     4.2.1.4. 情况四:当父亲为红节点,叔叔不存在或者存在为黑

    4.2.1.5. 情况五:父亲为红、叔叔不存在或者为黑

     4.2.1.5. 验证红黑树的插入

    4.2.1.6.  rb_tree插入的完整实现

    4.3. map和set的封装

    4.3.1. map的封装

    4.3.2. set的封装


    1. 序列式容器和关联式容器

    在之前我们已经学过了一些容器,例如string、vector、list等它们都被称之为序列式容器

    那什么叫做序列式容器?什么叫做关联式容器呢?

    序列式容器:

    序列式容器是指一种数据结构其底层为线性序列的数据结构,里面存储的是元素本身。

    序列式容器的特点是元素之间有固定的次序关系,并且可以根据位置进行访问。常见的序列式容器包括list、vector、string、deque等

    这些容器在使用时可以根据需要进行插入、删除、查找等操作,还可以使用迭代器来遍历容器中的元素。序列式容器提供了不同的访问和操作方式,满足不同场景下的需求。

    关联式容器:
    关联式容器是指一种数据结构,用于存储和管理以关键字(Key)为主要标识的数据元素。与序列式容器不同,关联式容器的元素是按照关键字进行组织和访问的。

    关联式容器通常使用红黑树(Red-Black Tree)等高效的数据结构来实现,在插入、删除、查找等操作上具有较高的效率。它们提供了基于关键字的访问和操作能力。

    常见的关联式容器包括集合(Set)、多重集合(Multiset)、映射(Map)和多重映射(Multimap)等。集合和多重集合存储唯一的关键字(Key),映射和多重映射存储关键字-值(Key-Value)对(也被称之为键值对)。

    关联式容器可以根据关键字(Key)进行快速的查找,使得数据的访问更加高效。同时,关联式容器往往还提供了排序、去重等功能,适用于需要按照关键字进行检索和排序的场景。

    2. 键值对

    键值对(Key-Value Pair)是一种数据表示方式,用于将一个值(Value)与一个唯一标识符(Key)相关联,我们可以通过Value找到Key。

    在键值对中,键(Key)用于唯一标识和索引值,而值(Value)则是与该键关联的数据。通过使用键作为索引,可以快速地访问和操作与之相关联的值。

    键值对可以用于表示和管理各种数据,例如字典、映射和属性列表等。它是一种非常常见和重要的数据组织方式,广泛应用于计算机科学和编程领域。

    在关联式容器中,键值对经常被使用。例如,在映射(Map)中,每个键对应一个唯一的值(这里的键具有唯一性);在多重映射(Multimap)中,一个键可以对应多个值(即允许相同的键值);在属性列表中,键值对用于表示对象的属性和对应的取值等。通过键值对的方式,我们可以方便地访问和操作数据。

    而在SGI-STL(Standard Template Library)中定义了名为pair的键值对结构

    pair是一个模板类,用于表示具有两个成员的键值对

    pair的定义如下:

    1. template <class T1, class T2>
    2. struct pair
    3. {
    4. typedef T1 first_type; // 键类型
    5. typedef T2 second_type; // 值类型
    6. T1 first; // 键(Key)
    7. T2 second; // 值(Value)
    8. // 构造函数
    9. pair()
    10. :first(T1())
    11. ,second(T2())
    12. {}
    13. pair(const T1& x, const T2& y)
    14. :first(x)
    15. ,second(y)
    16. {}
    17. template <class U, class V>
    18. pair(const pair& p)
    19. :first(p.first)
    20. ,seconde(p.second)
    21. {}
    22. };

    pair模板类具有两个模板参数,分别表示键的类型(T1值的类型(T2。它包含了两个公有成员变量 first 和 second,分别用于存储键和值。

    pair提供了多个构造函数,使得可以方便地创建和初始化键值对对象。除了默认构造函数外,还有一个接受键和值作为参数的构造函数,以及一个模板构造函数,允许从其他类型的 pair 对象转换。

    通过使用 pair,我们可以方便地组织和操作键值对数据。在STL的各种关联式容器中,经常使用 pair 来表示键值对,以实现高效的数据存储和访问。

    2.1. make_pair

    make_pair 是一个 C++ STL(标准模板库)中的函数模板,用于创建一个 pair 对象。

    pair 是一个模板类,可以保存两个不同类型的值。make_pair 函数模板允许你通过传递两个值作为参数,创建一个 pair 匿名对象,并自动推断出适当的类型

    在 C++ STL 中,make_pair 函数模板的具体实现通常是位于  头文件中,下面是它的一种简单实现示例:

    1. template <class T1, class T2>
    2. pair make_pair(T1 t, T2 u)
    3. {
    4. return pair(t, u);
    5. }

    上述示例只是make_pair一种常见的实现方式,但实际实现可能会有所不同。在编写代码时,通常可以直接使用  头文件中的 make_pair,而不需要关心其具体实现细节。 

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

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

    3.1. set(Key模型)

    T: set 中存放元素的类型,实际在底层存储 的键值对。
    Compare set 中元素默认按照小于来比较
    Alloc set 中元素空间的管理方式,使用 STL 提供的空间配置器管理

    set是一种集合模型(Set Model),它是关联式容器的一种。集合模型是一种数据结构,用于存储一组唯一值的集合,并提供高效的插入、删除和查找操作。

    在集合模型中,每个元素都是唯一的(Key值是唯一的),不会存在重复值。集合模型通常基于某种平衡树或哈希表实现,以确保高效的查找和去重功能。

    C++标准库中的 set 是一种基于红黑树(Red-Black-Tree)的关联式容器。C++中的set以红黑树的方式存储唯一值,并提供了一系列的操作,如插入新元素、删除指定元素、查找特定元素等。由于使用了红黑树作为底层数据结构,set 在插入和删除操作时能够维持自动的排序和平衡,具有较快的查找性能

    set 提供了一系列的成员函数和算法,用于操作和访问集合中的元素。它的特点是按照升序排列元素,并且每个元素都是唯一的。如果需要存储多个相同值的元素,可以使用 multiset 容器set 在各种场景中都有广泛的应用,例如去重、排序、查找等。

    在 C++ 的 set 容器中,元素的键值是不可修改的。这是因为 set 是基于红黑树实现的,红黑树中的每个元素节点的键值是用于按照特定顺序进行排序和比较的,保持元素的有序性。

    一旦将一个元素插入到 set 中,它的键值就不能再被修改。如果需要修改元素的键值,你需要先将该元素从 set 中删除,然后修改其值,最后再将修改后的元素重新插入到 set 中。

    测试一:验证set中的Key是否具有唯一性和它的排序性

    1. void Test1(void)
    2. {
    3. std::vector<int> v{ 6, 2, 4, 3, 1, 2, 6, 4, 3, 8, 7 };
    4. std::set<int> s;
    5. for (auto e : v)
    6. {
    7. s.insert(e);
    8. }
    9. std::set<int>::iterator it = s.begin();
    10. while (it != s.end())
    11. {
    12. std::cout << *it << " ";
    13. ++it;
    14. }
    15. std::cout << std::endl;
    16. }

    测试结果:

    1 2 3 4 6 7 8 

    我们发现,std::set中的元素的确不可重复, 且迭代器遍历的顺序默认是升序。即set的作用:去重 + 排序

     测试二:set中的Key值是否可以被修改

     可以看到,在 C++ 的 set 容器中,元素的键值是不可修改的

    3.1.1. std::set::find && std::set:count

    1. /*
    2. ***value_type: The first template parameter (T)
    3. ***size_type: usually the same as size_t
    4. */
    5. iterator find (const value_type& val) const;
    6. size_type count (const value_type& val) const;

    set::find()可以判断某个Key在不在

    找到了返回迭代器的位置,如果没找到返回end()

    set::count()也可以判断某个Key在不在

    如果存在返回1,otherwise,返回0

    测试三:测试find

    1. void Test3(void)
    2. {
    3. std::vector<int> v{ 6, 2, 4, 3, 1, 2, 6, 4, 3, 8, 7 };
    4. std::set<int> s;
    5. for (auto e : v)
    6. {
    7. s.insert(e);
    8. }
    9. int x = 0;
    10. while (1)
    11. {
    12. std::cin >> x;
    13. std::set<int>::iterator pos = s.find(x);
    14. //找到了返回对应迭代器,没找到返回end()
    15. if (pos != s.end())
    16. {
    17. std::cout << "找到了" << std::endl;
    18. }
    19. else
    20. {
    21. std::cout << "没找到" << std::endl;
    22. }
    23. }
    24. }

    测试四: 测试count

    1. void Test4(void)
    2. {
    3. std::vector<int> v{ 6, 2, 4, 3, 1, 2, 6, 4, 3, 8, 7 };
    4. std::set<int> s;
    5. for (auto e : v)
    6. {
    7. s.insert(e);
    8. }
    9. int x = 0;
    10. while (1)
    11. {
    12. std::cin >> x;
    13. size_t ret = s.count(x);
    14. // 找到了返回1,没找到返回0
    15. if (ret)
    16. {
    17. std::cout << "找到了" << std::endl;
    18. }
    19. else
    20. {
    21. std::cout << "没找到" << std::endl;
    22. }
    23. }
    24. }

    3.2. map(Key-Value模型)

    key: 键值对中Key 的类型
    T : 键值对中V alue 的类型
    Compare: 比较器的类型, map 中的元素是按照 key 来比较的,缺省情况下按照小于来比较,一般情况下( 内置类型元素 ) 该参数不需要传递,如果无法比较时 ( 自定义类型 ) ,需要用户自己显式传递比较规则( 一般情况下按照函数指针或者仿函数来传递 )
    Alloc :通过空间配置器来申请底层空间,不需要用户传递,除非用户不想使用标准库提供的
    空间配置器
    注意:在使用 map 时,需要包含头文件 #include

    map 是 C++ STL(标准模板库)中的一个关联容器,它提供了一种将键(key)与值(value)关联起来的方式。

    map 类似于字典,它由一系列键值对组成。每个键与一个对应的值相关联,这样就可以通过键来快速查找和访问对应的值。map 中的键是唯一的,每个键对应一个值

    map 内部使用一种红黑树的数据结构来实现,它能够保持键的有序性,这使得 map 在数据查找方面非常高效。对于大量的数据,map 的插入、查找和删除操作的时间复杂度都是 O(log n)。

    3.2.1. std::map::insert 

    1. /*
    2. ***value_type : pair
    3. ***key_type : The first template parameter (Key)
    4. ***mapped_type: The second template parameter (T)
    5. */
    6. single element (1)
    7. pairbool> insert (const value_type& val);
    8. with hint (2)
    9. iterator insert (iterator position, const value_type& val);
    10. range (3)
    11. template <class InputIterator>
    12. void insert (InputIterator first, InputIterator last);

    测试六:测试insert(1)

    1. void Test6(void)
    2. {
    3. std::map dict;
    4. dict.insert(std::pair("current", "当前的"));
    5. dict.insert(std::pair("note", "注意"));
    6. // 但是我们一般不使用上面的方式(通过构造pair的匿名对象)
    7. // 我们一般使用std::make_pair
    8. // make_pair会根据我们传入的参数自动构造pair的匿名对象并返回
    9. dict.insert(std::make_pair("delete", "删除"));
    10. dict.insert(std::make_pair("segmentation", "分割"));
    11. dict.insert(std::make_pair("exist", "存在"));
    12. // map支持插入相同的Key吗?
    13. dict.insert(std::make_pair("note", "笔记"));
    14. // 如何遍历 ?
    15. std::map::iterator it = dict.begin();
    16. while (it != dict.end())
    17. {
    18. // note: 如果这里直接对迭代器进行解引用,那么会调用迭代器的operator*,返回的是一个pair的对象
    19. // 因此在这里要以下面的方式取pair的first(Key)和second(Value)
    20. //std::cout << (*it).first << ":" << (*it).second << std::endl;
    21. // 但是,我们并不喜欢上面的方式(太麻烦了),我们直接调用迭代器的operator->,返回的是pair对象的地址
    22. // 因此此时在用->就可以取到pair的first(Key)和second(Value)
    23. // 但是为了代码的可读性,编译器省略了这个->
    24. std::cout << it->first << ":" << it->second << std::endl;
    25. ++it;
    26. }
    27. std::cout << std::endl;
    28. }

    测试结果:

    current:当前的
    delete:删除
    exist:存在
    note:注意
    segmentation:分割

    从结果上面来看,map不支持插入相同的Key(map不支持键值冗余)。

    当使用 std::map 的 insert 函数插入一个键值对时,如果插入的键已经存在,则 insert 不会进行插入操作,而是保持 map 不变,返回一个迭代器指向已存在的键值对。

    具体地说,如果插入的键已经在 std::map 中存在,insert 函数将返回一个指向已存在键值对的迭代器,而不会插入新的键值对。如果插入的键是新的,则会将该键值对插入 std::map 中,并返回指向新插入键值对的迭代器。

    因此可以认为:insert有插入+修改和查找的功能;

    当插入一个未存在的Key就是插入Key+修改Valuye

    当插入一个已存在的Key,通过返回值(pair)的first可以得到这个已经存在的Key的迭代器,可以起到查找的作用。

    测试七:测试insert(2)

    统计动物的个数,利用map,如果存在该动物就++Value,不存在就直接插入即可

    1. void Test7(void)
    2. {
    3. std::mapint> get_count;
    4. std::string str[] = { "老虎", "狮子", "大熊猫", "长颈鹿", "孔雀" };
    5. srand((unsigned int)time(nullptr));
    6. for (size_t i = 0; i < 10; ++i)
    7. {
    8. std::string tmp = str[rand() % 5];
    9. std::mapint>::iterator pos = get_count.find(tmp);
    10. // 如果该动物没存在,就插入map中,并将Value赋值为1
    11. if (pos == get_count.end())
    12. {
    13. get_count.insert(make_pair(tmp,1));
    14. }
    15. // 如果该动物存在,将Value值++即可
    16. else
    17. {
    18. ++pos->second;
    19. }
    20. }
    21. auto it = get_count.begin();
    22. while (it != get_count.end())
    23. {
    24. std::cout << it->first << ":" << it->second << std::endl;
    25. ++it;
    26. }
    27. std::cout << std::endl;
    28. }

    测试结果:

    长颈鹿:2
    大熊猫:1
    孔雀:3
    老虎:2
    狮子:2

    3.2.2. operator[]

    1. /*
    2. ***value_type : pair
    3. ***key_type : The first template parameter (Key)
    4. ***mapped_type: The second template parameter (T)
    5. */
    6. pairbool> insert (const value_type& val);
    7. mapped_type& operator[] (const key_type& k);
    8. // A call to this function is equivalent to:
    9. (*((this->insert(make_pair(k,mapped_type()))).first)).second

    表达式 (*((this->insert(std::make_pair(k, mapped_type()))).first)).second 可以被理解为在 std::map 中插入一个键值对,并返回插入的键所对应的值。

    让我们逐步解释这个表达式:

    1. 首先,std::make_pair(k, mapped_type()) 使用键 k 和默认构造的值 mapped_type() (假设 mapped_type 是键 k 对应的值类型的占位符)创建一个新的键值对pair。
    2. 然后,this->insert(std::make_pair(k, mapped_type())) 将上述键值对插入到当前的 std::map 对象中。这个 insert 操作返回一个 std::pair 对象(这个pair对象的first和second分别是:iterator,bool),其中的 first 成员表示插入的迭代器位置,second 成员表示是否进行了插入操作(true 表示插入成功,false 表示键已存在)。
    3. 接着,*((this->insert(std::make_pair(k, mapped_type()))).first) 解引用插入操作返回的迭代器,即得到插入的键值对。
    4. 最后,(*((this->insert(std::make_pair(k, mapped_type()))).first)).second 取得插入的键值对的值部分,即当前插入的键所对应的值。

    这个表达式的目的是获取插入键 k 对应的值,无论是已经存在的键还是新插入的键。它的效果等同于使用 [] 运算符来访问键对应的值,例如 map[k]。通过这个表达式,可以确保插入操作执行后,可以立即获取到插入的键所对应的值,即使该键已存在

    insert已经存在的Key,会返回<之前已经存在的Key的迭代器,false>

    insert不存在的Key,会返回<插入Key的迭代器,true>

    operator[] 的功能:

    1. 插入             (如果这个Key不存在)

    2. 修改             (如果这个Key存在,修改Value)

    3. 插入 + 修改 (这个Key不存在插入,然后修改Value)

    4. 查找             (这个Key存在,且不修改Value)

    因此上面统计动物个数的代码,我们可以用operator[]实现:

    1. void Test8(void)
    2. {
    3. std::mapint> get_count;
    4. std::string str[] = { "老虎", "狮子", "大熊猫", "长颈鹿", "孔雀" };
    5. srand((unsigned int)time(nullptr));
    6. for (size_t i = 0; i < 10; ++i)
    7. {
    8. // 如果这个动物不存在那么就是: 插入Key + 修改Value
    9. // 如果这个动物存在那么就是 : 修改Value
    10. ++get_count[str[rand() % 5]];
    11. }
    12. auto it = get_count.begin();
    13. while (it != get_count.end())
    14. {
    15. std::cout << it->first << ":" << it->second << std::endl;
    16. ++it;
    17. }
    18. std::cout << std::endl;
    19. }

    我们上面的dict也可以用operator[]来实现:

    1. void Test9(void)
    2. {
    3. std::map dict;
    4. dict.insert(std::pair("current", "当前的"));
    5. dict.insert(std::pair("note", "注意"));
    6. // 但是我们一般不使用上面的方式(通过构造pair的匿名对象)
    7. // 我们一般使用std::make_pair
    8. dict.insert(std::make_pair("delete", "删除"));
    9. dict.insert(std::make_pair("segmentation", "分割"));
    10. dict.insert(std::make_pair("exist", "存在"));
    11. // 但是,我们有时候也会用operator[]完成一系列工作,利用operator[]的四个功能:
    12. /*operator[] 的功能:
    13. ***插入(如果这个key不存在)
    14. ***修改(如果这个key存在)
    15. ***插入 + 修改(这个key不存在插入,然后修改Value)
    16. ***查找 (这个Key存在,且不修改Value)
    17. */
    18. dict["step"]; // "step"这个Key不存在,直接插入Key
    19. dict["step"] = "步骤"; // "step"这个Key存在,修改Value
    20. dict["patience"] = "耐心"; // "patience"这个Key不存在,通过返回值的引用实现 插入Key + 修改Value
    21. std::cout <<"exist: " << dict["exist"] << std::endl; //注意:当要查找的Key是存在的才是查找,otherwise,就是插入了
    22. auto it = dict.begin();
    23. while (it != dict.end())
    24. {
    25. std::cout << it->first << ":" << it->second << std::endl;
    26. ++it;
    27. }
    28. std::cout << std::endl;
    29. }

    测试结果:

    exist: 存在
    current:当前的
    delete:删除
    exist:存在
    note:注意
    patience:耐心
    segmentation:分割
    step:步骤 

    有了map,我们之前遇到的一些问题,现在就简单多了,例如,下面的这道题:

    138. 随机链表的复制 - 力扣(LeetCode)

    以前我们的做法就是分为三步:

    1、遍历原表,复制原表节点,尾插到原节点的后面

    2、 根据原节点,处理random        

    a. 如果原表中节点的random为nullptr,那么新表中的节点的random也会nullptr        

    b. 根据原节点位置,得到我们需要的random

    3、建立新表,恢复原表

    而现在我们提出了新的解法:

    1、遍历原表,复制一个新表

    2、将原表和新表insert进map

    3、新表的random就是对应map的Key的random的second

    代码如下:

    1. class Solution {
    2. public:
    3. Node* copy_list(Node* head)
    4. {
    5. Node* newhead = nullptr;
    6. Node* newtail = nullptr;
    7. while(head)
    8. {
    9. if(newhead == nullptr)
    10. {
    11. newhead = newtail = new Node(head->val);
    12. }
    13. else
    14. {
    15. newtail->next = new Node(head->val);
    16. newtail = newtail->next;
    17. }
    18. head = head->next;
    19. }
    20. return newhead;
    21. }
    22. Node* copyRandomList(Node* head) {
    23. if(!head)
    24. return nullptr;
    25. // 1、建立新表
    26. Node* newhead = copy_list(head);
    27. // 2、将旧表和新表insert到node_map中
    28. // 即Key就是旧表节点,Value是新表节点
    29. std::map node_map;
    30. Node* cur = head;
    31. Node* tmp = newhead;
    32. while(cur != nullptr)
    33. {
    34. node_map[cur] = tmp;
    35. cur = cur->next;
    36. tmp = tmp->next;
    37. }
    38. // 3、通过map的Key-Value处理新表的random
    39. Node* ret = head;
    40. while(ret)
    41. {
    42. auto pos = node_map.find(ret);
    43. if(pos != node_map.end())
    44. {
    45. // 如果旧表的random == nullptr,那么新表的random也为nullptr
    46. if(pos->first->random == nullptr)
    47. {
    48. pos->second->random = nullptr;
    49. }
    50. // otherwise,新表的random就是对应map的Key的random的second
    51. else
    52. {
    53. pos->second->random = node_map[ret->random];
    54. }
    55. }
    56. ret = ret->next;
    57. }
    58. return newhead;
    59. }
    60. };

    3.3. multimap

    std::multimap 是 C++ 标准库中的容器之一,它是一个关联容器,允许存储多个相同键(允许键值冗余)的键值对。

    std::multimap 类模板提供了一种将键和值关联的方式,它所存储的键值对可以具有相同的键(Key)。这就是与 std::map 的主要区别,std::map 只允许每个键关联一个值,而 std::multimap 允许一个键关联多个值

    std::multimap 在内部使用红黑树(一种平衡二叉搜索树)数据结构来实现。这使得插入、查找和删除操作的平均时间复杂度都为对数级别(O(log n)),提供了效率较高的键值对访问。

    为什么multimap没有operator[]呢?

    std::multimap 没有提供 operator[] 运算符的原因是,operator[] 通常用于直接访问容器中特定键对应的值,而对于 std::multimap 这种允许一个键关联多个值的容器来说,使用 operator[] 并不明确应该返回哪个值。

    operator[] 用于在映射容器中使用键直接访问值,例如对于 std::map,可以使用 map[key] 来获取键 key 对应的值(因为对于std::map中的operator[]是键值对中的Value的引用)。但是在 std::multimap 中,由于一个键可以对应多个值,所以 operator[] 的行为就变得模糊不清。

    如果 std::multimap 提供了 operator[] 运算符,那么它将需要决定返回哪个值,这可能会引起混淆。为避免歧义,std::multimap 不提供 operator[]

    如果你想要访问 std::multimap 中特定键对应的所有值,可以使用 equal_range 函数或迭代器来获取一个表示值范围的区间,然后通过遍历该区间来访问所有的值。

    3.4. multiset

    multiset 是 C++ STL(标准模板库)中的一个容器,用于存储一组元素,允许包含相同值的元素(即允许存储相同的Key或者叫允许键值冗余)。

    multiset 是基于红黑树实现的,它类似于 set 容器,但允许存储重复的元素。这意味着你可以向 multiset 中插入多个相同的值,并且 multiset 会根据元素值的顺序自动进行排序(默认是升序)。与 set 一样,multiset 也支持快速的插入、删除和查找操作。

    multiset 容器可以方便地用于处理需要存储重复元素且有序的情况。它提供了一些有用的成员函数,如 insert(插入元素)、erase(删除元素)、find(查找元素)等,以及一些迭代器相关的操作来遍历容器中的元素。

    3.4.1. std::multiset::find

    iterator find (const value_type& val) const;

    multiset 是一种基于红黑树实现的容器,它允许存储重复的元素,并且会根据键的排序准则来自动对元素进行排序。当进行查找操作时,multiset 会使用红黑树的查找算法,找到中序遍历(LNR)第一个匹配的键。

    3.4.2. std::multiset::count

    size_type count (const value_type& val) const;

    std::multiset::count 是 C++ STL(标准模板库)中 std::multiset 容器的成员函数之一,用于计算指定键(Key)在 multiset 中的出现次数。

    std::multiset 是一种基于红黑树实现的容器,允许存储重复的元素,并且按键的排序准则自动对元素进行排序。std::multiset::count 函数接受一个参数,即要计算出现次数的键,返回该键在 multiset 中的出现次数

    测试五:

    1. void Test5(void)
    2. {
    3. std::vector<int> v{ 6, 2, 4, 3, 1, 2, 6, 4, 3, 8, 7 };
    4. std::multiset<int> s;
    5. for (auto e : v)
    6. {
    7. s.insert(e);
    8. }
    9. size_t cnt = s.count(2);
    10. if (!cnt)
    11. std::cout << "没有这个Key" << std::endl;
    12. else
    13. std::cout << "Key的cnt: " << cnt << std::endl;
    14. }

    测试结果:

    Key的cnt: 2 

    可以看到:std::multiset::count可以计算特定键(Key)的个数

    3.5. 关于map和set的练习题

    3.5.1. 前K个高频单词:

    692. 前K个高频单词 - 力扣(LeetCode)

    思路:先统计每个单词出现的个数(建立单词和单词个数的联系),题干要求,如果K相等,需要按照字典顺序排序,因此要控制比较逻辑

    第一种思路:

    1、用map统计每个单词出现的次数

    2、将map中的数据导入vector>中

    3、用标准库的sort(sort只支持随机迭代器),当然还需要我们控制比较逻辑

    第二种思路:

    1、用map统计每个单词出现的次数

    2、set,comapre> 因为set可以传递仿函数,因此我们可以通过set控制比较逻辑             

    1. //第一种思路
    2. class Solution {
    3. public:
    4. vector topKFrequent(vector& words, int k) {
    5. struct compare
    6. {
    7. bool operator()(const std::pairint>& kv1,const std::pairint>& kv2) const
    8. {
    9. return kv1.second > kv2.second || (kv1.second == kv2.second && kv1.first < kv2.first);
    10. }
    11. };
    12. // 第一步:计算单词出现的个数
    13. std::mapint> count_map;
    14. for(auto& e : words)
    15. {
    16. ++count_map[e];
    17. }
    18. // 第二步: 将数据导入vector中
    19. std::vectorint>> v(count_map.begin(),count_map.end());
    20. // 第三步: 排序,需要控制比较逻辑,个数不相等,大的在前;个数相等,按照字典顺序排序
    21. std::sort(v.begin(),v.end(),compare());
    22. std::vector ret;
    23. for(size_t i = 0; i < k; ++i)
    24. {
    25. ret.push_back(v[i].first);
    26. }
    27. return ret;
    28. }
    29. };
    30. //第二种思路:
    31. class Solution {
    32. public:
    33. vector topKFrequent(vector& words, int k) {
    34. struct compare
    35. {
    36. bool operator()(const std::pairint>& kv1,const std::pairint>& kv2) const
    37. {
    38. return kv1.second > kv2.second || (kv1.second == kv2.second && kv1.first < kv2.first);
    39. }
    40. };
    41. // 第一步:计算单词出现的个数
    42. std::mapint> count_map;
    43. for(auto& e : words)
    44. {
    45. ++count_map[e];
    46. }
    47. // set可以显示传递仿函数,利用仿函数的比较逻辑
    48. std::setint>,compare> s(count_map.begin(),count_map.end());
    49. std::vector ret;
    50. auto pos = s.begin();
    51. while(k--)
    52. {
    53. ret.push_back(pos->first);
    54. ++pos;
    55. }
    56. return ret;
    57. }
    58. };

    3.5.2.  两个数组的交集

    349. 两个数组的交集 - 力扣(LeetCode)

    找交集(需要先排序+去重):

    1、不相等,小的++

    2、相等,就是交集,同时++

    3、一个集合走完就结束

    找差集(先排序)

    1、相等,同时++

    2、不相等,小的就是差集,小的++

    3、一个集合走完了,剩下没有走完的集合的值也是差集

    1. class Solution {
    2. public:
    3. vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
    4. std::set<int> s1;
    5. // 排序 + 去重
    6. for(auto& e : nums1)
    7. s1.insert(e);
    8. std::set<int> s2(nums2.begin(),nums2.end());
    9. // 根据交集的求解方法
    10. auto pos1 = s1.begin();
    11. auto pos2 = s2.begin();
    12. std::vector<int> ret;
    13. while(pos1 != s1.end() && pos2 != s2.end())
    14. {
    15. // 小的++
    16. if(*pos1 < *pos2)
    17. ++pos1;
    18. else if(*pos1 > *pos2)
    19. ++pos2;
    20. // 相等就是交集
    21. else
    22. {
    23. ret.push_back(*pos1);
    24. ++pos1;
    25. ++pos2;
    26. }
    27. }
    28. return ret;
    29. }
    30. };

    4. 底层结构 

    4.1. AVL树

    AVL树的概念:

    AVL树是一种自平衡的二叉搜索树,它以其发明者 G.M.Adelson-Velsky 和 E.M.Landis 的名字命名。AVL树保持树的左右子树高度的平衡,使得树的整体高度相对较小,提供了快速的查找、插入和删除操作。

    在AVL树中,每个节点都包含一个键值对,且满足以下性质:

    1. 左子树中的所有节点的键都小于该节点的键。
    2. 右子树中的所有节点的键都大于该节点的键。
    3. 每个节点的左子树和右子树的高度差(平衡因子)最多为 1。

    为了维持这种平衡,AVL树在每次插入或删除节点时会根据需要进行旋转操作。旋转操作包括左旋和右旋,并通过重新分配节点的位置使得树重新平衡。这种自平衡的机制保证了AVL树的高度始终保持在较小的范围内,时间复杂度为O(log n)。

    相对于其他平衡二叉搜索树(如红黑树),AVL树的平衡因子要求更为严格,不允许有任何节点的平衡因子绝对值超过1。这使得AVL树的修改操作更加频繁,但在查询操作上性能优于红黑树。

    总结来说,AVL树是一种自平衡二叉搜索树,通过保持树的左右子树高度的平衡来提供快速的查找、插入和删除操作。它在每个节点上维护了平衡因子,并通过旋转操作来保持树的平衡性。这种自平衡的特性使得AVL树在一些对插入和删除操作要求较频繁的场景中具有优势。

    AVL树的性质:

    AVL树具有以下几个性质:

    1. 二叉搜索树性质:AVL树是一种二叉搜索树,即满足以下性质:

      • 左子树中的所有节点的键都小于该节点的键。
      • 右子树中的所有节点的键都大于该节点的键。
      • 左右子树都是二叉搜索树。
    2. 平衡性:AVL树的关键特点是保持平衡,即每个节点的左子树和右子树的高度差(平衡因子)最多为 1,确保了树的整体高度相对较小。

    3. 平衡因子:每个节点都有一个平衡因子,定义为右子树的高度减去左子树的高度(或左子树的高度减去右子树的高度)。平衡因子可为 -1、0 或 1。

    4. 自平衡:当进行插入或删除操作导致AVL树不再平衡时,AVL树会通过旋转操作来恢复平衡。旋转操作包括左旋和右旋以及左右双旋和右左双旋,并通过重新分配节点的位置调整树的结构,使得树重新达到平衡状态。

    5. 高效性:由于AVL树的平衡性,其高度相对较小,从而保证了查找、插入和删除操作的平均时间复杂度为 O(log n)。

    综上所述,AVL树是一种平衡二叉搜索树,具有二叉搜索树的性质,同时通过自平衡保持平衡性。它的平衡性和高效性使得它在某些场景中具有优势。

    注意:AVL树不一定有平衡因子(balance factor),我们在这里使用平衡因子只是它的一种实现方式,在这里我们的平衡因子 =  右子树的高度 - 左子树的高度

    如下图所示:这就是一颗AVL树

    我们在这里实现的AVL树采用三叉链的形式,大致框架如下:

    1. namespace Xq
    2. {
    3. template<class K,class V>
    4. struct avl_tree_node
    5. {
    6. // 采用三叉链的形式
    7. avl_tree_node* _left;
    8. avl_tree_node* _right;
    9. avl_tree_node* _parent;
    10. std::pair _kv;
    11. int _bf; // balance factor
    12. avl_tree_node(const std::pair& kv = std::pair())
    13. :_left(nullptr)
    14. ,_right(nullptr)
    15. ,_parent(nullptr)
    16. ,_kv(kv)
    17. ,_bf(0)
    18. {}
    19. };
    20. template<class K,class V>
    21. class avl_tree
    22. {
    23. private:
    24. typedef avl_tree_node Node;
    25. public:
    26. avl_tree(Node* root = nullptr):_root(root){}
    27. bool insert(const std::pair& kv){}
    28. private:
    29. private:
    30. Node* _root;
    31. };
    32. }

    4.1.1 AVL树的插入

    AVL树的插入元素,也符合普通二叉搜索树的原则,找到合适的位置进行插入元素

    我们可以将AVL树的insert分为三个过程:

    1、在合适位置插入元素

    2、更新平衡因子

    3、如果平衡因子 > 1,需要进行旋转处理

    4.1.1.1 平衡因子的更新

    1、如果更新完以后,平衡因子没有出现问题(平衡因子的绝对值 |bf|<= 1),那么说明插入对AVL树的平衡结构不影响,不需要处理

    2、如果更新的过程中,平衡因子出现问题(平衡因子的绝对值大于1),平衡结构受到影响,需要停止更新,并旋转处理;

    旋转分为四种:左旋、右旋、左右旋、右左旋

    插入新增节点,会影响祖先的bf(全部或者部分祖先)。

    平衡因子的更新有三种情况

    那么,什么决定了平衡因子是否要继续往上更新?取决于parent的所在的子树高度(即左右子树高度的较大值)是否变化?如果变了(例如会影响祖先的平衡因子(全部或部分))继续更新,不变则不再更新

    cur是新增节点

    如果 cur == parent->right  那么父亲parent的平衡因子bf++

    如果 cur == parent->left    那么父亲parent的平衡因子bf--

    注意:插入之前我们需要保证原树是一颗AVL树,因此插入之前所有节点的|bf| <= 1

    第一种情况: parent->bf == 1 || parent->bf == -1

    -> 说明parent所在的子树的高度变了,继续向上更新,为什么?-> 因为插入元素之后parent的bf==1或者bf ==-1,那么说明插入之前parent->bf == 0,说明原左右子树的高度相等,现在有一边的子树高度高1,说明parent一边高一边低,高度变了,继续向上更新。

    如图所示:

    第二种情况:parent->bf == 0

    说明parent所在的子树高度不变,不用继续往上更新,这一次插入了就结束 ---> 插入之前parent->bf == 1 || parent->bf == -1,说明插入之前一边高一边低,新增节点插入在矮的那边,插入之后,左右子树高度相同,parent的高度不变,因此平衡因子不用向上更新了,插入结束。

    如图所示:

    第三种情况:parent->bf == -2 || parent->bf == 2

    说明插入新增节点后parent的所在的子树不平衡(|bf| >= 2),因此需要停止向上更新,处理这颗子树

    如何处理 --- 旋转处理,处理完插入就结束。

    如图所示:

    平衡因子的更新的代码框架如下:

    1. while (parent)
    2. {
    3. if (cur == parent->_left)
    4. --parent->_bf;
    5. else
    6. ++parent->_bf;
    7. //case 1: 继续向上更新
    8. if (parent->_bf == 1 || parent->_bf == -1)
    9. {
    10. cur = parent;
    11. parent = parent->_parent;
    12. continue;
    13. }
    14. // case 2: 符合AVL树,结束更新
    15. else if (parent->_bf == 0)
    16. {
    17. break;
    18. }
    19. // case 3: 当前子树出现问题了(|bf| >= 2),需要停止更新,处理当前AVL子树,处理后,插入结束
    20. else if (parent->_bf == 2 || parent->_bf == -2)
    21. {
    22. // 旋转处理:
    23. break;
    24. }
    25. // case 4:非法情况,说明前面的AVL树不符合规则
    26. else
    27. {
    28. // 直接断死
    29. assert(false);
    30. }
    31. }
    4.1.1.2. AVL树的旋转

    旋转操作是为了保持或恢复AVL树的平衡性。

    AVL树的平衡在于每个节点的左子树和右子树的高度差(平衡因子)最多为 1。当进行插入或删除操作后,可能会打破原本的平衡性,导致某个节点的平衡因子超过了允许的范围。

    为了恢复平衡,AVL树采用不同类型的旋转操作,包括左旋和右旋。旋转操作通过重新分配节点的位置,使得树重新达到平衡状态,确保每个节点的平衡因子保持在允许的范围内。

    旋转操作的具体目的如下:
    1. 左旋:当一个节点的右子树高度大于左子树高度时,进行左旋操作。左旋将当前节点和其右子节点进行交换,使得原先的右子节点成为新的根节点,同时保证原来左子树不变、右子节点的左子树作为新的右子树,从而降低了树的高度差。
    2. 右旋:当一个节点的左子树高度大于右子树高度时,进行右旋操作。右旋将当前节点和其左子节点进行交换,使得原先的左子节点成为新的根节点,同时保证原来右子树不变、左子节点的右子树作为新的左子树,从而降低了树的高度差。

    通过旋转操作,AVL树可以在插入或删除节点后自动调整自己,降低AVL树的高度,维持其平衡结构 ,提供更高效的查找、插入和删除操作。

    总结来说,旋转操作的目的是为了保持或恢复AVL树的平衡性,通过重新分配节点的位置降低树的高度差,确保每个节点的平衡因子在允许范围内。这样可以提供更好的性能和效率。

    旋转处理的情况分为四种:左单旋、右单旋、左右双旋、右左双旋

    旋转的原则:旋转后仍是一颗AVL树

    旋转的目的:左右均衡,降低整棵AVL树的高度

    我们依次来看:

    4.1.1.2.1. 左单旋

    如下图所示:

    当h==0时,情况如下:

    当h==1时,情况如下: 

    当h == 2时,情况如下: 

    上面列出了三种情况,当然远远不止,虽然有很多种情况,但是对于AVL树的左单旋的处理方式是固定的:

    只要满足 cur->bf == 2 && cur_right->bf == 1那么就对cur进行左单旋

    旋转后将cur和cur_right的平衡因子置为0即可

    具体如下图所示:

    有了上面的分析,我们可以得出结论:

    当cur->bf == 2 && cur_right->bf == 1那么就对cur进行左单旋

    旋转后更新平衡因子cur->bf = 0; cur_right->bf = 0;

    因此,我们的左单旋实现如下 :

    为了更好地实现左单旋,我们借助下面的图来实现

    1. void left_rotate(Node* parent)
    2. {
    3. // 确立四个节点的初始位置
    4. Node* cur = parent;
    5. Node* cur_right = cur->_right;
    6. Node* cur_right_left = cur_right->_left;
    7. Node* cur_parent = cur->_parent;
    8. cur->_right = cur_right_left;
    9. // 当h == 0时,cur_right_left是为空的,因此在这里要判断一下
    10. if (cur_right_left)
    11. cur_right_left->_parent = cur;
    12. cur_right->_left = cur;
    13. cur->_parent = cur_right;
    14. // 如果parent是根节点,那么cur_right就是新根
    15. if (!cur_parent)
    16. {
    17. cur_right->_parent = nullptr;
    18. _root = cur_right;
    19. }
    20. // 如果parent不是根节点,那么cur_parent不为空
    21. else
    22. {
    23. if (cur_parent->_kv.first > cur_right->_kv.first)
    24. {
    25. cur_parent->_left = cur_right;
    26. }
    27. else
    28. {
    29. cur_parent->_right = cur_right;
    30. }
    31. cur_right->_parent = cur_parent;
    32. }
    33. // 更新cur和cur_right的平衡因子
    34. cur_right->_bf = cur->_bf = 0;
    35. }

    4.1.1.2.2. 右单旋

    对于右单旋来说,分析思路与左单旋差别不大,只不过右单旋是单纯的左边高,进行右单旋,在这里只以h==1的具象图和抽象图用以举例说明

     当h==1时,如下图所示:

    右单旋的抽象图,如下图所示:

    与左单旋同样,虽然右单旋会有很多种情况,但是它们的处理方式是一样的,只要满足

    cur->bf == -2 && cur_left->bf == -1 就对cur进行右单旋,旋转玩后将cur和cur_left的bf更新为0,旋转结束。

    有了上面的分析,我们可以得出结论:

    当cur->bf == -2 && cur_left->bf == -1那么就对cur进行右单旋

    旋转后更新平衡因子cur->bf = 0; cur_left->bf = 0;

    因此,我们的右单旋实现如下 :

    为了更好地实现右单旋,我们借助下面的图来实现:

    1. void right_rotate(Node* parent)
    2. {
    3. // 确立四个节点的初始位置
    4. Node* cur = parent;
    5. Node* cur_left = cur->_left;
    6. Node* cur_left_right = cur_left->_right;
    7. Node* cur_parent = cur->_parent;
    8. cur->_left = cur_left_right;
    9. // 当h == 0时,cur_left_right为空,因此在这里要判断一下
    10. if (cur_left_right)
    11. cur_left_right->_parent = cur;
    12. cur_left->_right = cur;
    13. cur->_parent = cur_left;
    14. // 如果cur_parent为空,那么cur_left就是新根
    15. if (!cur_parent)
    16. {
    17. cur_left->_parent = nullptr;
    18. _root = cur_left;
    19. }
    20. else
    21. {
    22. // 在这里需要判断一下kv.first的大小,以确定cur_left是左孩子还是右孩子
    23. if (cur_parent->_kv.first > cur_left->_kv.first)
    24. {
    25. cur_parent->_left = cur_left;
    26. }
    27. else
    28. {
    29. cur_parent->_right = cur_left;
    30. }
    31. // 最后也要链接父亲
    32. cur_left->_parent = cur_parent;
    33. }
    34. // 更新平衡因子
    35. cur->_bf = cur_left->_bf = 0;
    36. }
    4.1.1.2.3. 左右双旋

    左右双旋:‘例如这种形状<’,整体看是左边高,但是左子树又是右边高

    先对左子树进行左单旋、在对整体进行右单旋。

    先左单旋的目的是:让这棵AVL子树变成单纯的左边高,在进行右单旋

    如图所示:

    当h == 0时,如下图所示 

    当 h == 1时,如下图说式:

    左右双旋的抽象图:

    将上面的图联系到一起,我们可以发现,他们的旋转方式和旋转条件是一样的。

    旋转条件: cur->bf == -2 && cur_left->bf == 1  (对应到上图:(100就是cur,50就是cur_left) )

    旋转方式:先对左子树进行左单旋,在对整体进行右单旋。

    但是最后的平衡因子的更新却不一样,可以分为三种情况

    当插入元素后:

    case 1:cur_left_right->bf == 0

    旋转后:cur->bf = 0; cur_left->bf = 0; cur_left_right->_bf = 0;

    case 2:cur_left_right->bf == -1

    旋转后:cur->bf = 1; cur_left->bf = 0; cur_left_right->_bf = 0;

    case 3:cur_left_right->bf == 1

    旋转后:cur->bf = 0; cur_left->bf = -1; cur_left_right->_bf = 0;

    1. void left_right_rotate(Node* parent)
    2. {
    3. Node* cur = parent;
    4. Node* cur_left = cur->_left;
    5. Node* cur_left_right = cur_left->_right;
    6. int bf = cur_left_right->_bf;
    7. // 先左旋、后右旋
    8. left_rotate(cur_left);
    9. right_rotate(cur);
    10. if (bf == 0)
    11. {
    12. cur->_bf = 0;
    13. cur_left->_bf = 0;
    14. cur_left_right->_bf = 0;
    15. }
    16. else if (bf == -1)
    17. {
    18. cur->_bf = 1;
    19. cur_left->_bf = 0;
    20. cur_left_right->_bf = 0;
    21. }
    22. else if (bf == 1)
    23. {
    24. cur->_bf = 0;
    25. cur_left->_bf = -1;
    26. cur_left_right->_bf = 0;
    27. }
    28. else
    29. {
    30. // 非法情况,直接断死
    31. assert(false);
    32. }
    33. }
    4.1.1.2.4. 右左双旋

    右左双旋的分析思路和左右双旋没有太大差异。在这里只以h == 0 和 对应的抽象图举例分析其中细节。

    如图所示,这就是右左双旋的抽象图:

    当h == 0时的具象图,如下图所示

     剩下两种情况的抽象图,如下图所示:

     将上面的图联系到一起,我们可以发现,他们的旋转方式和旋转条件是一样的。

    旋转条件: cur->bf == 2 && cur_right->bf == -1 (对应到上图:(50就是cur,100就是cur_right)) 

    旋转方式:先对右子树进行右单旋,在对整体进行左单旋。

    但是最后的平衡因子的更新却不一样,可以分为三种情况

    当插入元素后:

    case 1:cur_right_left->bf == 0

    旋转后:cur->bf = 0;cur_right->bf = 0; cur_right_left->bf = 0;

    case 2:cur_right_left->bf == -1

    旋转后:cur->bf = 0;cur_right->bf = 1; cur_right_left->bf = 0;

    case 3:cur_right_left->bf == 1

    旋转后:cur->bf = -1;cur_right->bf = 0; cur_right_left->bf = 0;

    1. void right_left_rotate(Node* parent)
    2. {
    3. Node* cur = parent;
    4. Node* cur_right = cur->_right;
    5. Node* cur_right_left = cur_right->_left;
    6. int bf = cur_right_left->_bf;
    7. right_rotate(cur_right);
    8. left_rotate(cur);
    9. if (bf == 0)
    10. {
    11. cur->_bf = 0;
    12. cur_right->_bf = 0;
    13. cur_right_left->_bf = 0;
    14. }
    15. else if (bf == -1)
    16. {
    17. cur->_bf = 0;
    18. cur_right->_bf = 1;
    19. cur_right_left->_bf = 0;
    20. }
    21. else if (bf == 1)
    22. {
    23. cur->_bf = -1;
    24. cur_right->_bf = 0;
    25. cur_right_left->_bf = 0;
    26. }
    27. else
    28. {
    29. // 非法情况,直接断死
    30. assert(false);
    31. }
    32. }
     4.1.1.3. 验证AVL树的插入

    如何验证:

    我们需要验证每一棵AVL子树的左右子树高度差是否小于等于1,且要判断每个节点的平衡因子是否等于当前节点的左右子树的高度差

    代码实现:

    1. int _get_tree_high(Node* root)
    2. {
    3. if (!root)
    4. return 0;
    5. else
    6. {
    7. int left_high = _get_tree_high(root->_left);
    8. int right_high = _get_tree_high(root->_right);
    9. return left_high > right_high ? ++left_high : ++right_high;
    10. }
    11. }
    12. bool _is_balance_tree(Node* root)
    13. {
    14. // 空树可以认为是AVL树
    15. if (!root)
    16. return true;
    17. else
    18. {
    19. int left_high = get_tree_high(root->_left);
    20. int right_high = get_tree_high(root->_right);
    21. // 如果当前节点的平衡因子不等于当前节点的左右子树的高度差,说明异常
    22. if (right_high - left_high != root->_bf)
    23. {
    24. std::cout << root->_kv.first << " : 该节点的平衡因子出现异常" << std::endl;
    25. return false;
    26. }
    27. // 计算每颗AVL子树的左右子树高度差,如果存在大于1的情况,说明异常
    28. int bf = right_high - left_high;
    29. if (bf < 0)
    30. bf *= -1;
    31. return bf <= 1
    32. && _is_balance_tree(root->_left)
    33. && _is_balance_tree(root->_right);
    34. }
    35. }
    4.1.1.4. AVL树插入的完整实现
    1. #pragma once
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. namespace Xq
    9. {
    10. template<class K,class V>
    11. struct avl_tree_node
    12. {
    13. avl_tree_node* _left;
    14. avl_tree_node* _right;
    15. avl_tree_node* _parent;
    16. std::pair _kv;
    17. int _bf; // balance factor
    18. avl_tree_node(const std::pair& kv = std::pair())
    19. :_left(nullptr)
    20. ,_right(nullptr)
    21. ,_parent(nullptr)
    22. ,_kv(kv)
    23. ,_bf(0)
    24. {}
    25. };
    26. template<class K,class V>
    27. class avl_tree
    28. {
    29. private:
    30. typedef avl_tree_node Node;
    31. public:
    32. avl_tree(Node* root = nullptr):_root(root){}
    33. bool insert(const std::pair& kv)
    34. {
    35. if(_root == nullptr)
    36. {
    37. _root = new Node(kv);
    38. return true;
    39. }
    40. else
    41. {
    42. Node* cur = _root;
    43. Node* parent = nullptr;
    44. while(cur)
    45. {
    46. if(cur->_kv.first > kv.first)
    47. {
    48. parent = cur;
    49. cur = cur->_left;
    50. }
    51. else if(cur->_kv.first < kv.first)
    52. {
    53. parent = cur;
    54. cur = cur->_right;
    55. }
    56. else
    57. {
    58. return false;
    59. }
    60. }
    61. cur = new Node(kv);
    62. if(kv.first > parent->_kv.first)
    63. {
    64. parent->_right = cur;
    65. cur->_parent = parent;
    66. }
    67. else
    68. {
    69. parent->_left = cur;
    70. cur->_parent = parent;
    71. }
    72. // 调整平衡因子
    73. while(parent)
    74. {
    75. if(cur == parent->_left)
    76. --parent->_bf;
    77. else
    78. ++parent->_bf;
    79. //case 1: 继续向上更新
    80. if(parent->_bf == 1 || parent->_bf == -1)
    81. {
    82. cur = parent;
    83. parent = parent->_parent;
    84. continue;
    85. }
    86. // case 2: 符合AVL树,结束更新
    87. else if(parent->_bf == 0)
    88. {
    89. break;
    90. }
    91. // case 3: 当前子树出现问题了(|bf| >= 2),需要停止更新,处理当前AVL子树,处理后,插入结束
    92. else if(parent->_bf == 2 || parent->_bf == -2)
    93. {
    94. // 旋转处理:
    95. //case 1: 左单旋
    96. if(parent->_bf == 2 && parent->_right->_bf == 1)
    97. {
    98. left_rotate(parent);
    99. }
    100. //case 2: 右单旋
    101. else if(parent->_bf == -2 && parent->_left->_bf == -1)
    102. {
    103. right_rotate(parent);
    104. }
    105. //case 3:左右双旋
    106. else if(parent->_bf == -2 && parent->_left->_bf == 1)
    107. {
    108. left_right_rotate(parent);
    109. }
    110. //case 4:右左双旋
    111. else if(parent->_bf == 2 && parent->_right->_bf == -1)
    112. {
    113. right_left_rotate(parent);
    114. }
    115. //非法情况,断死
    116. else
    117. {
    118. assert(false);
    119. }
    120. break;
    121. }
    122. // case 4:非法情况,说明前面的AVL树不符合规则
    123. else
    124. {
    125. // 直接断死
    126. assert(false);
    127. }
    128. }
    129. return true;
    130. }
    131. }
    132. void left_rotate(Node* parent)
    133. {
    134. // 1. 确立四个节点的初始位置
    135. Node* cur = parent;
    136. Node* cur_right = cur->_right;
    137. Node* cur_right_left = cur_right->_left;
    138. Node* cur_parent = cur->_parent;
    139. cur->_right = cur_right_left;
    140. // 当h == 0时,cur_right_left是为空的,因此在这里要判断一下
    141. if(cur_right_left)
    142. cur_right_left->_parent = cur;
    143. cur_right->_left = cur;
    144. cur->_parent = cur_right;
    145. // 如果parent是根节点,那么cur_right就是新根
    146. if(!cur_parent)
    147. {
    148. cur_right->_parent = nullptr;
    149. _root = cur_right;
    150. }
    151. // 如果parent不是根节点,那么cur_parent不为空
    152. else
    153. {
    154. if(cur_parent->_kv.first > cur_right->_kv.first)
    155. {
    156. cur_parent->_left = cur_right;
    157. }
    158. else
    159. {
    160. cur_parent->_right = cur_right;
    161. }
    162. cur_right->_parent = cur_parent;
    163. }
    164. cur->_bf = cur_right->_bf = 0;
    165. }
    166. void right_rotate(Node* parent)
    167. {
    168. // 确立四个节点的初始位置
    169. Node* cur = parent;
    170. Node* cur_left = cur->_left;
    171. Node* cur_left_right = cur_left->_right;
    172. Node* cur_parent = cur->_parent;
    173. cur->_left = cur_left_right;
    174. // 当h == 0时,cur_left_right为空,因此在这里要判断一下
    175. if(cur_left_right)
    176. cur_left_right->_parent = cur;
    177. cur_left->_right = cur;
    178. cur->_parent = cur_left;
    179. // 如果cur_parent为空,那么cur_left就是新根
    180. if(!cur_parent)
    181. {
    182. cur_left->_parent = nullptr;
    183. _root = cur_left;
    184. }
    185. else
    186. {
    187. if(cur_parent->_left == cur)
    188. {
    189. cur_parent->_left = cur_left;
    190. }
    191. else
    192. {
    193. cur_parent->_right = cur_left;
    194. }
    195. cur_left->_parent = cur_parent;
    196. }
    197. // 更新平衡因子
    198. cur->_bf = cur_left->_bf = 0;
    199. }
    200. void left_right_rotate(Node* parent)
    201. {
    202. Node* cur = parent;
    203. Node* cur_left = cur->_left;
    204. Node* cur_left_right = cur_left->_right;
    205. int bf = cur_left_right->_bf;
    206. // 先左旋、后右旋
    207. left_rotate(cur_left);
    208. right_rotate(cur);
    209. if(bf == 0)
    210. {
    211. cur->_bf = 0;
    212. cur_left->_bf = 0;
    213. cur_left_right->_bf = 0;
    214. }
    215. else if(bf == -1)
    216. {
    217. cur->_bf = 1;
    218. cur_left->_bf = 0;
    219. cur_left_right->_bf = 0;
    220. }
    221. else if(bf == 1)
    222. {
    223. cur->_bf = 0;
    224. cur_left->_bf = -1;
    225. cur_left_right->_bf = 0;
    226. }
    227. else
    228. {
    229. // 非法情况,直接断死
    230. assert(false);
    231. }
    232. }
    233. void right_left_rotate(Node* parent)
    234. {
    235. Node* cur = parent;
    236. Node* cur_right = cur->_right;
    237. Node* cur_right_left = cur_right->_left;
    238. int bf = cur_right_left->_bf;
    239. right_rotate(cur_right);
    240. left_rotate(cur);
    241. if(bf == 0)
    242. {
    243. cur->_bf = 0;
    244. cur_right->_bf = 0;
    245. cur_right_left->_bf = 0;
    246. }
    247. else if(bf == -1)
    248. {
    249. cur->_bf = 0;
    250. cur_right->_bf = 1;
    251. cur_right_left->_bf = 0;
    252. }
    253. else if(bf == 1)
    254. {
    255. cur->_bf = -1;
    256. cur_right->_bf = 0;
    257. cur_right_left->_bf = 0;
    258. }
    259. else
    260. {
    261. // 非法情况,直接断死
    262. assert(false);
    263. }
    264. }
    265. void level_order()
    266. {
    267. _level_order(_root);
    268. }
    269. int get_tree_high(Node* root)
    270. {
    271. return _get_tree_high(root);
    272. }
    273. bool is_balance_tree()
    274. {
    275. return _is_balance_tree(_root);
    276. }
    277. int in_outside_get_tree_high()
    278. {
    279. return _get_tree_high(_root);
    280. }
    281. private:
    282. void _level_order(Node* root)
    283. {
    284. if(!root)
    285. return ;
    286. else
    287. {
    288. std::queue qu;
    289. qu.push(root);
    290. while(!qu.empty())
    291. {
    292. Node* front = qu.front();
    293. qu.pop();
    294. if(front)
    295. {
    296. qu.push(front->_left);
    297. qu.push(front->_right);
    298. }
    299. if(!front)
    300. std::cout << "N ";
    301. else
    302. std::cout << front->_kv.first << " ";
    303. }
    304. std::cout << std::endl;
    305. }
    306. }
    307. int _get_tree_high(Node* root)
    308. {
    309. if(!root)
    310. return 0;
    311. else
    312. {
    313. int left_high = _get_tree_high(root->_left);
    314. int right_high = _get_tree_high(root->_right);
    315. return left_high > right_high ? ++left_high : ++right_high;
    316. }
    317. }
    318. bool _is_balance_tree(Node* root)
    319. {
    320. // 空树可以认为是AVL树
    321. if(!root)
    322. return true;
    323. else
    324. {
    325. int left_high = get_tree_high(root->_left);
    326. int right_high = get_tree_high(root->_right);
    327. if(right_high - left_high != root->_bf)
    328. {
    329. std::cout << root->_kv.first <<" : 该节点的平衡因子出现异常" << std::endl;
    330. return false;
    331. }
    332. // 计算每颗AVL子树的左右子树高度差,如果存在大于1的情况,说明异常
    333. int bf = right_high-left_high;
    334. if(bf < 0)
    335. bf *= -1;
    336. return bf <= 1
    337. && _is_balance_tree(root->_left)
    338. && _is_balance_tree(root->_right);
    339. }
    340. }
    341. private:
    342. Node* _root;
    343. };
    344. }

    4.2. 红黑树

    红黑树是一种自平衡的二叉搜索树,它通过约束节点的颜色和结构来保持平衡。红黑树是由 Rudolf Bayer 在1972年发明的,被认为是一种优秀的平衡树结构,广泛应用于各种数据结构和算法中。

    红黑树具有以下几个性质:

    1. 二叉搜索树性质:红黑树是一种二叉搜索树,即满足以下性质:

      • 左子树中的所有节点的键都小于该节点的键。
      • 右子树中的所有节点的键都大于该节点的键。
      • 左右子树都是二叉搜索树。
    2. 节点颜色性质:每个节点被标记为红色或黑色。

    3. 根节点性质:根节点为黑色。

    4. 叶子节点(NIL节点)性质:叶子节点都为黑色的空节点(NIL节点),并被视为树的终止节点。

    5. 红色节点性质:红节点的子节点必须是黑节点,不能出现连续的红色节点。

    6. 黑节点性质:从任一节点到其每个叶子节点的路径上,经过的黑节点数量是相同的(黑色平衡性)。

    7. 通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路
      径会比其他路径长出俩倍以上,因而是接近平衡的。

    这些性质保证了红黑树的平衡性和有序性。红黑树通过在插入和删除操作中进行颜色调整和旋转操作来维护平衡性。与AVL树相比,红黑树对于平衡性的要求相对宽松,因此插入和删除操作的平衡调整(旋转)相对较少。

    由于红黑树的平衡性,其高度相对较小,平均时间复杂度为 O(log n),提供了快速的查找、插入和删除操作。红黑树在各种应用中被广泛使用,例如C++ STL中的map和set容器,以及各种数据库、编译器和操作系统的实现

    总结来说,红黑树是一种具有自平衡性质的二叉搜索树,通过约束节点的颜色和结构来保持平衡。它具有二叉搜索树的性质,并通过节点颜色、根节点性质、叶子节点性质、红色节点性质和黑节点性质来维护平衡性。红黑树提供了高效的查找、插入和删除操作,被广泛应用于不同领域的数据结构和算法中。

    思考 :为什么红黑树可以保证任何一条路径不会比其他路径长出两倍以上呢?

    首先我们要知道什么是路径?

    路径是从根节点开始,沿着树的分支一直走到达叶子节点。注意:这里的叶子节点特指为空节点,在这里一般用NIL节点表示空节点,且NIL节点是黑色的。

    对于一棵红黑树而言,它的最短路径和最长路径分别为:

    最短路径:就是这条路径上的节点是全黑的。

    最长路径:该路径是由一黑一红连续组成的。

    红黑树的性质告诉我们,每条路径上的黑色节点个数是一定的,且没有连续的红色节点

    那么我们假设最短路径的黑色节点个数为N

    那么最长路径的黑色节点个数也为N,其红色节点个数最多也为N,那么最长路径的节点个数为2N

    因此我们可以得出,红黑树的最长路径可以是最短路径的二倍,但是不会超过二倍, 因此红黑树的任意一条路径的节点个数不可能比其他任何一条路径长过两倍以上,以达到近似平衡的状态。

    综上所述,红黑树通过对节点的着色和结构的限制,确保了树的高度相对较小,从而保证了没有一条路径会比其他路径长出两倍以上。这种限制确保红黑树保持近似平衡的状态,从而提供了较好的性能。当插入、删除或查找操作发生时,红黑树能够在O(logN)的时间内完成操作,其中N是树中节点的数量。最长路径接近最短路径长度的情况下,红黑树的高度相对较小,保证了快速的操作效率。

    4.2.1. 红黑树的插入

    如下图所示,这就是一颗红黑树

     第一个问题,如果我们现在要插入一个新节点,我们是把这个新节点的初始颜色设置为黑色还是红色呢???

    假设我把新增节点设置为黑色,那么就会有下面的场景:

    我们发现,此时就出现了问题,插入了5之后,这棵树还是红黑树吗?抱歉,根据红黑树的性质:每个路径上的黑色节点个数必须相等,我们可以知道,此时这棵树已经不是红黑树了。

    但如果我将新增节点的初始颜色设置为红色呢,又会出现什么情况?如下图所示:

     我们发现,当我们把新增节点的初始颜色设置为红色的时候,插入之后会有两种情况:

    第一种情况:插入之后违反了红黑树的规则,即不能出现连续的红色节点。

    第二种情况:插入之后没有违反任何规则。

    综上所述,我们将新增节点的初始颜色设置为红色。因为如果新增节点是黑色,那么一定会违反红黑树的规则,反之,如果是红色,则可能插入之后不违反任何规则,因此我们将新增节点设置为红色。

    严格意义上讲,红黑树的插入会有五种情况,让我们一一进行理解。

    4.2.1.1. 情况一:新增节点的父亲为空

    这种情况最为简单,就是当是一颗空树的时候,直接插入即可。并将新增节点的颜色更新为黑即可。

    4.2.1.2. 情况二:新增节点的父亲非空且为黑色节点

    这种情况也很简单,直接插入红色节点即可,不会破环红黑树的规则。

    4.2.1.3. 情况三:当父亲为红节点,叔叔存在且为红

    当新增节点的父亲节点和叔叔节点非空且为红时,变色即可。

    因为parent为红,那么它一定不是根节点,因此它一定有父亲节点,即grandfather一定不为空。

    声明:

    g --- grandfather(祖父节点 )  p --- parent(父节点)  u --- uncle(叔叔节点)  c --- cur(新增节点)

    变色规则:p、u变黑,g变红

    如图所示:

     1、当祖父为根节点的时候

    2、 当祖父不是根节点的时候

    总结,当p、u为红的时候,那么将p、u变黑、将g变红,c = g; p = c->parent,继续判断,如果符合前面的条件(p、u为红)继续向上更新,最后为了避免不同的情况,将根节点的颜色更新为黑即可。

     4.2.1.4. 情况四:当父亲为红节点,叔叔不存在或者存在为黑

    父亲为红、叔叔不存在或者存在为黑,且g、p、c在同一条直线上,单旋处理,处理完,插入结束。

    因为parent为红,那么它一定不是根节点,因此它一定有父亲节点,即grandfather一定不为空。

    单旋非为左单旋和右单旋两种情况,具体操作如图所示:

    其中,a、b、c、d、e代表红黑树子树

    总结:当g、p、c在同一条直线上,就进行单旋,虽然有两种情况,但是最后它们的变色情况是一致的,p:由红变黑 g:由黑变红

    4.2.1.5. 情况五:父亲为红、叔叔不存在或者为黑

    父亲为红、叔叔不存在或者存在为黑,且g、p、c不在同一条直线上,需要双旋处理,处理完,插入结束。

    因为parent为红,那么它一定不是根节点,因此它一定有父亲节点,即grandfather一定不为空。

    双旋非为左右双旋和右左双旋两种情况,具体操作如图所示:

    其中,a、b、c、d、e代表红黑树子树

     总结:当g、p、c不在同一条直线上(一条折线就是双旋),就进行双旋,虽然有两种情况,但是最后它们的变色情况是一致的,c由红变黑  g由黑变红  p不变,保持红色

     4.2.1.5. 验证红黑树的插入

     思路:只要满足下面的所有条件就是红黑树

    <1> 第一个条件:

    根节点是黑色的

    <2> 第二个条件:

    红色的节点的左右子树都必须是黑色的,也就是说父子节点只能有一个红色节点

    思路: 只要某节点是红色的,那么只要判断它的父亲即可,如果父亲是红色的,那么就不是红黑树,反之,则符合红黑树

    <3> 第三个条件:

    每一条路径的黑色节点是相等的。

    路径:从根节点到"叶子节点"

    注意:这里的叶子节点不是传统意义上的叶子节点,在这里把空节点当作叶子节点

    步骤一: 先求一条路径上的黑色节点个数作为基准值;

    步骤二: 用该基准值分别于其他所有路径的黑色节点个数进行比较;

    1. bool is_balance_tree()
    2. {
    3. // 检查根节点
    4. if (_root->_col != BLACK)
    5. {
    6. std::cout << "根节点是红色,异常" << std::endl;
    7. return false;
    8. }
    9. return _is_balance_tree(_root);
    10. }
    11. bool _is_balance_tree(Node* root)
    12. {
    13. if (!root)
    14. return true;
    15. else
    16. {
    17. // basic_value作为这颗红黑树的黑色节点个数的基准值
    18. int basic_value = _get_black_node_num(root);
    19. return _is_check_rb_tree(root, 0, basic_value);
    20. }
    21. }
    22. bool _is_check_rb_tree(Node* root, int black_num, int basic_value)
    23. {
    24. // basic_value: 红黑树的一条路径中的黑色节点个数,在这里作为基准值
    25. if (root == nullptr)
    26. {
    27. if (black_num != basic_value)
    28. {
    29. std::cout << "某条路径中的黑色节点个数不相等,异常" << std::endl;
    30. return false;
    31. }
    32. else
    33. return true;
    34. }
    35. else
    36. {
    37. if (root->_col == BLACK)
    38. ++black_num;
    39. // 如果一个节点是红色的,那么该节点一定不是根,因此一定有父亲
    40. // 如果这个节点的父亲也是红色的,那么就说明出现异常
    41. if (root->_col == RED && root->_parent->_col == RED)
    42. {
    43. std::cout << "child: " << root->_kv.first << "parent: " << root->_parent->_kv.first << "出现了连续的红色节点,异常" << std::endl;
    44. return false;
    45. }
    46. return _is_check_rb_tree(root->_left, black_num, basic_value)
    47. && _is_check_rb_tree(root->_right, black_num, basic_value);
    48. }
    49. }
    50. int _get_black_node_num(Node* root)
    51. {
    52. if (root == nullptr)
    53. return 0;
    54. else
    55. {
    56. int ret = 0;
    57. while (root)
    58. {
    59. if (root->_col == BLACK)
    60. ++ret;
    61. root = root->_left;
    62. }
    63. return ret;
    64. }
    65. }

    4.2.1.6.  rb_tree插入的完整实现
    1. #pragma once
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. namespace Xq
    9. {
    10. enum color
    11. {
    12. RED,
    13. BLACK
    14. };
    15. template<class K, class V>
    16. struct rb_tree_node
    17. {
    18. rb_tree_node* _left;
    19. rb_tree_node* _right;
    20. rb_tree_node* _parent;
    21. std::pair _kv;
    22. color _col;
    23. rb_tree_node(const std::pair& kv = std::pair())
    24. :_left(nullptr)
    25. , _right(nullptr)
    26. , _parent(nullptr)
    27. , _kv(kv)
    28. , _col(RED)
    29. {}
    30. };
    31. template<class K, class V>
    32. class rb_tree
    33. {
    34. private:
    35. typedef rb_tree_node Node;
    36. public:
    37. rb_tree(Node* root = nullptr) :_root(root){}
    38. bool insert(const std::pair& kv)
    39. {
    40. if (!_root)
    41. {
    42. _root = new Node(kv);
    43. _root->_col = BLACK;
    44. return true;
    45. }
    46. else
    47. {
    48. Node* cur = _root;
    49. Node* parent = nullptr;
    50. while (cur)
    51. {
    52. if (cur->_kv.first > kv.first)
    53. {
    54. parent = cur;
    55. cur = cur->_left;
    56. }
    57. else if (cur->_kv.first < kv.first)
    58. {
    59. parent = cur;
    60. cur = cur->_right;
    61. }
    62. else
    63. {
    64. return false;
    65. }
    66. }
    67. cur = new Node(kv);
    68. if (kv.first > parent->_kv.first)
    69. {
    70. parent->_right = cur;
    71. }
    72. else
    73. {
    74. parent->_left = cur;
    75. }
    76. cur->_parent = parent;
    77. // 当父节点parent的颜色为红色时,需要调整
    78. while (parent && parent->_col == RED)
    79. {
    80. // 因为父节点parent->_col == RED,那么说明parent一定有父节点
    81. Node* grandfather = parent->_parent;
    82. if (parent == grandfather->_left)
    83. {
    84. Node* uncle = grandfather->_right;
    85. // case 1: 当父节点为红,且叔叔不为空且为红
    86. // Solution : 变色
    87. if (uncle && uncle->_col == RED)
    88. {
    89. parent->_col = uncle->_col = BLACK;
    90. grandfather->_col = RED;
    91. if (grandfather == _root)
    92. grandfather->_col = BLACK;
    93. // 继续向上判断,如果依旧符合case 1,继续变色
    94. cur = grandfather;
    95. parent = cur->_parent;
    96. continue;
    97. }
    98. // case 2: 父节点为红,且叔叔为空或者不为空且为黑,并且g、p、c在同一条直线上
    99. // Solution: 单旋, 在这里就是对grandfather右单旋
    100. else if (parent->_left == cur && ((!uncle) || uncle->_col == BLACK))
    101. {
    102. // g p
    103. // p u ---> c g
    104. // c u
    105. right_rotate(grandfather);
    106. parent->_col = BLACK;
    107. grandfather->_col = RED;
    108. // 旋转完,更新完颜色,调整就结束
    109. break;
    110. }
    111. // case 3:父节点为红,且叔叔为空或者不为空且为黑,并且g、p、c不在同一条直线上
    112. // Solution: 双旋,在这里就是先对p进行左单旋,在对g进行右单旋
    113. else if (parent->_right == cur && ((!uncle) || uncle->_col == BLACK))
    114. {
    115. // g 先对p进行左单旋 g 在对g进行右单旋 c
    116. // p u ---> c u ---> p g
    117. // c p u
    118. left_rotate(parent);
    119. right_rotate(grandfather);
    120. cur->_col = BLACK;
    121. grandfather->_col = RED;
    122. // 旋转完,更新完颜色,调整就结束
    123. break;
    124. }
    125. else
    126. {
    127. // 非法情况
    128. assert(false);
    129. }
    130. }
    131. // 当parent是grandfather的右孩子,那么uncle就是grandfather的左孩子
    132. else
    133. {
    134. Node* uncle = grandfather->_left;
    135. // case 1: 当父节点为红,且叔叔不为空且为红
    136. // Solution : 变色
    137. if (uncle && uncle->_col == RED)
    138. {
    139. parent->_col = uncle->_col = BLACK;
    140. grandfather->_col = RED;
    141. if (grandfather == _root)
    142. grandfather->_col = BLACK;
    143. // 继续向上判断,如果依旧符合case 1,继续变色
    144. cur = grandfather;
    145. parent = cur->_parent;
    146. continue;
    147. }
    148. // case 2: 父节点为红,且叔叔为空或者不为空且为黑,并且g、p、c在同一条直线上
    149. // Solution: 单旋, 在这里就是对grandfather左单旋
    150. else if (parent->_right == cur && ((!uncle) || uncle->_col == BLACK))
    151. {
    152. // g 对g进行左单旋 p
    153. // u p ---> g c
    154. // c u
    155. left_rotate(grandfather);
    156. parent->_col = BLACK;
    157. grandfather->_col = RED;
    158. //旋转并将颜色更新后,就退出调整
    159. break;
    160. }
    161. // case 3:父节点为红,且叔叔为空或者不为空且为黑,并且g、p、c不在同一条直线上
    162. // Solution: 双旋,在这里就是先对p进行右单旋,在对g进行左单旋
    163. else if (parent->_left == cur && ((!uncle) || uncle->_col == BLACK))
    164. {
    165. // g 先对p进行右单旋 g 在对g进行左单旋 c
    166. // u p ---> u c ---> g p
    167. // c p u
    168. right_rotate(parent);
    169. left_rotate(grandfather);
    170. cur->_col = BLACK;
    171. grandfather->_col = RED;
    172. break;
    173. }
    174. else
    175. {
    176. // 非法情况,断死
    177. assert(false);
    178. }
    179. }
    180. }
    181. _root->_col = BLACK;
    182. return true;
    183. }
    184. }
    185. void level_order()
    186. {
    187. _level_order(_root);
    188. }
    189. bool is_balance_tree()
    190. {
    191. if (_root->_col != BLACK)
    192. {
    193. std::cout << "根节点是红色的,异常" <
    194. return false;
    195. }
    196. return _is_balance_tree(_root);
    197. }
    198. int get_rb_tree_high()
    199. {
    200. return _get_rb_tree_high(_root);
    201. }
    202. private:
    203. void _level_order(Node* root)
    204. {
    205. if (!root)
    206. return;
    207. else
    208. {
    209. std::queue qu;
    210. qu.push(root);
    211. while (!qu.empty())
    212. {
    213. Node* front = qu.front();
    214. qu.pop();
    215. if (front)
    216. {
    217. qu.push(front->_left);
    218. qu.push(front->_right);
    219. }
    220. if (!front)
    221. std::cout << "N ";
    222. else
    223. std::cout << front->_kv.first << " ";
    224. }
    225. std::cout << std::endl;
    226. }
    227. }
    228. void left_rotate(Node* parent)
    229. {
    230. Node* cur = parent;
    231. Node* cur_right = cur->_right;
    232. Node* cur_right_left = cur_right->_left;
    233. Node* cur_parent = cur->_parent;
    234. cur->_right = cur_right_left;
    235. if (cur_right_left)
    236. cur_right_left->_parent = cur;
    237. cur_right->_left = cur;
    238. cur->_parent = cur_right;
    239. if (!cur_parent)
    240. {
    241. cur_right->_parent = nullptr;
    242. _root = cur_right;
    243. }
    244. else
    245. {
    246. if (cur_parent->_left == cur)
    247. {
    248. cur_parent->_left = cur_right;
    249. }
    250. else
    251. {
    252. cur_parent->_right = cur_right;
    253. }
    254. cur_right->_parent = cur_parent;
    255. }
    256. }
    257. void right_rotate(Node* parent)
    258. {
    259. Node* cur = parent;
    260. Node* cur_left = cur->_left;
    261. Node* cur_left_right = cur_left->_right;
    262. Node* cur_parent = cur->_parent;
    263. cur->_left = cur_left_right;
    264. if (cur_left_right)
    265. cur_left_right->_parent = cur;
    266. cur_left->_right = cur;
    267. cur->_parent = cur_left;
    268. if (!cur_parent)
    269. {
    270. cur_left->_parent = nullptr;
    271. _root = cur_left;
    272. }
    273. else
    274. {
    275. if (cur_parent->_kv.first > cur_left->_kv.first)
    276. {
    277. cur_parent->_left = cur_left;
    278. }
    279. else
    280. {
    281. cur_parent->_right = cur_left;
    282. }
    283. cur_left->_parent = cur_parent;
    284. }
    285. }
    286. bool _is_balance_tree(Node* root)
    287. {
    288. if (!root)
    289. return true;
    290. else
    291. {
    292. int basic_value = _get_black_node_num(root);
    293. return _is_check_rb_tree(root, 0, basic_value);
    294. }
    295. }
    296. bool _is_check_rb_tree(Node* root, int black_num, int basic_value)
    297. {
    298. // basic_value: 红黑树的一条路径中的黑色节点个数,在这里作为基本值
    299. if (root == nullptr)
    300. {
    301. if (black_num != basic_value)
    302. {
    303. std::cout << "路径中的黑色节点个数不相等,异常" << std::endl;
    304. return false;
    305. }
    306. else
    307. return true;
    308. }
    309. else
    310. {
    311. if (root->_col == BLACK)
    312. ++black_num;
    313. // 如果一个节点是红色的,那么该节点一定不是根,因此一定有父亲
    314. // 如果这个节点的父亲也是红色的,那么就说明出现异常
    315. if (root->_col == RED && root->_parent->_col == RED)
    316. {
    317. std::cout << "child: " << root->_kv.first << "parent: " << root->_parent->_kv.first << "出现了连续的红色节点,异常" << std::endl;
    318. return false;
    319. }
    320. return _is_check_rb_tree(root->_left, black_num, basic_value)
    321. && _is_check_rb_tree(root->_right, black_num, basic_value);
    322. }
    323. }
    324. int _get_black_node_num(Node* root)
    325. {
    326. if (root == nullptr)
    327. return 0;
    328. else
    329. {
    330. int ret = 0;
    331. while (root)
    332. {
    333. if (root->_col == BLACK)
    334. ++ret;
    335. root = root->_left;
    336. }
    337. return ret;
    338. }
    339. }
    340. int _get_rb_tree_high(Node* root)
    341. {
    342. if (root == nullptr)
    343. return 0;
    344. else
    345. {
    346. int left_high = _get_rb_tree_high(root->_left);
    347. int right_high = _get_rb_tree_high(root->_right);
    348. return left_high > right_high ? ++left_high : ++right_high;
    349. }
    350. }
    351. private:
    352. Node* _root;
    353. };
    354. }

    4.3. map和set的封装

    可以看到,我们下面的map和set的封装都用了一个仿函数,原因是因为

    map底层用的红黑树的第二个模板参数是一个pair

    set底层用的红黑树的第二个模板参数是一个key

    第一个模板参数:为了拿到K的类型,因为find、erase这些接口函数的参数都是K

    第二个模板参数决定了红黑树的节点存放的数据类型 一种是K,一种是KV

    但是此时红黑树不知道你传过来的第二个模板参数具体是什么,因此我们需要在上一层也就是set/map这一层将第二个模板参数是什么告诉给红黑树。

    那怎么告诉呢?在这里我们用一个仿函数:

    map_key_of_t/set_key_of_t的作用就是,如果你是set,那么我就去取出key,如果你是map那么我就去取出pair的first

    4.3.1. map的封装

    1. //map的封装
    2. namespace Xq
    3. {
    4. template<class K,class V>
    5. class map
    6. {
    7. struct map_key_of_t
    8. {
    9. const K& operator()(const std::pair<const K,V>& kv)
    10. {
    11. return kv.first;
    12. }
    13. };
    14. public:
    15. //当去取一个类模板类型的时候,需要加typename,因为编译器无法区别静态变量和类型,
    16. //需要加typename告诉编译器这里是类型
    17. typedef typename Xq::_rb_tree_iteratorconst K,V>,std::pair<const K,V>&,std::pair<const K,V>*> iterator;
    18. iterator begin()
    19. {
    20. return _tree.begin();
    21. }
    22. iterator end()
    23. {
    24. return _tree.end();
    25. }
    26. // 返回插入的键值对的second的引用
    27. V& operator[](const K& key)
    28. {
    29. std::pairbool> ret = _tree.insert(std::make_pair(key,V()));
    30. return ret.first->second;
    31. }
    32. std::pairbool> insert(const std::pair& kv)
    33. {
    34. return _tree.insert(kv);
    35. }
    36. private:
    37. Xq::rb_treeconst K,V>,map_key_of_t> _tree;
    38. };
    39. }

    4.3.2. set的封装

    1. #include "rb_tree.h"
    2. namespace Xq
    3. {
    4. template<class K>
    5. class set
    6. {
    7. public:
    8. typedef typename Xq::_rb_tree_iterator iterator;
    9. struct set_key_of_t
    10. {
    11. const K& operator()(const K& key)
    12. {
    13. return key;
    14. }
    15. };
    16. iterator begin()
    17. {
    18. return _tree.begin();
    19. }
    20. iterator end()
    21. {
    22. return _tree.end();
    23. }
    24. std::pairbool> insert(const K& key)
    25. {
    26. return _tree.insert(key);
    27. }
    28. private:
    29. Xq::rb_treeset_key_of_t> _tree;
    30. };
    31. }
    1. //map和set的底层数据结构红黑树
    2. #pragma once
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. #include
    10. namespace Xq
    11. {
    12. enum color
    13. {
    14. RED,
    15. BLACK
    16. };
    17. template<class T>
    18. struct rb_tree_node
    19. {
    20. rb_tree_node* _left;
    21. rb_tree_node* _right;
    22. rb_tree_node* _parent;
    23. T _data;
    24. color _col;
    25. rb_tree_node(const T& data = T())
    26. :_left(nullptr)
    27. , _right(nullptr)
    28. , _parent(nullptr)
    29. , _data(data)
    30. , _col(RED)
    31. {}
    32. };
    33. template<class T, class Ref, class Ptr>
    34. struct _rb_tree_iterator
    35. {
    36. typedef typename Xq::_rb_tree_iterator Self;
    37. typedef typename Xq::rb_tree_node Node;
    38. Node* _node;
    39. _rb_tree_iterator(Node* node) :_node(node){}
    40. Ref operator*()
    41. {
    42. return _node->_data;
    43. }
    44. Ptr operator->()
    45. {
    46. return &(operator*());
    47. }
    48. bool operator!=(const Self& it)
    49. {
    50. return it._node != _node;
    51. }
    52. Self& operator++()
    53. {
    54. if (_node->_right)
    55. {
    56. // 如果当前节点的右子树不为空,那么下一个节点的位置就是右子树的最左节点
    57. Node* left_node = _node->_right;
    58. while (left_node->_left)
    59. {
    60. left_node = left_node->_left;
    61. }
    62. _node = left_node;
    63. }
    64. else
    65. {
    66. //如果当前节点的右子树为空,找孩子是父亲的左孩子的那个祖先
    67. //也就是说当该节点是其父节点的右孩子,则说明它和它的父亲都被访问过了,继续向上走
    68. Node* cur = _node;
    69. Node* parent = cur->_parent;
    70. while (parent && parent->_right == cur)
    71. {
    72. cur = parent;
    73. parent = cur->_parent;
    74. }
    75. _node = parent;
    76. }
    77. return *this;
    78. }
    79. Self& operator--()
    80. {
    81. // 依据右根左的思想
    82. // 如果当前节点的左子树不为空,那么下一个迭代器的位置就是
    83. // 左子树的最右节点
    84. if (_node->_left)
    85. {
    86. Node* right_node = _node->_left;
    87. while (right_node->_right)
    88. {
    89. right_node = right_node->_right;
    90. }
    91. _node = right_node;
    92. }
    93. //如果左子树为空,那么就要找当前孩子是父亲的右孩子的那个祖先
    94. else
    95. {
    96. Node* cur = _node;
    97. Node* parent = cur->_parent;
    98. while (parent && parent->_left == cur)
    99. {
    100. cur = parent;
    101. parent = parent->_parent;
    102. }
    103. _node = parent;
    104. }
    105. return *this;
    106. }
    107. };
    108. template<class K, class T, class key_of_t>
    109. class rb_tree
    110. {
    111. private:
    112. typedef typename Xq::rb_tree_node Node;
    113. typedef typename Xq::_rb_tree_iterator iterator;
    114. typedef typename Xq::_rb_tree_iteratorconst T&, const T*> const_iterator;
    115. public:
    116. rb_tree(Node* root = nullptr) :_root(root){}
    117. ~rb_tree()
    118. {
    119. _destroy(_root);
    120. }
    121. iterator begin()
    122. {
    123. //中序遍历---> begin就是最左节点
    124. Node* cur = _root;
    125. while (cur && cur->_left)
    126. {
    127. cur = cur->_left;
    128. }
    129. return iterator(cur);
    130. }
    131. iterator end()
    132. {
    133. // end()是最后一个节点的下一个位置的迭代器
    134. return iterator(nullptr);
    135. }
    136. bool find(const T& data)
    137. {
    138. key_of_t kot;
    139. Node* cur = _root;
    140. while (cur)
    141. {
    142. if (kot(cur->_data) > kot(data))
    143. {
    144. cur = cur->_left;
    145. }
    146. else if (kot(cur->_data) < kot(data))
    147. {
    148. cur = cur->_right;
    149. }
    150. else
    151. {
    152. return true;
    153. }
    154. }
    155. return false;
    156. }
    157. std::pairbool> insert(const T& data)
    158. {
    159. if (!_root)
    160. {
    161. _root = new Node(data);
    162. _root->_col = BLACK;
    163. return std::make_pair(iterator(_root), true);
    164. }
    165. else
    166. {
    167. Node* cur = _root;
    168. Node* parent = nullptr;
    169. key_of_t kot;
    170. while (cur)
    171. {
    172. if (kot(cur->_data) > kot(data))
    173. {
    174. parent = cur;
    175. cur = cur->_left;
    176. }
    177. else if (kot(cur->_data)< kot(data))
    178. {
    179. parent = cur;
    180. cur = cur->_right;
    181. }
    182. else
    183. {
    184. //如果插入的键已经存在,则返回一个已存在该键值对的迭代器
    185. return std::make_pair(iterator(cur), false);
    186. }
    187. }
    188. cur = new Node(data);
    189. //由于下面的旋转可能会更改cur,因此提前保存一下
    190. Node* newnode = cur;
    191. if (kot(data) > kot(parent->_data))
    192. {
    193. parent->_right = cur;
    194. }
    195. else
    196. {
    197. parent->_left = cur;
    198. }
    199. cur->_parent = parent;
    200. // 当父节点parent的颜色为红色时,需要调整
    201. while (parent && parent->_col == RED)
    202. {
    203. // 因为父节点parent->_col == RED,那么说明parent一定有父节点
    204. Node* grandfather = parent->_parent;
    205. if (parent == grandfather->_left)
    206. {
    207. Node* uncle = grandfather->_right;
    208. // case 1: 当父节点为红,且叔叔不为空且为红
    209. // Solution : 变色
    210. if (uncle && uncle->_col == RED)
    211. {
    212. parent->_col = uncle->_col = BLACK;
    213. grandfather->_col = RED;
    214. if (grandfather == _root)
    215. grandfather->_col = BLACK;
    216. // 继续向上判断,如果依旧符合case 1,继续变色
    217. cur = grandfather;
    218. parent = cur->_parent;
    219. continue;
    220. }
    221. // case 2: 父节点为红,且叔叔为空或者不为空且为黑,并且g、p、c在同一条直线上
    222. // Solution: 单旋, 在这里就是对grandfather右单旋
    223. else if (parent->_left == cur && ((!uncle) || uncle->_col == BLACK))
    224. {
    225. right_rotate(grandfather);
    226. parent->_col = BLACK;
    227. grandfather->_col = RED;
    228. // 旋转完,更新完颜色,调整就结束
    229. break;
    230. }
    231. // case 3:父节点为红,且叔叔为空或者不为空且为黑,并且g、p、c不在同一条直线上
    232. // Solution: 双旋,在这里就是先对p进行左单旋,在对g进行右单旋
    233. else if (parent->_right == cur && ((!uncle) || uncle->_col == BLACK))
    234. {
    235. left_rotate(parent);
    236. right_rotate(grandfather);
    237. cur->_col = BLACK;
    238. grandfather->_col = RED;
    239. // 旋转完,更新完颜色,调整就结束
    240. break;
    241. }
    242. else
    243. {
    244. // 非法情况
    245. assert(false);
    246. }
    247. }
    248. // 当parent是grandfather的右孩子,那么uncle就是grandfather的左孩子
    249. else
    250. {
    251. Node* uncle = grandfather->_left;
    252. // case 1: 当父节点为红,且叔叔不为空且为红
    253. // Solution : 变色
    254. if (uncle && uncle->_col == RED)
    255. {
    256. parent->_col = uncle->_col = BLACK;
    257. grandfather->_col = RED;
    258. if (grandfather == _root)
    259. grandfather->_col = BLACK;
    260. // 继续向上判断,如果依旧符合case 1,继续变色
    261. cur = grandfather;
    262. parent = cur->_parent;
    263. continue;
    264. }
    265. // case 2: 父节点为红,且叔叔为空或者不为空且为黑,并且g、p、c在同一条直线上
    266. // Solution: 单旋, 在这里就是对grandfather左单旋
    267. else if (parent->_right == cur && ((!uncle) || uncle->_col == BLACK))
    268. {
    269. left_rotate(grandfather);
    270. parent->_col = BLACK;
    271. grandfather->_col = RED;
    272. //旋转并将颜色更新后,就退出调整
    273. break;
    274. }
    275. // case 3:父节点为红,且叔叔为空或者不为空且为黑,并且g、p、c不在同一条直线上
    276. // Solution: 双旋,在这里就是先对p进行右单旋,在对g进行左单旋
    277. else if (parent->_left == cur && ((!uncle) || uncle->_col == BLACK))
    278. {
    279. right_rotate(parent);
    280. left_rotate(grandfather);
    281. cur->_col = BLACK;
    282. grandfather->_col = RED;
    283. break;
    284. }
    285. else
    286. {
    287. // 非法情况,断死
    288. assert(false);
    289. }
    290. }
    291. }
    292. _root->_col = BLACK;
    293. return std::make_pair(iterator(newnode), true);
    294. }
    295. }
    296. private:
    297. void left_rotate(Node* parent)
    298. {
    299. Node* cur = parent;
    300. Node* cur_right = cur->_right;
    301. Node* cur_right_left = cur_right->_left;
    302. Node* cur_parent = cur->_parent;
    303. cur->_right = cur_right_left;
    304. if (cur_right_left)
    305. cur_right_left->_parent = cur;
    306. cur_right->_left = cur;
    307. cur->_parent = cur_right;
    308. if (!cur_parent)
    309. {
    310. cur_right->_parent = nullptr;
    311. _root = cur_right;
    312. }
    313. else
    314. {
    315. if (cur_parent->_left == cur)
    316. {
    317. cur_parent->_left = cur_right;
    318. }
    319. else
    320. {
    321. cur_parent->_right = cur_right;
    322. }
    323. cur_right->_parent = cur_parent;
    324. }
    325. }
    326. void right_rotate(Node* parent)
    327. {
    328. key_of_t kot;
    329. Node* cur = parent;
    330. Node* cur_left = cur->_left;
    331. Node* cur_left_right = cur_left->_right;
    332. Node* cur_parent = cur->_parent;
    333. cur->_left = cur_left_right;
    334. if (cur_left_right)
    335. cur_left_right->_parent = cur;
    336. cur_left->_right = cur;
    337. cur->_parent = cur_left;
    338. if (!cur_parent)
    339. {
    340. cur_left->_parent = nullptr;
    341. _root = cur_left;
    342. }
    343. else
    344. {
    345. if (kot(cur_parent->_data) > kot(cur_left->_data))
    346. {
    347. cur_parent->_left = cur_left;
    348. }
    349. else
    350. {
    351. cur_parent->_right = cur_left;
    352. }
    353. cur_left->_parent = cur_parent;
    354. }
    355. }
    356. void _destroy(Node*& root)
    357. {
    358. if (root == nullptr)
    359. return;
    360. else
    361. {
    362. _destroy(root->_left);
    363. _destroy(root->_right);
    364. delete root;
    365. root = nullptr;
    366. }
    367. }
    368. private:
    369. Node* _root;
    370. };
    371. }

  • 相关阅读:
    Spring框架系列(10) - Spring AOP实现原理详解之AOP代理的创建
    ----JAVA 继承----
    基于 Flask-Admin 与 AdminLTE 构建通用后台管理系统
    有哪些方法下载外文文献?
    计算机网络——wireshark抓包
    初识Kafka构造组成
    求职简历这样写,轻松搞定面试官
    LC15. 三数之和 题解总结
    django基于pythonweb的大学生兼职系统设计与分析
    【开发流程】持续集成、持续交付、持续部署
  • 原文地址:https://blog.csdn.net/m0_62229058/article/details/133417916