【C++】map和set的使用及其模拟实现

news/2025/2/21 4:24:20/

文章目录

  • 一、map和set的使用
    • 1. 关联式容器
    • 2. 键值对
    • 3. 关联式容器的使用
      • 3.1 set
      • 3.2 multiset
      • 3.3 map
      • 3.4 multimap
  • 二、map和set的模拟实现
    • 1. 红黑树的实现(封装map和set版本)
      • 1.1 节点的实现
      • 1.2 KeyOfT(仿函数)
      • 1.3 红黑树的插入Insert
      • 1.4 迭代器iterator
    • 2. set的模拟实现
    • 3. map的模拟实现
    • 4. RbTree的完整源码


一、map和set的使用

1. 关联式容器

在初阶阶段,我们已经接触过STL中的部分容器,比如:vector、list、deque、forward_list(C++11)等,这些容器统称为序列式容器,因为其底层为线性序列的数据结构,里面存储的是元素本身。那什么是关联式容器?它与序列式容器有什么区别?

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

💕 树形结构的关联式容器:

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


2. 键值对

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

比如:现在要建立一个英汉互译的字典,那该字典中必然有英文单词与其对应的中文含义,而且,英文单词与其中文含义是一一对应的关系,即通过该应该单词,在词典中就可以找到与其对应的中文含义。

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

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){}
};

这里我们可以看到,pair类中的first就是键值key,second就是key对应的信息 value;所以以后在定义KV模型的时候,只需要在容器/容器中的每一个节点中定义一个pair对象即可。

💕 make_pair函数

由于 pair 是类模板,所以我们通常是以 显式实例化 + 匿名对象 的方式来进行使用,但是由于显式实例化比较麻烦,所以 C++ 还提供了 make_pair 函数,其定义如下:

在这里插入图片描述

make_pair函数的内部实现如下:

template <class T1,class T2>
pair<T1,T2> make_pair (T1 x, T2 y)
{return ( pair<T1,T2>(x,y) );
}

make_pair 返回的是一个 pair 的匿名对象,匿名对象会自动调用 pair 的默认构造完成初始化;但由于 make_pair 是一个函数模板,所以模板参数的类型可以根据实参来自动推导完成隐式实例化,这样我们就不用每次都显式指明参数类型了。


3. 关联式容器的使用

3.1 set

set是一种key模型的容器,也就是说,set中只有键值key,而没有对应的value,并且每个key都是唯一的,set容器中的元素不允许修改,因为那样会破坏搜索树的规则。但是set允许插入和删除。

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

总结:

  • set 是K模型的容器,所以 set 中插入元素时,只需要插入 key 即可,不需要构造键值对;
  • set中的元素不可以重复,因此可以使用set进行去重;
  • 由于 set 底层是搜索树,所以使用 set 的迭代器遍历 set 中的元素,可以得到有序序列,即 set 可以用来排序;
  • set 默认使用的仿函数为 less,所以 set 中的元素默认按照小于来比较;
  • 由于 set 底层是平衡树搜索树,所以 set 中查找某个元素,时间复杂度为 O(logN);
  • set 中的元素不允许修改,因为这可能破坏搜索树的结构;
  • set 中的底层使用平衡二叉搜索树 (红黑树) 来实现。

💕 set的使用

构造

在这里插入图片描述

迭代器

在这里插入图片描述

修改

在这里插入图片描述

其他操作

在这里插入图片描述

set使用范例

void TestSet()
{// 用数组array中的元素构造setint array[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0, 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };set<int> s(array, array + sizeof(array) / sizeof(array[0]));cout << s.size() << endl;// 正向打印set中的元素,从打印结果中可以看出:set可去重for (auto& e : s)cout << e << " ";cout << endl;// 使用迭代器逆向打印set中的元素for (auto it = s.rbegin(); it != s.rend(); ++it)cout << *it << " ";cout << endl;// set中值为3的元素出现了几次cout << s.count(3) << endl;
}

在这里插入图片描述


3.2 multiset

multiset 也是 K模型 的容器,它和 set 唯一的区别在于 multiset 中允许存在重复的 key 值节点 ,所以 multiset 可以用来排序和查找,但是不能用来去重

在这里插入图片描述

💕 multiset的使用

multiset 的使用和 set 几乎一样,这里我们需要注意两点即可:

  • 由于 multiset 中允许存在重复 key 值的节点,所以 multiset 中 count 函数就有作用了,我们可以通过 count 函数来统计同一 key 中在 multiset 中的数量。
  • multiset 中 find 函数的使用也和 set 有所区别 – 由于 set 中没有重复的节点,所以 find 时要么返回该节点位置的迭代器,要么返回 end();而 multiset 中可能有重复的节点,所以 find 时返回的是同一 key 值中的哪一个节点呢?实际上 find 返回的是中序遍历过程中第一个匹配的节点位置的迭代器。

multiset的使用范例

void multiset_test() {// 构造multisetint array[] = { 1, 3, 5, 7, 3, 2, 4, 6, 8, 0, 1, 3, 5, 7, 3, 2, 4, 6, 8, 3 };multiset<int> s(array, array + sizeof(array) / sizeof(array[0]));cout << s.size() << endl;// 正向打印multiset中的元素,multiset允许重复元素的出现for (const auto& e : s)cout << e << " ";cout << endl;//如果查找的key在multiset中有多个,则返回中序遍历中遇到的第一个节点的迭代器multiset<int>::iterator pos = s.find(3);while (pos != s.end()) {cout << *pos << " ";++pos;}cout << endl;// 查找+删除cout << s.count(3) << endl; //输出key为3的节点的个数pos = s.find(3);if (pos != s.end())s.erase(pos);cout << s.count(3) << endl;
}

在这里插入图片描述


3.3 map

map 和 set 一样都是按照一定次序存储元素的容器,其底层也是一棵平衡二叉搜索树;和 set 不同的是,map 是 KV模型 的容器在map 中,键值 key 通常用于排序和惟一地标识元素,而值 value中 用于存储与此键值 key 关联的内容,键值 key 和值value的类型可以不同; 在 map 内部,key-value 通过成员类型 pair 绑定在一起,也就是我们文章开始提到的键值对

在这里插入图片描述

需要注意的是:map 中的元素是按照键值 key 进行比较排序的,而与 key 对应的 value 无关,同时,map 中也不允许有重复 key 值的节点,也不允许修改key,但是它可以修改节点中key对应的value;map 也可用于排序、查找和去重,且 map 查找的时间复杂度也为 O(logN)。

💕 map的使用

构造

在这里插入图片描述

迭代器

在这里插入图片描述

修改

在这里插入图片描述

元素访问

在这里插入图片描述

[ ]运算符重载的函数定义如下:

mapped_type& operator[] (const key_type& k) 
{(*((this->insert(make_pair(k, mapped_type()))).first)).second;
}
//拆解后的函数
V& operator[] (const K& key)
{pair<iterator, bool> ret = insert(make_pair(key, V()));//return *(ret.first)->second;return ret.first->second;
}

下面我们来分析一下这个函数:

  • operator[] 函数先向map容器中插入key:如果map中有与该值相等的节点,则插入失败,返回的pair中存放着该节点位置的迭代器和false;如果map中没有与该节点相等的节点,则插入成功,该节点的value值为 V 默认构造的缺省值。
  • 然后,operator[] 会取出pair迭代器(ret,first)然后对迭代器进行解引用得到一个pair<k,v>对象,最后再返回排pair对象中的second的引用,即key对应的value的引用。因此我们可以直接在函数外部修改key对应的value的值。

map的使用案例

void map_test() {map<string, string> m;m.insert(pair<string, string>("peach", "桃子"));//为了方便,我们建议直接用make_pair函数来构造键值对m.insert(make_pair("banan", "香蕉"));m["apple"] = "苹果";for (auto& e : m)cout << e.first << ":" << e.second << endl;cout << endl;// map中的键值对key一定是唯一的,如果key存在将插入失败auto ret = m.insert(make_pair("peach", "桃色"));if (ret.second)cout << "<peach, 桃色>不在map中, 已经插入" << endl;elsecout << "键值为 peach 的元素已经存在:" << ret.first->first << "--->" << ret.first->second << " 插入失败" << endl;// 删除key为"apple"的元素m.erase("apple");if (1 == m.count("apple"))cout << "apple还在" << endl;elsecout << "apple被吃了" << endl;
}

在这里插入图片描述


3.4 multimap

同样,multimap与map的区别和multiset与set的区别是一样的。find返回的是中序遍历中遇到的第一个节点的迭代器。count返回和key值相等的节点的个数。

这里我们需要注意的是:multimap中没有重载[]运算符,因为multimap中的key值是可以重复的。如果使用 [] 运算符,会导致无法确定访问哪个哪一个元素。


二、map和set的模拟实现

我们知道,map和set容器的底层都是使用红黑树实现的。但是,set是K模型的的容器,而map是KV模型的容器,他们却都能使用红黑树来封装。下面,我们先来看一下源码中是如何实现的。

在这里插入图片描述

这里我们发现,set头文件中包含了 stl_set.hstl_multiset.h ,而map头文件中包含了 stl_map.hstl_multimap.h,而stl_tree.h应该就是用来封装实现他们的红黑树。

在这里插入图片描述

这里我们可以看到:set和map的增删查改功能都是封装的同一个红黑树的接口。

对于 map来说,key_type 就是我们平常所理解的 K,但是 value_type 却是 pair 类型,它的两个参数分别是模板参数 _Key 和 _Tp,也就是我们认为的传递给 map 的 K 和 V;

而对于 set 来说,key_type 也是我们平时认为的 K,但是我们发现 set 中居然还有一个变量 value_type,并且 value_type 的类型就是 key_type 的类型。

下面我们来看一下红黑树的源码,看一下为什么对于这种K模型还需要设计一个value_type变量呢?

//stl_tree.h
//红黑树的基类
struct __rb_tree_node_base
{typedef __rb_tree_color_type color_type;typedef __rb_tree_node_base* base_ptr;color_type color; base_ptr parent;base_ptr left;base_ptr right;
};//红黑树的节点结构
template <class Value>
struct __rb_tree_node : public __rb_tree_node_base
{typedef __rb_tree_node<Value>* link_type;Value value_field;
};//红黑树
template <class Key, class Value, class KeyOfValue, class Compare,class Alloc = alloc>
class rb_tree {
protected:typedef __rb_tree_node_base* base_ptr;typedef __rb_tree_node<Value> rb_tree_node;
public:typedef Key key_type;typedef Value value_type;typedef rb_tree_node* link_type;
}

set、maprbtree之间的关系:

在这里插入图片描述

通过这张图我们就可以理解,为什么set和map是不同模型的容器,但是他们却都可以使用同一棵树红黑树作为底层容器了。

如果是 map,则红黑树的模板参数 _Val 被实例化为 pair\u003CK,V>,那么红黑树节点中存储的数据的类型也是 pair\u003CK,V>;如果是 set,则红黑树的模板参数 _Val 被实例化为 K,此时红黑树节点中存储的数据的类型就为 K;

也就是说,红黑树完全可以根据第二个模板参数 _Val 的实参类型的不同实例化出符合 set 要求和 符合 map 要求的不同的两个类。这里我们来解答一下传递第一个模板参数 _Key 的作用?

这是因为某些函数需要使用 key,比如 find 函数就只能通过 key 进行查询,即在 map 的 find 中不能通过 _Val 即 pair 类型来进行查询。


1. 红黑树的实现(封装map和set版本)

现在,我们需要参考源码中的方式将我们自己实现的红黑树改造一下,使得它既能够封装map,又能够封装set。

1.1 节点的实现

//创建红黑树的节点
template <class T>
struct RBTreeNode
{RBTreeNode<T>* _left;RBTreeNode<T>* _right;RBTreeNode<T>* _parent;T _data;Colour _col;//枚举红黑树的颜色RBTreeNode(const T& data):_left(nullptr), _right(nullptr), _parent(nullptr), _data(data), _col(RED){}
};
template<class K, class T>
class RBTree
{typedef RBTreeNode<T> Node;
private:Node* _root = nullptr;
};

1.2 KeyOfT(仿函数)

我们在 insert 时需要比较 key 的大小来确定插入位置,在之前模拟实现的红黑树中,我们直接将节点定义为了pair<K,V> 类型,所以我们可以直接取 kv.first 进行比较;但是现在,节点中的数据既可能是 pair 类型,也可能是 key 的类型,所以我们不能再用 data.first,同时由于stl 中实现的 pair 比较函数并不是单纯比较 first,而是先比较 first,再比较 second。

为了实现在插入操作中既可以比较Key模型的Key值,又可以比较KV模型的Key值,我们可以自己写一个仿函数来完成红黑树中节点数据 _data大小的比较了。当然,源码中也是这样实现的。

//set
namespace cjl
{template<class K>	class set{struct SetKeyOfT{const K& operator()(const K& key){return key;}};private:RBTree<K, K, SetKeyOfT> _t;};
}
//map
namespace cjl
{template<class K, class V>class map{struct MapKeyofT{const K& operator()(const pair<const K,V>& kv){return kv.first;}};private:RBTree<K, pair<const K,V>, MapKeyofT> _t;};
}

1.3 红黑树的插入Insert

template<class K, class T, class KeyOfT>
class RBTree {typedef RBTreeNode<T> Node;
public:bool insert(const T& data) {//判断根节点是否为空if (_root == nullptr) {_root = new Node(data);_root->_col = BLACK;  //根节点的颜色一定为黑return true;}//寻找插入位置KeyOfT kot;  //仿函数对象Node* parent = nullptr;Node* cur = _root;while (cur) {if (kot(data) < kot(cur->_data)) {parent = cur;cur = cur->_left;}else if (kot(data) > .kot(cur->_data)) {parent = cur;cur = cur->_right;}else {return false;  //不允许重复节点}}Node* newNode = new Node(data);if (kot(data) < kot(parent->_data))parent->_left = newNode;elseparent->_right = newNode;newNode->_parent = parent;  //修改父节点指向//调整节点//...return true;}
private:Node* _root;
};

1.4 迭代器iterator

💕 begin() 迭代器

begin()迭代器的位置是红黑树中序遍历的第一个节点,也就是整棵树的最左节点。

💕 end() 迭代器

end()迭代器是红黑树中序遍历最后一个元素的下一个位置。为了让 end() 能够指向最右节点的下一个节点, stl 源码中增加了一个哨兵位的头结点,该节点的 left 指向这棵树的最左节点,也就是begin(),right 指向这棵树的最右节点,parent 指向 nullptr,然后让根的父节点指向它,最右节点的 right 也指向它;所以在 stl 源码的实现中这个哨兵位的头结点就是 end();

在这里插入图片描述

这里我们直接将end()定义为nullptr即可。

💕 迭代器的++ 与 - -

因为红黑树的中序遍历是一个有序序列,所以++就是跳转到红黑树中序遍历的下一个节点的位置。--则是反过来跳转到中序遍历中上一个节点的位置。

在这里插入图片描述

+ +情况的分类

  • 若当前节点有右孩子,那么迭代器++跳转到右子树的最左节点。
  • 若当前节点没有右孩子,则迭代器++跳转到当前节点为父节点

- - 情况的分类

  • 若当前节点有左孩子,那么迭代器 - - 跳到左子树的最右节点;
  • 若当前节点没有左孩子,则迭代器 - - 跳到 当前节点为 父节点的右孩子 的那一个祖先节点;
//迭代器的实现
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);}//重载!=bool operator!=(const Self& s){return _node != s._node;}//重载 ++itSelf& operator++(){//本质上是找中序遍历的下一个元素——分为两种情况:right == nullptr 和 right != nullptrif (_node->_right) // right != nullptr{Node* parent = _node->_right;while (parent->_left){parent = parent->_left;}_node = parent;}else //right == nullptr{Node* parent = _node->_parent;Node* cur = _node;while (parent && cur == parent->_right){cur = cur->_parent;parent = parent->_parent;}_node = parent;}return *this;}//重载 --itSelf& operator--(){if (_node->_left) // left != nullptr{Node* parent = _node->_left;while (parent->_right){parent = parent->_right;}_node = parent;}else //left == nullptr{Node* parent = _node->_parent;Node* cur = _node;while (parent && cur == parent->_left){cur = cur->_parent;parent = parent->_parent;}_node = parent;}return *this;}};

2. set的模拟实现

set插入查询函数直接使用适配器模式,调用红黑树对应的函数即可完成,这里最重要的是迭代器的复用。

在这里插入图片描述

当我们直接复用红黑树的迭代器时,会发现一个问题,我们可以使用迭代器来修改key值,但是如果这样的话必定会破坏搜索树的结构。

在这里插入图片描述

我们直接让 set 的普通迭代器和 const 迭代器都使用红黑树的 const 迭代器来进行封装即可,这样 set 迭代器解引用得到的就是 const K& 了;但是这样做之后发现虽然的确不能通过迭代器来修改 key 的值了,但是这里又发生了新的错误:

在这里插入图片描述

其实这是因为在insert中返回的是红黑树的普通迭代器,但是接受返回值的却是红黑树的const迭代器。

要解决这个问题的方法其实也并不难,我们需要在红黑树的迭代器中实现一个拷贝构造函数。

  • 当模板实例化为 <T, T&, T*> 时,iterator 和 Self 相同,此时这是一个拷贝构造函数;
  • 当模板实例化为 <T, const T&, const T*> 时,Self 是 const 迭代器,而 iterator 是普通迭代器,此时这是一个构造函数,它可以用普通迭代器构造出一个 const 迭代器。

所以我们可以通过红黑树迭代器类中构造函数来实现用普通迭代器来构造 const迭代器。

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){}__RBTreeIterator(const __RBTreeIterator<T,T&,T*>& iterator):_node(iterator._node){}//运算符的重载...
};

在这里插入图片描述

💕 set模拟实现源码:

namespace cjl
{template<class K>	class set{struct SetKeyOfT{const K& operator()(const K& key){return key;}};public:	typedef typename RBTree<K, K, SetKeyOfT>::const_iterator iterator;typedef typename RBTree<K, K, SetKeyOfT>::const_iterator const_iterator;iterator begin(){return _t.begin();}iterator end(){return _t.end();}const_iterator begin() const {return _t.begin();}const_iterator end() const {return _t.end();}//插入pair<iterator, bool> insert(const K& key){return _t.Insert(key);}//查询iterator find(const K& key){return _t.Find(key);}public:private:RBTree<K, K, SetKeyOfT> _t;};
}

3. map的模拟实现

namespace cjl
{template<class K, class V>class map{struct MapKeyofT{const K& operator()(const pair<const K,V>& kv){return kv.first;}};public:typedef typename RBTree<K, pair<const K, V>, MapKeyofT>::iterator iterator;typedef typename RBTree<K, pair<const K, V>, MapKeyofT>::const_iterator const_iterator;iterator begin(){return _t.begin();}iterator end(){return _t.end();}const_iterator begin() const{return _t.begin();}const_iterator end() const{return _t.end();}pair<iterator,bool> insert(const pair<const K, V>& kv){return _t.Insert(kv);}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<const K,V>, MapKeyofT> _t;};
}

4. RbTree的完整源码

#pragma once//枚举类型,枚举红黑树的颜色红色和黑色
enum Colour {RED,BLACK,
};//创建红黑树的节点
template <class T>
struct RBTreeNode
{RBTreeNode<T>* _left;RBTreeNode<T>* _right;RBTreeNode<T>* _parent;T _data;Colour _col;//枚举红黑树的颜色RBTreeNode(const T& data):_left(nullptr), _right(nullptr), _parent(nullptr), _data(data), _col(RED){}
};//迭代器的实现
template<class T,class Ref,class Ptr>
struct __RBTreeIterator
{typedef RBTreeNode<T> Node;typedef __RBTreeIterator<T, Ref, Ptr> Self;Node* _node;//迭代器的构造函数__RBTreeIterator(Node* node):_node(node){}__RBTreeIterator(const __RBTreeIterator<T,T&,T*>& iterator):_node(iterator._node){}//重载*Ref operator*(){return _node->_data;}//重载->Ptr operator->(){return &(_node->_data);}//重载!=bool operator!=(const Self& s){return _node != s._node;}//重载 ++itSelf& operator++(){//本质上是找中序遍历的下一个元素——分为两种情况:right == nullptr 和 right != nullptrif (_node->_right) // right != nullptr{Node* parent = _node->_right;while (parent->_left){parent = parent->_left;}_node = parent;}else //right == nullptr{Node* parent = _node->_parent;Node* cur = _node;while (parent && cur == parent->_right){cur = cur->_parent;parent = parent->_parent;}_node = parent;}return *this;}//重载 --itSelf& operator--(){if (_node->_left) // left != nullptr{Node* parent = _node->_left;while (parent->_right){parent = parent->_right;}_node = parent;}else //left == nullptr{Node* parent = _node->_parent;Node* cur = _node;while (parent && cur == parent->_left){cur = cur->_parent;parent = parent->_parent;}_node = parent;}return *this;}};//创建红黑树
template<class K, class T ,class KeyOfT>
class RBTree
{typedef RBTreeNode<T> Node;
public://析构~RBTree(){_Destroy(_root);_root = nullptr;}//迭代器的实现typedef __RBTreeIterator<T, T&, T*> iterator;typedef __RBTreeIterator<T, const T&, const T*> const_iterator;iterator begin(){Node* cur = _root;while (cur && cur->_left){cur = cur->_left;}return iterator(cur);}iterator end(){return iterator(nullptr);}const_iterator begin() const {Node* cur = _root;while (cur && cur->_left){cur = cur->_left;}return iterator(cur);}const_iterator end() const{return iterator(nullptr);}//查询iterator Find(const K& key){KeyOfT kot;Node* cur = _root;while (cur){if (key > kot(cur->_data))cur = cur->_right;else if (key < kot(cur->_data))cur = cur->_left;elsereturn iterator(cur);}return iterator(nullptr);}//插入pair<iterator, bool> Insert(const T& data){//搜索树的插入过程if (_root == nullptr){_root = new Node(data);_root->_col = BLACK;return make_pair(iterator(_root), true);}KeyOfT kot;Node* parent = nullptr;Node* cur = _root;while (cur){if (kot(data) > kot(cur->_data)){parent = cur;cur = cur->_right;}else if (kot(data) < kot(cur->_data)){parent = cur;cur = cur->_left;}else{return make_pair(iterator(cur), false);}}//链接节点cur = new Node(data);Node* newnode = cur;if (kot(parent->_data) > kot(data))parent->_left = cur;elseparent->_right = cur;cur->_parent = parent;//链接父节点while (parent && parent->_col == RED){Node* grandfather = parent->_parent;if (grandfather->_left == parent){Node* uncle = grandfather->_right;if (uncle && uncle->_col == RED)// 情况1:u存在且为红,变色处理,并继续往上处理{parent->_col = BLACK;uncle->_col = BLACK;grandfather->_col = RED;//继续向上调整cur = grandfather;parent = cur->_parent;}else // 情况2 + 3:u不存在/u存在且为黑 --- 旋转 + 变色{//     g//   p   u// c if (cur == parent->_left){//右单旋RotateR(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else{//     g//   p   u//     c//先对parent节点进行左单旋,在对grandfather进行右单旋RotateL(parent);RotateR(grandfather);//调整颜色cur->_col = BLACK;grandfather->_col = RED;}break;}}else // (grandfather->_right == parent){// u存在且为红,变色处理,并继续往上处理//    g//  u   p//        cNode* uncle = grandfather->_left;if (uncle && uncle->_col == RED){parent->_col = BLACK;uncle->_col = BLACK;grandfather->_col = RED;cur = grandfather;parent = cur->_parent;}else{//    g//  u   p//    cif (cur == parent->_left){RotateR(parent);RotateL(grandfather);//调整颜色cur->_col = BLACK;grandfather->_col = RED;}else{//    g//  u   p//        cRotateL(grandfather);parent->_col = BLACK;grandfather->_col = RED;}break;}}}_root->_col = BLACK;return make_pair(iterator(newnode), true);}//中序遍历void InOrder(){_InOrder(_root);cout << endl;}//判断是否为红黑树bool IsBalance(){if (_root && _root->_col == RED){cout << "根节点颜色是红色的" << endl;return false;}int benchmark = 0;Node* cur = _root;while (cur){if (cur->_col == BLACK)benchmark++;cur = cur->_left;}//判断是否有连续红色节点return _Check(_root, 0, benchmark);}int Height(){return _Height(_root);}private://红黑树的高度计算int _Height(Node* root){if (root == nullptr)return 0;int leftHight = _Height(root->_left);int rightHight = _Height(root->_right);return leftHight > rightHight ? leftHight + 1 : rightHight + 1;}bool _Check(Node* root, int blackNum, int benchmark){if (root == nullptr){if (blackNum != benchmark){cout << "某条路径黑色节点的数量不相等" << endl;return false;}return true;}if (root->_col == BLACK)blackNum++;if (root->_col == RED&& root->_parent&& root->_parent->_col == RED){cout << "存在连续的红色节点" << endl;return false;}return _Check(root->_left, blackNum, benchmark)&& _Check(root->_right, blackNum, benchmark);}void _Destroy(Node* root){if (root == nullptr)return;_Destroy(root->_left);_Destroy(root->_right);delete root;}void _InOrder(Node* root){if (root == nullptr)return;KeyOfT kot;_InOrder(root->_left);cout << kot(root->_data) << " ";_InOrder(root->_right);}//左单旋void RotateL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;parent->_right = subRL;//这里我们需要注意一下,subRL有可能为空if (subRL)subRL->_parent = parent;Node* ppnode = parent->_parent;subR->_left = parent;parent->_parent = subR;//判断父节点是否为_root节点if (parent == _root) {_root = subR;_root->_parent = nullptr;}else {if (parent == ppnode->_left)ppnode->_left = subR;elseppnode->_right = subR;subR->_parent = ppnode;}}//右单旋void RotateR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;parent->_left = subLR;if (subLR)subLR->_parent = parent;//保存parent节点的_parent节点Node* ppnode = parent->_parent;subL->_right = parent;parent->_parent = subL;//判断parent节点是否为_root节点if (parent == _root){_root = subL;_root->_parent = nullptr;}else{if (parent == ppnode->_left)ppnode->_left = subL;elseppnode->_right = subL;subL->_parent = ppnode;}}private:Node* _root = nullptr;
};


http://www.ppmy.cn/news/103377.html

相关文章

4.文件系统

组成 Linux&#xff1a;一切皆文件 索引节点&#xff08;I-node&#xff09; I-node&#xff08;Index Node&#xff09;&#xff1a;文件系统的内部数据结构&#xff0c;用于管理文件的元数据和数据块。 文件的元数据&#xff1a;包括文件的权限、拥有者、大小、时间戳、索引…

分布式系统中的那些一致性(CAP、BASE、2PC、3PC、Paxos、ZAB、Raft)

本文介绍 CAP、BASE理论的正确理解、Paxos 算法如何保证一致性及死循环问题、ZAB 协议中原子广播及崩溃恢复以及 Raft 算法的动态演示。 下面还有投票&#xff0c;一起参与进来吧&#x1f44d; 文章目录 前言CAP理论理解误导正确的理解CAP理论的应用 BASE理论Paxos算法如何保证…

由于找不到msvcr110.dll 无法继续执行怎么解决(最新解决方法分享)

MSVCR110.dll是Windows操作系统中的一个重要文件&#xff0c;一旦它出现丢失问题&#xff0c;会导致影响计算机整体的问题。这个跟MSVCP110.dll类似的误报&#xff0c;通常是由于安装编程工具或 部分无法正确安装所导致的问题。在这篇文章中&#xff0c;我们将一些解决此问题的…

SpringBoot实践(四十四):应用开发编程规范总结

目录 使用mybatisplus代码生成器 controller返回体规范 impl方法规范 Dao层封装规范 异常类封装规范

代码随想录算法训练营15期 Day 6 | 242.有效的字母异位词 、349. 两个数组的交集 、202. 快乐数、1. 两数之和

由于昨天是周日&#xff0c;周日是休息日&#xff0c;所以就是什么也没有写啦。今天是day06天&#xff0c;继续加油。 哈希表理论基础 建议&#xff1a;大家要了解哈希表的内部实现原理&#xff0c;哈希函数&#xff0c;哈希碰撞&#xff0c;以及常见哈希表的区别&#xff0c;…

论文撰写总结与撰写心得——如何更好的产出几万字的论文

一张图开场&#xff0c;说说为什么会有本篇文章&#xff0c;因为晚上关了灯的屏幕太晃眼睛了&#xff0c;之前好几次不是哥哥我不加班帮小伙伴们搞论文&#xff0c;是当时眼睛顶不住了&#xff0c;所以这篇文章除了说一下论文的码字心得外&#xff0c;哥哥我再说一下如何在夜间…

操作系统原理 —— 内存管理的概念(十八)

为什么要有内存管理 为什么要对内存进行管理&#xff0c;需要解决什么问题&#xff1f; 要回答这个问题&#xff0c;首先我们需要明白&#xff1a;进程运行时&#xff0c;需放在内存才能运行。比如在执行一个程序时&#xff0c;需将该程序的相关数据与指令装入内存才能运行。…

【Unity100个实用小技巧】同一个Canvas下的UI顺序通过代码如何修改

☀️博客主页&#xff1a;CSDN博客主页&#x1f4a8;本文由 萌萌的小木屋 原创&#xff0c;首发于 CSDN&#x1f4a2;&#x1f525;学习专栏推荐&#xff1a;面试汇总❗️游戏框架专栏推荐&#xff1a;游戏实用框架专栏⛅️点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd;&#…