【C++】-- STL之用红黑树模拟实现map和set(一)

简介: 【C++】-- STL之用红黑树模拟实现map和set

一、map和set类模板

【C++】-- STL之map和set详解一文中提到,set用value标识元素(value就是key,类型为T),并且每个value必须唯一 。

template < class Key>//set

在map中,键值key通常用于排序和惟一地标识元素,而值value中存储与此键值key关联的内容。键值key和值value的类型可能不同,并且在map的内部,key与value通过成员类型value_type绑定在一起,为其取别名称为pair:

typedef pair<const Key, T> value_type;
template < class Key, class T>//map

红黑树同时封装出set和map时,set传给value的是一个value,map传给value的是一个pair,set和map传给红黑树的value决定了这棵树里面存的节点值类型。上层容器不同,底层红黑树的Key和T也不同。

在上层容器set中,K和T都代表Key,底层红黑树节点当中存储K和T都是一样的;map中,K代表键值Key,T代表由Key和Value构成的键值对,底层红黑树中只能存储T。所以红黑树为了满足同时支持set和map,节点当中存储T。

这就要对红黑树进行改动。

二、红黑树节点定义

1.红黑树的节点修改

【C++】-- 红黑树详解的红黑树节点定义由类模板

template<class K,class V>

修改为

template<class T>

那么节点定义就修改为

1. //红黑树节点定义
2. template<class T>
3. struct RBTreeNode
4. {
5.  RBTreeNode<T>* _left;//节点的左孩子
6.  RBTreeNode<T>* _right;//节点的右孩子
7.  RBTreeNode<T>* _parent;//节点的父亲
8. 
9.  T _data;//节点的值,_data里面存的是K就传K,存的是pair就传pair
10.   Colour _col;//节点颜色
11. 
12.   RBTreeNode(const T& x)
13.     :_left(nullptr)
14.     , _right(nullptr)
15.     , _parent(nullptr)
16.     , _data(x)
17.     , _col(RED)
18.   {}
19. };

由于红黑树不知道上层传的是K还是pair,这是由上层传递的模板参数T决定的,上层是封装我的map和set。

2.仿函数

(1)节点比较大小时存在的问题

红黑树插入节点时,需要比较节点的大小,kv需要改成_data:

1.  //插入
2.  pair<Node*, bool> Insert(const T& data)
3.  {
4.    if (_root == nullptr)
5.    {
6.      _root = new Node(data);
7.      _root->_col = BLACK;
8.      return make_pair(_root, true);
9.    }
10. 
11.     //1.先看树中,kv是否存在
12.     Node* parent = nullptr;
13.     Node* cur = _root;
14.     while (cur)
15.     {
16.       if (cur->_data < data)
17.       {
18.         //kv比当前节点值大,向右走
19.         parent = cur;
20.         cur = cur->_right;
21.       }
22.       else if (cur->_data > data)
23.       {
24.         //kv比当前节点值小,向左走
25.         parent = cur;
26.         cur = cur->_left;
27.       }
28.       else
29.       {
30.         //kv和当前节点值相等,已存在,插入失败
31.         return make_pair(cur, false);
32.       }
33.     }
34. 
35.     //2.走到这里,说明kv在树中不存在,需要插入kv,并且cur已经为空,parent已经是叶子节点了
36.     Node* newNode = new Node(kv);
37.     newNode->_col = RED;
38.     if (parent->_data < data)
39.     {
40.       //kv比parent值大,插入到parent的右边
41.       parent->_right = newNode;
42.       newNode->_parent = parent;
43.     }
44.     else
45.     {
46.       //kv比parent值小,插入到parent的左边
47.       parent->_left = newNode;
48.       newNode->_parent = parent;
49.     }
50.     cur = newNode;
51. 
52. //如果父亲存在,且父亲颜色为红就要处理
53.     while (parent && parent->_col == RED)
54.     {
55.       //情况一和情况二、三的区别关键看叔叔
56.       Node* grandfather = parent->_parent;//当父亲是红色时,根据规则(2)根节点一定是黑色,祖父一定存在
57.       if (parent == grandfather->_left)//父亲是祖父的左子树
58.       {
59.         Node* uncle = grandfather->_right;
60.         //情况一:叔叔存在且为红
61.         if (uncle->_col == RED)
62.         {
63.           parent->_col = uncle->_col = BLACK;
64.           grandfather->_col = RED;
65. 
66.           //继续向上调整
67.           cur = grandfather;
68.           parent = cur->_parent;
69.         }
70.         else//情况二+情况三:叔叔不存在或叔叔存在且为黑
71.         {
72.           //情况二:单旋
73.           if (cur == parent->_left)
74.           {
75.             RotateR(grandfather);
76.             parent->_col = BLACK;
77.             grandfather->_col = RED;
78.           }
79.           else//情况三:双旋
80.           {
81.             RotateL(parent);
82.             RotateR(grandfather);
83.             cur->_col = BLACK;
84.             grandfather->_col = RED;
85.           }
86.           break;//插入结束
87.         }
88.       }
89.       else//父亲是祖父的右子树
90.       {
91.         Node* uncle = grandfather->_left;
92.         //情况一:叔叔存在且为红
93.         if (uncle && uncle->_col == RED)
94.         {
95.           parent->_col = uncle->_col = BLACK;
96.           grandfather->_col = RED;
97. 
98.           //继续往上调整
99.           cur = grandfather;
100.          parent = grandfather->_parent;
101.        }
102.        else//情况二+情况三:叔叔不存在或叔叔存在且为黑
103.        {
104.          //情况二:单旋
105.          if (cur == parent->_right)
106.          {
107.            RotateL(grandfather);
108.            parent->_col = BLACK;
109.            grandfather->_col = RED;
110.          }
111.          else//情况三:双旋
112.          {
113.            RotateR(parent);
114.            RotateL(grandfather);
115.            cur->_col = BLACK;
116.            grandfather->_col = RED;
117.          }
118.          break;//插入结束
119.        }
120.      }
121. 
122.    }
123.    _root->_col = BLACK;
124. 
125.    return make_pair(newNode, true);
126.  }

但是以上代码在插入新节和查找节点时,当和当前节点比较大小时,Key可以比较,但是pair比较不了,也就是set可以比较,但是map比较不了。这就需要写一个仿函数,如果是map就取_data里面的first也就是Key进行比较,通过泛型解决红黑树里面存的是什么。所以上层容器map需要向底层的红黑树提供仿函数来获取T里面的Key,这样无论上层容器是set还是map,都可以用统一的方式进行比较了。

(2)仿函数

仿函数让一个类的使用看上去像个函数。仿函数是在类中实现了一个operator( ),是一个类的对象,这个类就有了类似函数的行为,所以这个类就是一个仿函数类,目的是为了让函数拥有类的性质。

这个类的对象即仿函数,可以当作一般函数去用,只不过仿函数的功能是在一个类中的运算符operator()中实现的,使用的时候把函数作为参进行传递即可。更详细的理解请参考【C++】-- STL容器适配器之priority_queue第三节第1小节的内容。

set有set的仿函数,map有map的仿函数,尽管set的仿函数看起来没有什么作用,但是,必须要把它传给底层红黑树,这样红黑树就能根据仿函数分别获取set的key和map的first。

①set的仿函数

1. namespace delia
2. {
3.  template<class K>
4.  class set
5.  {
6.    //仿函数,获取set的key
7.    struct SetKeyOfT
8.    {
9.      const K& operator()(const K& key)
10.       {
11.         return key;
12.       }
13.     };
14. 
15. public:
16.     bool insert(const K& k)
17.     {
18.       _t.Insert(k);
19.       return true;
20.     }
21. 
22.   private:
23.     RBTree<K, K,SetKeyOfT> _t;
24.   };
25. }

②map的仿函数

1. namespace delia
2. {
3.  template<class K,class V>
4.  class map
5.  {
6.    //仿函数,获取map的first
7.    struct MapKeyOfT
8.    {
9.      const K& operator()(const pair<const K, V>& kv)
10.       {
11.         return kv.first;
12.       }
13.     };
14. 
15. public:
16. //插入
17.     bool insert(const pair<const K, V>& kv)
18.     {
19.       _t.Insert(kv);
20.       return true;
21.     }
22.   private:
23.     RBTree<K, pair<const K, V>, MapKeyOfT> _t;
24.   };
25. }

有了仿函数红黑树的类在实现时,就要在模板参数中增加KeyOfT仿函数。

(3)修改红黑树定义

1. template<class K, class T, class KeyOfT>
2. class RBTree
3. {
4.  typedef RBTreeNode<T> Node;
5. 
6. private:
7.  Node* _root;
8. };

(4)修改红黑树插入

1.  //插入
2.  pair<Node*, bool> Insert(const pair<K, V>& kv)
3.  {
4.    if (_root == nullptr)
5.    {
6.      _root = new Node(kv);
7.      _root->_col = BLACK;
8.      return make_pair(_root, true);
9.    }
10. 
11.     KeyOfT kot;
12. 
13.     //1.先看树中,kv是否存在
14.     Node* parent = nullptr;
15.     Node* cur = _root;
16.     while (cur)
17.     {
18.       if (kot(cur->_data) < kot(data))
19.       {
20.         //kv比当前节点值大,向右走
21.         parent = cur;
22.         cur = cur->_right;
23.       }
24.       else if (kot(cur->_data) > kot(data))
25.       {
26.         //kv比当前节点值小,向左走
27.         parent = cur;
28.         cur = cur->_left;
29.       }
30.       else
31.       {
32.         //kv和当前节点值相等,已存在,插入失败
33.         return make_pair(cur, false);
34.       }
35.     }
36. 
37.     //2.走到这里,说明kv在树中不存在,需要插入kv,并且cur已经为空,parent已经是叶子节点了
38.     Node* newNode = new Node(kv);
39.     newNode->_col = RED;
40.     if (kot(parent->_data) < kot(data))
41.     {
42.       //kv比parent值大,插入到parent的右边
43.       parent->_right = newNode;
44.       newNode->_parent = parent;
45.     }
46.     else
47.     {
48.       //kv比parent值小,插入到parent的左边
49.       parent->_left = newNode;
50.       newNode->_parent = parent;
51.     }
52.     cur = newNode;
53. 
54.     //如果父亲存在,且父亲颜色为红就要处理
55.     while (parent && parent->_col == RED)
56.     {
57.       //情况一和情况二、三的区别关键看叔叔
58.       Node* grandfather = parent->_parent;//当父亲是红色时,根据规则(2)根节点一定是黑色,祖父一定存在
59.       if (parent == grandfather->_left)//父亲是祖父的左子树
60.       {
61.         Node* uncle = grandfather->_right;
62.         //情况一:叔叔存在且为红
63.         if (uncle->_col == RED)
64.         {
65.           parent->_col = uncle->_col = BLACK;
66.           grandfather->_col = RED;
67. 
68.           //继续向上调整
69.           cur = grandfather;
70.           parent = cur->_parent;
71.         }
72.         else//情况二+情况三:叔叔不存在或叔叔存在且为黑
73.         {
74.           //情况二:单旋
75.           if (cur == parent->_left)
76.           {
77.             RotateR(grandfather);
78.             parent->_col = BLACK;
79.             grandfather->_col = RED;
80.           }
81.           else//情况三:双旋
82.           {
83.             RotateL(parent);
84.             RotateR(grandfather);
85.             cur->_col = BLACK;
86.             grandfather->_col = RED;
87.           }
88.           break;//插入结束
89.         }
90.       }
91.       else//父亲是祖父的右子树
92.       {
93.         Node* uncle = grandfather->_left;
94.         //情况一:叔叔存在且为红
95.         if (uncle && uncle->_col == RED)
96.         {
97.           parent->_col = uncle->_col = BLACK;
98.           grandfather->_col = RED;
99. 
100.          //继续往上调整
101.          cur = grandfather;
102.          parent = grandfather->_parent;
103.        }
104.        else//情况二+情况三:叔叔不存在或叔叔存在且为黑
105.        {
106.          //情况二:单旋
107.          if (cur == parent->_right)
108.          {
109.            RotateL(grandfather);
110.            parent->_col = BLACK;
111.            grandfather->_col = RED;
112.          }
113.          else//情况三:双旋
114.          {
115.            RotateR(parent);
116.            RotateL(grandfather);
117.            cur->_col = BLACK;
118.            grandfather->_col = RED;
119.          }
120.          break;//插入结束
121.        }
122.      }
123. 
124.    }
125.    _root->_col = BLACK;
126. 
127.    return make_pair(newNode, true);
128.  }
129. 
130.  void RotateR(Node* parent)
131.  {
132.    Node* subL = parent->_left;
133.    Node* subLR = nullptr;
134. 
135.    if (subL)
136.    {
137.      subLR = subL->_right;
138.    }
139.    //1.左子树的右子树变我的左子树
140.    parent->_left = subLR;
141. 
142.    if (subLR)
143.    {
144.      subLR->_parent = parent;
145.    }
146. 
147.    //左子树变父亲
148.    subL->_right = parent;
149.    Node* parentParent = parent->_parent;
150.    parent->_parent = subL;
151. 
152. 
153.    if (parent == _root)//parent是根
154.    {
155.      _root = subL;
156.      _root->_parent = nullptr;
157.    }
158.    else//parent不是根,是子树
159.    {
160.      if (parentParent->_left == parent)
161.      {
162.        //parent是自己父亲的左子树,将subL作为parent父亲的左孩子
163.        parentParent->_left = subL;
164.      }
165.      else
166.      {
167.        //parent是自己父亲的右子树,将subL作为parent父亲的右孩子
168.        parentParent->_right = subL;
169.      }
170. 
171.      //subL的父亲就是parent的父亲
172.      subL->_parent = parentParent;
173.    }
174.  }
175. 
176.  void RotateL(Node* parent)
177.  {
178.    Node* subR = parent->_right;
179.    Node* subRL = nullptr;
180. 
181.    if (subR)
182.    {
183.      subRL = subR->_left;
184.    }
185. 
186.    //1.右子树的左子树变我的右子树
187.    parent->_right = subRL;
188. 
189.    if (subRL)
190.    {
191.      subRL->_parent = parent;
192.    }
193. 
194.    //2.右子树变父亲
195.    subR->_left = parent;
196.    Node* parentParent = parent->_parent;
197.    parent->_parent = subR;
198. 
199.    if (parent == _root)//parent是根
200.    {
201.      _root = parent;
202.      _root->_parent = nullptr;
203.    }
204.    else//parent不是根,是子树
205.    {
206.      if (parentParent->_left == parent)
207.      {
208.        //parent是自己父亲的左子树,将subR作为parent父亲的左孩子
209.        parentParent->_left = subR;
210.      }
211.      else
212.      {
213.        //parent是自己父亲的右子树,将subR作为parent父亲的右孩子
214.        parentParent->_right = subR;
215.      }
216. 
217.      //subR的父亲就是parent的父亲
218.      subR->_parent = parentParent;
219.    }
220.  }

(5)修改红黑树查找

1.  //查找
2.  Node* Find(const K& key)
3.  {
4.    KeyOfT kot;
5.    Node* cur = _root;
6.    while (cur)
7.    {
8.      if (kot(cur->_data) < key)
9.      {
10.         cur = cur->_right;
11.       }
12.       else if (kot(cur->_data) > key)
13.       {
14.         cur = cur->_left;
15.       }
16.       else
17.       {
18.         return cur;
19.       }
20.     }
21.     return nullptr;//空树,直接返回
22.   }


相关文章
|
14天前
|
缓存 算法 程序员
C++STL底层原理:探秘标准模板库的内部机制
🌟蒋星熠Jaxonic带你深入STL底层:从容器内存管理到红黑树、哈希表,剖析迭代器、算法与分配器核心机制,揭秘C++标准库的高效设计哲学与性能优化实践。
C++STL底层原理:探秘标准模板库的内部机制
|
7月前
|
编译器 C++ 容器
【c++丨STL】基于红黑树模拟实现set和map(附源码)
本文基于红黑树的实现,模拟了STL中的`set`和`map`容器。通过封装同一棵红黑树并进行适配修改,实现了两种容器的功能。主要步骤包括:1) 修改红黑树节点结构以支持不同数据类型;2) 使用仿函数适配键值比较逻辑;3) 实现双向迭代器支持遍历操作;4) 封装`insert`、`find`等接口,并为`map`实现`operator[]`。最终,通过测试代码验证了功能的正确性。此实现减少了代码冗余,展示了模板与仿函数的强大灵活性。
191 2
|
7月前
|
存储 算法 C++
【c++丨STL】map/multimap的使用
本文详细介绍了STL关联式容器中的`map`和`multimap`的使用方法。`map`基于红黑树实现,内部元素按键自动升序排列,存储键值对,支持通过键访问或修改值;而`multimap`允许存在重复键。文章从构造函数、迭代器、容量接口、元素访问接口、增删操作到其他操作接口全面解析了`map`的功能,并通过实例演示了如何用`map`统计字符串数组中各元素的出现次数。最后对比了`map`与`set`的区别,强调了`map`在处理键值关系时的优势。
362 73
|
4月前
|
编译器 C++ 容器
用一棵红黑树同时封装出map和set
再完成上面的代码后,我们的底层代码已经完成了,这时候已经是一个底层STL的红黑树了,已经已符合库里面的要求了,这时候我们是需要给他穿上对应的“衣服”,比如穿上set的“衣服”,那么这个穿上set的“衣服”,那么他就符合库里面set的要求了,同样map一样,这时候我们就需要实现set与map了。因此,上层容器map需要向底层红黑树提供一个仿函数,用于获取T当中的键值Key,这样一来,当底层红黑树当中需要比较两个结点的键值时,就可以通过这个仿函数来获取T当中的键值了。我们就可以使用仿函数了。
51 0
|
6天前
|
存储 JavaScript Java
(Python基础)新时代语言!一起学习Python吧!(四):dict字典和set类型;切片类型、列表生成式;map和reduce迭代器;filter过滤函数、sorted排序函数;lambda函数
dict字典 Python内置了字典:dict的支持,dict全称dictionary,在其他语言中也称为map,使用键-值(key-value)存储,具有极快的查找速度。 我们可以通过声明JS对象一样的方式声明dict
44 1
|
3月前
|
存储 缓存 JavaScript
Set和Map有什么区别?
Set和Map有什么区别?
282 1
|
4月前
|
存储 JavaScript 前端开发
for...of循环在遍历Set和Map时的注意事项有哪些?
for...of循环在遍历Set和Map时的注意事项有哪些?
270 121
|
4月前
|
存储 C++ 容器
unordered_set、unordered_multiset、unordered_map、unordered_multimap的介绍及使用
unordered_set是不按特定顺序存储键值的关联式容器,其允许通过键值快速的索引到对应的元素。在unordered_set中,元素的值同时也是唯一地标识它的key。在内部,unordered_set中的元素没有按照任何特定的顺序排序,为了能在常数范围内找到指定的key,unordered_set将相同哈希值的键值放在相同的桶中。unordered_set容器通过key访问单个元素要比set快,但它通常在遍历元素子集的范围迭代方面效率较低。它的迭代器至少是前向迭代器。前向迭代器的特性。
195 0
|
4月前
|
存储 编译器 容器
set、map、multiset、multimap的介绍及使用以及区别,注意事项
set是按照一定次序存储元素的容器,使用set的迭代器遍历set中的元素,可以得到有序序列。set当中存储元素的value都是唯一的,不可以重复,因此可以使用set进行去重。set默认是升序的,但是其内部默认不是按照大于比较,而是按照小于比较。set中的元素不能被修改,因为set在底层是用二叉搜索树来实现的,若是对二叉搜索树当中某个结点的值进行了修改,那么这棵树将不再是二叉搜索树。
206 0