【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.   }


相关文章
|
11天前
|
存储 C++ 容器
C++STL(标准模板库)处理学习应用案例
【4月更文挑战第8天】使用C++ STL,通过`std:vector`存储整数数组 `{5, 3, 1, 4, 2}`,然后利用`std::sort`进行排序,输出排序后序列:`std:vector<int> numbers; numbers = {5, 3, 1, 4, 2}; std:sort(numbers.begin(), numbers.end()); for (int number : numbers) { std::cout << number << " "; }`
17 2
|
7天前
|
存储 算法 C++
【C++初阶】STL详解(九) priority_queue的使用与模拟实现
【C++初阶】STL详解(九) priority_queue的使用与模拟实现
20 0
|
7天前
|
存储 算法 编译器
【C++初阶】STL详解(三)vector的介绍与使用
【C++初阶】STL详解(三)vector的介绍与使用
23 0
|
7天前
|
存储 编译器 C++
【C++初阶】STL详解(四)vector的模拟实现
【C++初阶】STL详解(四)vector的模拟实现
33 1
|
12天前
|
存储 算法 C语言
【C++初阶】8. STL初阶 + String类
【C++初阶】8. STL初阶 + String类
47 1
|
16天前
|
存储 JavaScript 前端开发
set和map的区别
set和map的区别
17 4
|
23天前
|
存储 C++
【C++练级之路】【Lv.16】红黑树(冰与火的碰撞,红与黑的史诗)
【C++练级之路】【Lv.16】红黑树(冰与火的碰撞,红与黑的史诗)
|
23天前
|
算法 C++ 容器
【C++练级之路】【Lv.10】【STL】priority_queue类和反向迭代器的模拟实现
【C++练级之路】【Lv.10】【STL】priority_queue类和反向迭代器的模拟实现
|
23天前
|
C++ 容器
【C++练级之路】【Lv.9】【STL】stack类和queue类的模拟实现
【C++练级之路】【Lv.9】【STL】stack类和queue类的模拟实现
|
25天前
|
存储 算法 C语言
【C++入门到精通】C++入门 —— map & multimap (STL)
之前我们学习了C++的基础和一些概念,现在将探讨重要的STL组件——map与multimap。map是关联容器,提供有序键值对存储,基于红黑树,支持高效查找、插入和删除。每个键唯一对应一个值。multimap则允许键的重复。两者都提供迭代器支持,但map的键是唯一的,而multimap允许键重复,插入和查找效率不同。更多详情,请查阅官方文档。祝学习愉快!
12 0