当前位置: 首页 > news >正文

【C++】封装红黑树实现的map和set

前言

这篇博客我们将上篇博客实现的红黑树来封装成自己实现的set和map,来模拟一下库里的map和set

💓 个人主页:小张同学zkf

⏩ 文章专栏:C++

若有问题 评论区见📝

🎉欢迎大家点赞👍收藏⭐文章

 

 

 

1.源码及框架分析

SGI-STL30版本源代码,map和set的源代码在map/set/stl_map.h/stl_set.h/stl_tree.h等⼏个头⽂件
中。
map和set的实现结构框架核⼼部分截取出来如下:
1 // set
2 # ifndef __SGI_STL_INTERNAL_TREE_H
3 # include <stl_tree.h>
4 # endif
5 # include <stl_set.h>
6 # include <stl_multiset.h>
7
8 // map
9 # ifndef __SGI_STL_INTERNAL_TREE_H
10 # include <stl_tree.h>
11 # endif
12 # include <stl_map.h>
13 # include <stl_multimap.h>
14
15 // stl_set.h
16 template < class Key , class Compare = less<Key>, class Alloc = alloc>
17 class set {
18 public :
19 // typedefs:
20 typedef Key key_type;
21 typedef Key value_type;
22 private :
23 typedef rb_tree<key_type, value_type,
24 identity<value_type>, key_compare, Alloc> rep_type;
25 rep_type t; // red-black tree representing set
26 };
27
28 // stl_map.h
29 template < class Key , class T , class Compare = less<Key>, class Alloc = alloc>
30 class map {
31 public :
32 // typedefs:
33 typedef Key key_type;
34 typedef T mapped_type;
35 typedef pair< const Key, T> value_type;
36 private :
37 typedef rb_tree<key_type, value_type,
38 select1st<value_type>, key_compare, Alloc> rep_type;
39 rep_type t; // red-black tree representing map
40 };
41
42 // stl_tree.h
43 struct __rb_tree_node_base
44 {
45 typedef __rb_tree_color_type color_type;
46 typedef __rb_tree_node_base* base_ptr;
47
48 color_type color;
49 base_ptr parent;
50 base_ptr left;
51 base_ptr right;
52 };
53
54 // stl_tree.h
55 template < class Key , class Value , class KeyOfValue , class Compare , class Alloc
= alloc>
56 class rb_tree {
57 protected :
58 typedef void * void_pointer;
59 typedef __rb_tree_node_base* base_ptr;
60 typedef __rb_tree_node<Value> rb_tree_node;
61 typedef rb_tree_node* link_type;
62 typedef Key key_type;
63 typedef Value value_type;
64 public :
65 // insert ⽤的是第⼆个模板参数左形参
66 pair<iterator, bool > insert_unique ( const value_type& x);
67
68 // erase find ⽤第⼀个模板参数做形参
69 size_type erase ( const key_type& x);
70 iterator find ( const key_type& x);
71 protected :
72 size_type node_count; // keeps track of size of tree
73 link_type header;
74 };
75
76 template < class Value >
77 struct __rb_tree_node : public __rb_tree_node_base
78 {
79 typedef __rb_tree_node<Value>* link_type;
80 Value value_field;
81 };
通过下图对框架的分析,我们可以看到源码中rb_tree⽤了⼀个巧妙的泛型思想实现,rb_tree是实
现key的搜索场景,还是key/value的搜索场景不是直接写死的,⽽是由第⼆个模板参数Value决定
_rb_tree_node中存储的数据类型。
set实例化rb_tree时第⼆个模板参数给的是key,map实例化rb_tree时第⼆个模板参数给的是
pair<const key, T>,这样⼀颗红⿊树既可以实现key搜索场景的set,也可以实现key/value搜索场
景的map。
要注意⼀下,源码⾥⾯模板参数是⽤T代表value,⽽内部写的value_type不是我们我们⽇常
key/value场景中说的value,源码中的value_type反⽽是红⿊树结点中存储的真实的数据的类型。
rb_tree第⼆个模板参数Value已经控制了红⿊树结点中存储的数据类型,为什么还要传第⼀个模板
参数Key呢?尤其是set,两个模板参数是⼀样的,要注意的是对于 map和set,find/erase时的函数参数都是Key,所以第⼀个模板参数是传给find/erase等函数做形参的类型的。对于set⽽⾔两个参数是⼀样的,但是对于map⽽⾔就完全不⼀样了,map insert的是pair对象,但是find和ease的是Key对象。
吐槽⼀下,这⾥源码命名⻛格⽐较乱,set模板参数⽤的Key命名,map⽤的是Key和T命名,⽽
rb_tree⽤的⼜是Key和Value,可⻅⼤佬有时写代码也不规范,乱弹琴。

2.模拟实现map和set

2.1实现出复⽤红⿊树的框架,并⽀持insert

参考源码框架,map和set复⽤之前我们实现的红⿊树。
我们这⾥相⽐源码调整⼀下,key参数就⽤K,value参数就⽤V,红⿊树中的数据类型,我们使⽤
T。
其次因为RBTree实现了泛型不知道T参数导致是K,还是pair<K, V>,那么insert内部进⾏插⼊逻辑
⽐较时,就没办法进⾏⽐较,因为pair的默认⽀持的是key和value⼀起参与⽐较,我们需要时的任
何时候只⽐较key,所以我们在map和set层分别实现⼀个MapKeyOfT和SetKeyOfT的仿函数传给
RBTree的KeyOfT,然后RBTree中通过KeyOfT仿函数取出T类型对象中的key,再进⾏⽐较,具体
细节参考如下代码实现。
// 源码中 pair ⽀持的 < 重载实现
1template < class T1 , class T2 >
2bool operator < ( const pair<T1,T2>& lhs, const pair<T1,T2>& rhs)
3{ return lhs.first<rhs.first || (!(rhs.first<lhs.first) &&
lhs.second<rhs.second); }
4
5// Mymap.h
6namespace zkf
7{
7template < class K , class V >
8class map
9{
10struct MapKeyOfT
11{
12const K& operator ()( const pair<K, V>& kv)
13{
14return kv.first;
15}
16};
17public :
18bool insert ( const pair<K, V>& kv)
19{
20return _t . Insert (kv);
21}
22private :
23RBTree<K, pair<K, V>, MapKeyOfT> _t ;
24};
25}
26// Myset.h
27namespace zkf
28{
29template < class K >
33 class set
34 {
35 struct SetKeyOfT
36 {
37 const K& operator ()( const K& key)
38 {
39 return key;
40 }
41 };
42 public :
43 bool insert ( const K& key)
44 {
45 return _t . Insert (key);
46 }
47 private :
48 RBTree<K, K, SetKeyOfT> _t ;
49 };
50 }
51
52 // RBTree.h
53 enum Colour
54 {
55 RED,
56 BLACK
57 };
58
59 template < class T >
60 struct RBTreeNode
61 {
62 T _data;
63
64 RBTreeNode<T>* _left;
65 RBTreeNode<T>* _right;
66 RBTreeNode<T>* _parent;
67 Colour _col;
68
69 RBTreeNode ( const T& data)
70 : _data(data)
71 , _left( nullptr )
72 , _right( nullptr )
73 , _parent( nullptr )
74 {}
75 };
76
77 // 实现步骤:
78 // 1 、实现红⿊树
79 // 2 、封装 map set 框架,解决 KeyOfT 80 // 3 iterator
81 // 4 const_iterator
82 // 5 key 不⽀持修改的问题
83 // 6 operator[]
84 template < class K , class T , class KeyOfT >
85 class RBTree
86 {
87 private :
88 typedef RBTreeNode<T> Node;
89 Node* _root = nullptr ;
90
91 public :
92 bool Insert ( const T& data)
93 {
94 if (_root == nullptr )
95 {
96 _root = new Node (data);
97 _root->_col = BLACK;
98 return true ;
99 }
100
101 KeyOfT kot;
102 Node* parent = nullptr ;
103 Node* cur = _root;
104 while (cur)
105 {
106 if ( kot (cur->_data) < kot (data))
107 {
108 parent = cur;
109 cur = cur->_right;
110 }
111 else if ( kot (cur->_data) > kot (data))
112 {
113 parent = cur;
114 cur = cur->_left;
115 }
116 else
117 {
118 return false ;
119 }
120 }
121
122 cur = new Node (data);
123 Node* newnode = cur;
124
125 // 新增结点。颜⾊给红⾊
126 cur->_col = RED; 127 if ( kot (parent->_data) < kot (data))
128 {
129 parent->_right = cur;
130 }
131 else
132 {
133 parent->_left = cur;
134 }
135 cur->_parent = parent;
136
137 //...
138
139 return true ;
140 }
141 }

2.2支持iterator实现

iterator核⼼源代码

1 struct __rb_tree_base_iterator
2 {
3 typedef __rb_tree_node_base::base_ptr base_ptr;
4 base_ptr node;
5
6 void increment ()
7 {
8 if (node->right != 0 ) {
9 node = node->right;
10 while (node->left != 0 )
11 node = node->left;
12 }
13 else {
14 base_ptr y = node->parent;
15 while (node == y->right) {
16 node = y;
17 y = y->parent;
18 }
19 if (node->right != y)
20 node = y;
21 }
22 }
23
24 void decrement ()
25 { 26 if (node->color == __rb_tree_red &&
27 node->parent->parent == node)
28 node = node->right;
29 else if (node->left != 0 ) {
30 base_ptr y = node->left;
31 while (y->right != 0 )
32 y = y->right;
33 node = y;
34 }
35 else {
36 base_ptr y = node->parent;
37 while (node == y->left) {
38 node = y;
39 y = y->parent;
40 }
41 node = y;
42 }
43 }
44 };
45
46 template < class Value , class Ref , class Ptr >
47 struct __rb_tree_iterator : public __rb_tree_base_iterator
48 {
49 typedef Value value_type;
50 typedef Ref reference;
51 typedef Ptr pointer;
52 typedef __rb_tree_iterator<Value, Value&, Value*> iterator;
53 __rb_tree_iterator() {}
54 __rb_tree_iterator(link_type x) { node = x; }
55 __rb_tree_iterator( const iterator& it) { node = it.node; }
56
57 reference operator *() const { return link_type (node)->value_field; }
58 # ifndef __SGI_STL_NO_ARROW_OPERATOR
59 pointer operator ->() const { return &( operator *()); }
60 # endif /* __SGI_STL_NO_ARROW_OPERATOR */
61
62 self& operator ++() { increment (); return * this ; }
63 self& operator --() { decrement (); return * this ; }
64
65 inline bool operator ==( const __rb_tree_base_iterator& x,
66 const __rb_tree_base_iterator& y) {
67 return x.node == y.node;
68 }
69
70 inline bool operator !=( const __rb_tree_base_iterator& x,
71 const __rb_tree_base_iterator& y) {
72 return x.node != y.node;
73 }

 

iterator实现思路分析
iterator实现的⼤框架跟list的iterator思路是⼀致的,⽤⼀个类型封装结点的指针,再通过重载运算
符实现,迭代器像指针⼀样访问的⾏为。
这⾥的难点是operator++和operator--的实现。之前使⽤部分,我们分析了,map和set的迭代器⾛
的是中序遍历,左⼦树->根结点->右⼦树,那么begin()会返回中序第⼀个结点的iterator也就是10
所在结点的迭代器。
迭代器++的核⼼逻辑就是不看全局,只看局部,只考虑当前中序局部要访问的下⼀个结点。
迭代器++时,如果it指向的结点的右⼦树不为空,代表当前结点已经访问完了,要访问下⼀个结点
是右⼦树的中序第⼀个,⼀棵树中序第⼀个是最左结点,所以直接找右⼦树的最左结点即可。
迭代器++时,如果it指向的结点的右⼦树空,代表当前结点已经访问完了且当前结点所在的⼦树也
访问完了,要访问的下⼀个结点在当前结点的祖先⾥⾯,所以要沿着当前结点到根的祖先路径向上
找。
如果当前结点是⽗亲的左,根据中序左⼦树->根结点->右⼦树,那么下⼀个访问的结点就是当前结
点的⽗亲;如下图:it指向25,25右为空,25是30的左,所以下⼀个访问的结点就是30。
如果当前结点是⽗亲的右,根据中序左⼦树->根结点->右⼦树,当前当前结点所在的⼦树访问完
了,当前结点所在⽗亲的⼦树也访问完了,那么下⼀个访问的需要继续往根的祖先中去找,直到找
到孩⼦是⽗亲左的那个祖先就是中序要问题的下⼀个结点。如下图:it指向15,15右为空,15是10
的右,15所在⼦树话访问完了,10所在⼦树也访问完了,继续往上找,10是18的左,那么下⼀个
访问的结点就是18。
end()如何表⽰呢?如下图:当it指向50时,++it时,50是40的右,40是30的右,30是18的右,18
到根没有⽗亲,没有找到孩⼦是⽗亲左的那个祖先,这是⽗亲为空了,那我们就把it中的结点指针
置为nullptr,我们⽤nullptr去充当end。需要注意的是stl源码空,红⿊树增加了⼀个哨兵位头结点
做为end(),这哨兵位头结点和根互为⽗亲,左指向最左结点,右指向最右结点。相⽐我们⽤
nullptr作为end(),差别不⼤,他能实现的,我们也能实现。只是--end()判断到结点时空,特殊处
理⼀下,让迭代器结点指向最右结点。具体参考迭代器--实现。
迭代器--的实现跟++的思路完全类似,逻辑正好反过来即可,因为他访问顺序是右⼦树->根结点->
左⼦树,具体参考下⾯代码实现。
set的iterator也不⽀持修改,我们把set的第⼆个模板参数改成const K即可, RBTree<K,
const K , SetKeyOfT> _t;
map的iterator不⽀持修改key但是可以修改value,我们把map的第⼆个模板参数pair的第⼀个参
数改成const K即可, RBTree<K, pair<const K, V> , MapKeyOfT> _t;
⽀持完整的迭代器还有很多细节需要修改,具体参考下⾯题的代码。

 

 

2.3map⽀持[]

map要⽀持[]主要需要修改insert返回值⽀持,修改RBtree中的insert返回值为
pair<Iterator, bool> Insert(const T& data)
有了insert⽀持[]实现就很简单了,具体参考下⾯代码实现
bit::map和bit::set代码实现
// Myset.h
# include "RBTree.h"
namespace bit
{
template < class K >
class set
{
struct SetKeyOfT
{
const K& operator ()( const K& key)
{
return key;
}
};
public :
typedef typename RBTree<K, const K, SetKeyOfT>::Iterator iterator;
typedef typename RBTree<K, const K, SetKeyOfT>::ConstIterator
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)
{
42 return _t . Insert (key);
43 }
44
45 iterator find ( const K& key)
46 {
47 return _t . Find (key);
48 }
49
50 private :
51 RBTree<K, const K, SetKeyOfT> _t ;
52 };
53
54 void Print ( const set< int >& s)
55 {
56 set< int >::const_iterator it = s. end ();
57 while (it != s. begin ())
58 {
59 --it;
60 // 不⽀持修改
61 //*it += 2;
62
63 cout << *it << " " ;
64 }
65 cout << endl;
66 }
67
68 void test_set ()
69 {
70 set< int > s;
71 int a[] = { 4 , 2 , 6 , 1 , 3 , 5 , 15 , 7 , 16 , 14 };
72 for ( auto e : a)
73 {
74 s. insert (e);
75 }
76
77 for ( auto e : s)
78 {
79 cout << e << " " ;
80 }
81 cout << endl;
82
83 Print (s);
84 }
85 }
86
87 // Mymap.h
88 # include "RBTree.h" 89 namespace bit
90 {
91 template < class K , class V >
92 class map
93 {
94 struct MapKeyOfT
95 {
96 const K& operator ()( const pair<K, V>& kv)
97 {
98 return kv.first;
99 }
100 };
101 public :
102 typedef typename RBTree<K, pair< const K, V>, MapKeyOfT>::Iterator
iterator;
103 typedef typename RBTree<K, pair< const K, V>, MapKeyOfT>::ConstIterator
const_iterator;
104
105 iterator begin ()
106 {
107 return _t . Begin ();
108 }
109
110 iterator end ()
111 {
112 return _t . End ();
113 }
114
115 const_iterator begin () const
116 {
117 return _t . Begin ();
118 }
119
120 const_iterator end () const
121 {
122 return _t . End ();
123 }
124
125 pair<iterator, bool > insert ( const pair<K, V>& kv)
126 {
127 return _t . Insert (kv);
128 }
129
130 iterator find ( const K& key)
131 {
132 return _t . Find (key);
133 } 134
135 V& operator []( const K& key)
136 {
137 pair<iterator, bool > ret = insert ( make_pair (key, V ()));
138 return ret.first->second;
139 }
140
141 private :
142 RBTree<K, pair< const K, V>, MapKeyOfT> _t ;
143 };
144
145 void test_map ()
146 {
147 map<string, string> dict;
148 dict. insert ({ "sort" , " 排序 " });
149 dict. insert ({ "left" , " 左边 " });
150 dict. insert ({ "right" , " 右边 " });
151
152 dict[ "left" ] = " 左边,剩余 " ;
153 dict[ "insert" ] = " 插⼊ " ;
154 dict[ "string" ];
155
156 map<string, string>::iterator it = dict. begin ();
157 while (it != dict. end ())
158 {
159 // 不能修改 first ,可以修改 second
160 //it->first += 'x';
161 it->second += 'x' ;
162
163 cout << it->first << ":" << it->second << endl;
164 ++it;
165 }
166 cout << endl;
167 }
168 }
169
170 // RBtree.h
171 enum Colour
172 {
173 RED,
174 BLACK
175 };
176
177 template < class T >
178 struct RBTreeNode
179 {
180 T _data; 181
182 RBTreeNode<T>* _left;
183 RBTreeNode<T>* _right;
184 RBTreeNode<T>* _parent;
185 Colour _col;
186
187 RBTreeNode ( const T& data)
188 : _data(data)
189 , _left( nullptr )
190 , _right( nullptr )
191 , _parent( nullptr )
192 {}
193 };
194
195 template < class T , class Ref , class Ptr >
196 struct RBTreeIterator
197 {
198 typedef RBTreeNode<T> Node;
199 typedef RBTreeIterator<T, Ref, Ptr> Self;
200
201 Node* _node;
202 Node* _root;
203
204 RBTreeIterator (Node* node, Node* root)
205 :_node(node)
206 ,_root(root)
207 {}
208
209 Self& operator ++()
210 {
211 if (_node->_right)
212 {
213 // 右不为空,右⼦树最左结点就是中序第⼀个
214 Node* leftMost = _node->_right;
215 while (leftMost->_left)
216 {
217 leftMost = leftMost->_left;
218 }
219
220 _node = leftMost;
221 }
222 else
223 {
224 // 孩⼦是⽗亲左的那个祖先
225 Node* cur = _node;
226 Node* parent = cur->_parent;
227 while (parent && cur == parent->_right) 228 {
229 cur = parent;
230 parent = cur->_parent;
231 }
232
233 _node = parent;
234 }
235
236 return * this ;
237 }
238
239 Self& operator --()
240 {
241 if (_node == nullptr ) // end()
242 {
243 // --end() ,特殊处理,⾛到中序最后⼀个结点,整棵树的最右结点
244 Node* rightMost = _root;
245 while (rightMost && rightMost->_right)
246 {
247 rightMost = rightMost->_right;
248 }
249
250 _node = rightMost;
251 }
252 else if (_node->_left)
253 {
254 // 左⼦树不为空,中序左⼦树最后⼀个
255 Node* rightMost = _node->_left;
256 while (rightMost->_right)
257 {
258 rightMost = rightMost->_right;
259 }
260
261 _node = rightMost;
262 }
263 else
264 {
265 // 孩⼦是⽗亲右的那个祖先
266 Node* cur = _node;
267 Node* parent = cur->_parent;
268 while (parent && cur == parent->_left)
269 {
270 cur = parent;
271 parent = cur->_parent;
272 }
273
274 _node = parent; 275
276 }
277
278 return * this ;
279 }
280
281 Ref operator *()
282 {
283 return _node->_data;
284 }
285
286 Ptr operator ->()
287 {
288 return &_node->_data;
289 }
290
291 bool operator != ( const Self& s) const
292 {
293 return _node != s._node;
294 }
295
296 bool operator == ( const Self& s) const
297 {
298 return _node == s._node;
299 }
300 };
301
302 template < class K , class T , class KeyOfT >
303 class RBTree
304 {
305 typedef RBTreeNode<T> Node;
306 public :
307 typedef RBTreeIterator<T, T&, T*> Iterator;
308 typedef RBTreeIterator<T, const T&, const T*> ConstIterator;
309
310 Iterator Begin ()
311 {
312 Node* leftMost = _root;
313 while (leftMost && leftMost->_left)
314 {
315 leftMost = leftMost->_left;
316 }
317
318 return Iterator (leftMost, _root);
319 }
320
321 Iterator End () 322 {
323 return Iterator ( nullptr , _root);
324 }
325
326 ConstIterator Begin () const
327 {
328 Node* leftMost = _root;
329 while (leftMost && leftMost->_left)
330 {
331 leftMost = leftMost->_left;
332 }
333
334 return ConstIterator (leftMost, _root);
335 }
336
337 ConstIterator End () const
338 {
339 return ConstIterator ( nullptr , _root);
340 }
341
342 RBTree () = default ;
343
344 RBTree ( const RBTree& t)
345 {
346 _root = Copy (t._root);
347 }
348
349 RBTree& operator =(RBTree t)
350 {
351 swap (_root, t._root);
352 return * this ;
353 }
354
355 ~ RBTree ()
356 {
357 Destroy (_root);
358 _root = nullptr ;
359 }
360
361 pair<Iterator, bool > Insert ( const T& data)
362 {
363 if (_root == nullptr )
364 {
365 _root = new Node (data);
366 _root->_col = BLACK;
367 return make_pair ( Iterator (_root, _root), true );
368 }
369
370 KeyOfT kot;
371 Node* parent = nullptr ;
372 Node* cur = _root;
373 while (cur)
374 {
375 if ( kot (cur->_data) < kot (data))
376 {
377 parent = cur;
378 cur = cur->_right;
379 }
380 else if ( kot (cur->_data) > kot (data))
381 {
382 parent = cur;
383 cur = cur->_left;
384 }
385 else
386 {
387 return make_pair ( Iterator (cur, _root), false );
388 }
389 }
390
391 cur = new Node (data);
392 Node* newnode = cur;
393
394 // 新增结点。颜⾊红⾊给红⾊
395 cur->_col = RED;
396 if ( kot (parent->_data) < kot (data))
397 {
398 parent->_right = cur;
399 }
400 else
401 {
402 parent->_left = cur;
403 }
404 cur->_parent = parent;
405
406 while (parent && parent->_col == RED)
407 {
408 Node* grandfather = parent->_parent;
409 // g
410 // p u
411 if (parent == grandfather->_left)
412 {
413 Node* uncle = grandfather->_right;
414 if (uncle && uncle->_col == RED)
415 { 416 // u 存在且为红 - 》变⾊再继续往上处理
417 parent->_col = uncle->_col = BLACK;
418 grandfather->_col = RED;
419
420 cur = grandfather;
421 parent = cur->_parent;
422 }
423 else
424 {
425 // u 存在且为⿊或不存在 - 》旋转 + 变⾊
426 if (cur == parent->_left)
427 {
428 // g
429 // p u
430 //c
431 // 单旋
432 RotateR (grandfather);
433 parent->_col = BLACK;
434 grandfather->_col = RED;
435 }
436 else
437 {
438 // g
439 // p u
440 // c
441 // 双旋
442 RotateL (parent);
443 RotateR (grandfather);
444
445 cur->_col = BLACK;
446 grandfather->_col = RED;
447 }
448
449 break ;
450 }
451 }
452 else
453 {
454 // g
455 // u p
456 Node* uncle = grandfather->_left;
457 // 叔叔存在且为红, - 》变⾊即可
458 if (uncle && uncle->_col == RED)
459 {
460 parent->_col = uncle->_col = BLACK;
461 grandfather->_col = RED;
462 463 // 继续往上处理
464 cur = grandfather;
465 parent = cur->_parent;
466 }
467 else // 叔叔不存在,或者存在且为⿊
468 {
469 // 情况⼆:叔叔不存在或者存在且为⿊
470 // 旋转 + 变⾊
471 // g
472 // u p
473 // c
474 if (cur == parent->_right)
475 {
476 RotateL (grandfather);
477 parent->_col = BLACK;
478 grandfather->_col = RED;
479 }
480 else
481 {
482 // g
483 // u p
484 // c
485 RotateR (parent);
486 RotateL (grandfather);
487 cur->_col = BLACK;
488 grandfather->_col = RED;
489 }
490 break ;
491 }
492 }
493 }
494
495 _root->_col = BLACK;
496
497 return make_pair ( Iterator (newnode, _root), true );
498 }
499
500 Iterator Find ( const K& key)
501 {
502 Node* cur = _root;
503 while (cur)
504 {
505 if (cur->_kv.first < key)
506 {
507 cur = cur->_right;
508 }
509 else if (cur->_kv.first > key) 510 {
511 cur = cur->_left;
512 }
513 else
514 {
515 return Iterator (cur, _root);
516 }
517 }
518
519 return End ();
520 }
521
522 private :
523 void RotateL (Node* parent)
524 {
525 Node* subR = parent->_right;
526 Node* subRL = subR->_left;
527
528 parent->_right = subRL;
529 if (subRL)
530 subRL->_parent = parent;
531
532 Node* parentParent = parent->_parent;
533
534 subR->_left = parent;
535 parent->_parent = subR;
536
537 if (parentParent == nullptr )
538 {
539 _root = subR;
540 subR->_parent = nullptr ;
541 }
542 else
543 {
544 if (parent == parentParent->_left)
545 {
546 parentParent->_left = subR;
547 }
548 else
549 {
550 parentParent->_right = subR;
551 }
552
553 subR->_parent = parentParent;
554 }
555 }
556 557 void RotateR (Node* parent)
558 {
559 Node* subL = parent->_left;
560 Node* subLR = subL->_right;
561
562 parent->_left = subLR;
563 if (subLR)
564 subLR->_parent = parent;
565
566 Node* parentParent = parent->_parent;
567
568 subL->_right = parent;
569 parent->_parent = subL;
570
571 if (parentParent == nullptr )
572 {
573 _root = subL;
574 subL->_parent = nullptr ;
575 }
576 else
577 {
578 if (parent == parentParent->_left)
579 {
580 parentParent->_left = subL;
581 }
582 else
583 {
584 parentParent->_right = subL;
585 }
586
587 subL->_parent = parentParent;
588 }
589
590 }
591
592 void Destroy (Node* root)
593 {
594 if (root == nullptr )
595 return ;
596
597 Destroy (root->_left);
598 Destroy (root->_right);
599 delete root;
600 }
601
602 Node* Copy (Node* root)
603 { 604 if (root == nullptr )
605 return nullptr ;
606
607 Node* newRoot = new Node (root->_kv);
608 newRoot->_left = Copy (root->_left);
609 newRoot->_right = Copy (root->_right);
610
611 return newRoot;
612 }
613
614 private :
615 Node* _root = nullptr ;
616 };

 

 结束语

map和set详细知识这几篇博客算是总结完了,下片博客看看unordered_set和unordered_map.

本篇博客结束,感谢观看。

 

 

相关文章:

【C++】封装红黑树实现的map和set

前言 这篇博客我们将上篇博客实现的红黑树来封装成自己实现的set和map&#xff0c;来模拟一下库里的map和set &#x1f493; 个人主页&#xff1a;小张同学zkf ⏩ 文章专栏&#xff1a;C 若有问题 评论区见&#x1f4dd; &#x1f389;欢迎大家点赞&#x1f44d;收藏⭐文章 1.源…...

【SSM】mybatis的增删改查

目录 代理Dao方式的增删改查 1. 创建项目 $$1. 在sql.xml里增加日志代码以及user的mapper资源。 $$ 2. 在usermapper里引入接口。 $$3. 在测试类中引入以下代码&#xff0c;并修改其中名字。 $$ 4. 实例对象User.java里属性要与表中列严格对应。 2. 查询 1>. 查询所有 …...

macos下brew安装redis

首先确保已安装brew&#xff0c;接下来搜索资源&#xff0c;在终端输入如下命令&#xff1a; brew search redis 演示如下&#xff1a; 如上看到有redis资源&#xff0c;下面进行安装&#xff0c;执行下面的命令&#xff1a; brew install redis 演示效果如下&#xff1a; …...

鸿蒙修饰符

文章目录 一、引言1.1 什么是修饰符1.2 修饰符在鸿蒙开发中的重要性1.3 修饰符的作用机制 二、UI装饰类修饰符2.1 Styles修饰符2.1.1 基本概念和使用场景2.1.2 使用示例2.1.3 最佳实践 2.2 Extend修饰符2.2.1 基本概念2.2.2 使用示例2.2.3 Extend vs Styles 对比2.2.4 使用建议…...

【Linux】Linux2.6内核进程调度队列与调度原理

目录 一、进程管理中的部分概念二、寄存器三、进程切换四、Linux2.6内核进程调度队列与调度原理结尾 一、进程管理中的部分概念 竞争性: 系统进程数目众多&#xff0c;而CPU资源只有少量&#xff0c;甚至1个&#xff0c;所以进程之间是具有竞争属性的。为了高效完成任务&#…...

MacOS使用VSCode编写C++程序如何配置clang编译环境

前言 这段时间在练习写C和Python&#xff0c;用vscode这个开发工具&#xff0c;调试的时候遇到一些麻烦&#xff0c;浪费了很多时间&#xff0c;因此整理了这个文档。将详细的细节描述清楚&#xff0c;避免与我遇到同样问题的人踩坑。 1.开发环境的配置 vscode的开发环境配置…...

【Spark源码分析】规则框架- `analysis`分析阶段使用的规则

analysis分析阶段使用的规则 规则批策略规则说明SubstitutionfixedPointOptimizeUpdateFields该规则优化了 UpdateFields 表达式链&#xff0c;因此看起来更像优化规则。但是&#xff0c;在处理深嵌套模式时&#xff0c;UpdateFields 表达式树可能会非常复杂&#xff0c;导致分…...

Windows和Ubuntu系统下cmake和opencv的安装和使用

以下是在Windows和Ubuntu系统下分别安装CMake并使用C配置OpenCV实现读取图片并显示功能的详细步骤&#xff1a; Windows系统 1. 安装CMake 访问CMake官方网站&#xff08;https://cmake.org/download/&#xff09;。根据你的Windows系统版本&#xff08;32位或64位&#xff…...

详解 Qt QtPDF之QPdfPageNavigator 页面跳转

文章目录 前言头文件&#xff1a; 自 Qt 6.4 起继承自&#xff1a; 属性backAvailable : const boolcurrentLocation : const QPointFcurrentPage : const intcurrentZoom : const qrealforwardAvailable : const bool 公共函数QPdfPageNavigator(QObject *parent)virtual ~QPd…...

设计模式之单例

单例可以说是设计模式中最简单的一种模式。但任何一种设计模式都是普遍经验的总结&#xff0c;都有值得思考的地方。所以单例也并不简单&#xff0c;下面让我们慢慢了解它。 单例顾名思义这个类只有一个实例。要做到这点&#xff0c;需要做到以下几点&#xff1a; &#xff08;…...

笔记软件:我来、思源笔记、Obsidian、OneNote

最近wolai的会员到期了&#xff0c;促使我更新了一下笔记软件。 首先&#xff0c;wolai作为一个笔记软件&#xff0c;我觉得有很多做得不错的方面&#xff08;否则我也不会为它付费2年了&#xff09;&#xff0c;各种功能集成得很全&#xff08;公式识别这个功能我写论文的时候…...

前端入门指南:前端模块有哪些格式?分别什么情况使用

前言 在当今的前端开发中&#xff0c;模块化是提升代码组织性和可维护性的关键手段。随着前端技术的发展&#xff0c;出现了多种模块化方案&#xff0c;每种方案都有其独特的优势和适用场景。本文将详细探讨常见的前端模块格式&#xff0c;包括全局变量、IIFE、CommonJS、AMD、…...

Vue3 常用指令解析:v-bind、v-if、v-for、v-show、v-model

Vue 是一个非常强大的前端框架&#xff0c;提供了许多常用指令来简化模板的使用。Vue 指令以 v- 开头&#xff0c;用于对 DOM 元素和组件的行为进行控制。本文将介绍 Vue 中常见的五个指令&#xff1a;v-bind、v-if、v-for、v-show 和 v-model&#xff0c;并通过实例代码来演示…...

如何查看ubuntu服务器的ssh服务是否可用

你可以通过以下几种方法检查 Ubuntu 服务器上的 SSH 服务是否可用&#xff1a; 1. 使用 systemctl 检查 SSH 服务状态 首先&#xff0c;检查 SSH 服务是否正在运行&#xff1a; sudo systemctl status ssh如果 SSH 服务正在运行&#xff0c;你会看到类似以下的输出&#xff…...

redis面试复习

1.redis是单线程还是多线程 无论什么版本工作线程就是是一个&#xff0c;6.x高版本出现了IO多线程 单线程满足redis的串行原子&#xff0c;只不过IO多线程后&#xff0c;把输入/输出放到更多的线程里区并行&#xff0c;好处&#xff1a; 1.执行的时间更短&#xff0c;更快&a…...

【人工智能基础04】线性模型

文章目录 一. 基本知识1. 线性回归1.1. 基本形式1.2. 线性回归 2. 优化方法&#xff1a;梯度下降法2.1. 梯度下降法的直观意义2.2. 随机梯度下降法 3. 分类问题3.1. 二分类&#xff1a;逻辑回归-sigmoid函数3.2. 多分类问题--softmax函数 4. 岭回归与套索回归4.1. 基础概念什么…...

使用YOLO系列txt目标检测标签的滑窗切割:批量处理图像和标签的实用工具

使用YOLO系列txt目标检测标签的滑窗切割&#xff1a;批量处理图像和标签的实用工具 使用YOLO的TXT目标检测标签的滑窗切割&#xff1a;批量处理图像和标签的实用工具背景1. 代码概述2. 滑窗切割算法原理滑窗切割步骤&#xff1a;示例&#xff1a; 3. **代码实现**1. **加载标签…...

《装甲车内气体检测“神器”:上海松柏 K-5S 电化学传感器模组详解》

《装甲车内气体检测“神器”:上海松柏 K-5S 电化学传感器模组详解》 一、引言二、K-5S 电化学传感器模组概述&#xff08;一&#xff09;产品简介&#xff08;二&#xff09;产品特点&#xff08;三&#xff09;产品适用场景 三、电化学传感器原理及优点&#xff08;一&#xf…...

【笔记】文明、现代化与价值投资

文章目录 价值投资与理性思考资管行业特点及对从业人员的道德底线要求价值投资长期来看&#xff0c;各项资产的走势投资与投机 对文明的认知对文明的计量方式狩猎文明或1.0文明农业畜牧文明或2.0文明农业文明的天花板及三次冲顶农业文明中的思想革命和制度创新 科技文明或3.0文…...

排序学习整理(1)

1.排序的概念及运用 1.1概念 排序&#xff1a;所谓排序&#xff0c;就是使⼀串记录&#xff0c;按照其中的某个或某些关键字的大小&#xff0c;递增或递减的排列起来的操作&#xff0c;以便更容易查找、组织或分析数据。 1.2运用 购物筛选排序 院校排名 1.3常见排序算法 2.实…...

提升分布式系统响应速度:分布式系统远程调用性能提升之道

目录 一、远程调用直接案例分析 二、并行调用 &#xff08;一&#xff09;核心思想 &#xff08;二&#xff09;并行调用的实现方式 1. 基本思路 2. 代码示例 3. 关键点说明 4.线程池配置建议 三、数据异构 &#xff08;一&#xff09;场景重提 &#xff08;二&…...

通过MinIO+h2non/imaginary 搭建自己的阿里云OSS

安装MinIO Docker部署MinIO对象存储服务 图片访问地址&#xff1a;http://192.168.153.138:9000/public/su7_1.jpg 安装h2non/imaginary Docker部署h2non/imaginary 处理图片地址&#xff1a;http://192.168.153.138:7000/resize?urlhttp://192.168.153.138:9000/public/su…...

.NET 9 AOT的突破 - 支持老旧Win7与XP环境

引言 随着技术的不断进步&#xff0c;微软的.NET 框架在每次迭代中都带来了令人惊喜的新特性。在.NET 9 版本中&#xff0c;一个特别引人注目的亮点是 AOT&#xff08; Ahead-of-Time&#xff09;支持&#xff0c;它允许开发人员将应用程序在编译阶段就优化为能够在老旧的 Win…...

iOS与Windows间传文件

想用数据线从 windows 手提电脑传文件入 iPhone&#xff0c;有点迂回。 参考 [1]&#xff0c;要在 windows 装 Apple Devices。装完、打开、插线之后会检测到手机&#xff0c;界面&#xff1a; 点左侧栏「文件」&#xff0c;不是就直接可以传&#xff0c;而是要通过某个应用传…...

ospf协议(动态路由协议)

ospf基本概念 定义 OSPF 是典型的链路状态路由协议&#xff0c;是目前业内使用非常广泛的 IGP 协议之一。 目前针对 IPv4 协议使用的是 OSPF Version 2 &#xff08; RFC2328 &#xff09;&#xff1b;针对 IPv6 协议使用 OSPF Version 3 &#xff08; RFC2740 &#xff09;。…...

直击高频编程考点:聚焦新版综合编程能力考查汇总

目录 一、业务性编程和广度能力考查 &#xff08;一&#xff09;基本定义 &#xff08;二&#xff09;必要性分析 二、高频考查样题&#xff08;编程扩展问法&#xff09; 考题1: 用java 代码实现一个死锁用例&#xff0c;说说怎么解决死锁问题&#xff1f;&#xff08;高…...

爬虫框架快速入门——Scrapy

适用人群&#xff1a;零基础、对网络爬虫有兴趣但不知道从何开始的小白。 什么是 Scrapy&#xff1f; Scrapy 是一个基于 Python 的网络爬虫框架&#xff0c;它能帮助你快速爬取网站上的数据&#xff0c;并将数据保存到文件或数据库中。 特点&#xff1a; 高效&#xff1a;支…...

Springfox、Swagger 和 Springdoc

Springfox、Swagger 和 Springdoc 是用于在 Spring Boot 项目中生成 API 文档的工具&#xff0c;但它们之间有显著的区别和演进关系&#xff1a; 1. Swagger 简介 Swagger 是一个开源项目&#xff0c;旨在为 RESTful APIs 提供交互式文档。最早由 SmartBear 开发&#xff0c;…...

Css、less和Sass(SCSS)的区别详解

文章目录 Css、less和Sass&#xff08;SCSS&#xff09;的区别详解一、引言二、CSS 简介1.1、CSS 示例 三、Less 简介2.1、Less 特性2.2、Less 示例 四、Sass&#xff08;SCSS&#xff09;简介3.1、Sass 特性3.2、SCSS 示例 五、总结 Css、less和Sass&#xff08;SCSS&#xff…...

新能源汽车充电基础设施短板问题多,如何实现高效、综合、智能化管理?

随着城市经济的发展&#xff0c;人民生活水平的提升&#xff0c;新能源汽车保有量快速增长&#xff0c;而日益增长的新能源汽车需求与充电基础设施建设不平衡的矛盾日益突出。由于停车泊位充电基础设施总量不足、布局待优化、利用效率低、建设运营存在短板问题等原因&#xff0…...

DBA面试题-1

面临失业&#xff0c;整理一下面试题&#xff0c;找下家继续搬砖 主要参考&#xff1a;https://www.csdn.net/?spm1001.2101.3001.4476 略有修改 一、mysql有哪些数据类型 1&#xff0c; 整形 tinyint,smallint,medumint,int,bigint&#xff1b;分别占用1字节、2字节、3字节…...

LAN,WAN,VLAN,WLAN,VPN了解笔记

局域网LAN---公司的内部网络就是局域网LAN。 提供有线连接的接口允许局域网内的设备&#xff08;如台式电脑、网络打印机、网络存储设备等&#xff09;通过以太网线连接到路由器并与其他局域网设备进行通信实现设备之间的数据传输和资源共享一种私有的网络相对其他网络传输速度…...

1.2 算法和算法评价

1.2.1 算法的基本概念 算法&#xff1a;对特定问题求解步骤的一种描述&#xff0c;它是指令的有限序列&#xff0c;其中的每条指令表示一个或多个操作。 算法的五个重要特性 “好”的算法的五个目标 1.2.2 算法效率的度量 一、时间复杂度 算法的时间复杂度是指一个算法每行…...

各大常见编程语言应用领域

不同编程语言因其特性和设计目标而适用于不同的应用领域。以下是一些常见编程语言及其主要应用领域&#xff1a; 1. Python 数据科学与人工智能&#xff1a;Python 在数据分析、机器学习、深度学习等领域广泛使用&#xff0c;因其丰富的库&#xff08;如 NumPy、Pandas、Tens…...

【FFT】数据点数是否一定为2的n次方?不补零会如何处理?

一般来说&#xff0c;FFT的数据点个数为以2为基数的整数次方&#xff08;采用以2为基的FFT算法&#xff0c;可以提升运算性能&#xff09;&#xff0c;但是并没有要求FFT的数据点个数一定为2的n次方。 因此针对数据点数不是以2为基数的整数次方&#xff0c;有两种处理方法&…...

shell脚本小练习#003:查找并拷贝目录

实例1&#xff1a; # 从当前执行脚本的路径位置开始向上搜索一个名为sourceProject目录名 # 并将这个文件目录的路径名称打印出来#!/bin/bashfunction find_dir() {local current_dir$PWDwhile [[ $current_dir ! "/" ]]; doif [[ -d "${current_dir}/sourcePr…...

frp内网穿透

目录 1&#xff0c;准备公网服务器 2&#xff0c;下载安装frp服务端 3&#xff0c;服务端安装 2&#xff09;编辑服务端配置文件fprs.toml 3&#xff09;配置启动服务 4&#xff09;启动服务 5 &#xff09;设置开机启动服务 6&#xff09;查看服务启动状态 3&#xff0c…...

Android电视项目焦点跨层级流转

1. 背景 在智家电视项目中&#xff0c;主要操作方式不是触摸&#xff0c;而是遥控器&#xff0c;通过Focus进行移动&#xff0c;确定点击进行的交互&#xff0c;所以在电视项目中焦点、选中、确定、返回这几个交互比较重要。由于电视屏比较大&#xff0c;在一些复杂页面中会存…...

时频转换 | Matlab基于S变换S-transform一维数据转二维图像方法

目录 基本介绍程序设计参考资料获取方式基本介绍 时频转换 | Matlab基于S变换S-transform一维数据转二维图像方法 程序设计 clear clc % close all load x.mat % 导入数据 x =...

转载 为nautilus安装rabbitvcs

# 添加 rabbitvcs 的 ppa 源 sudo add-apt-repository ppa:rabbitvcs/ppa sudo apt update # 安装 rabbitvcs sudo apt install rabbitvcs-cli rabbitvcs-core rabbitvcs-gedit rabbitvcs-nautilus # 注销后重新登录&#xff0c;右键即可使用 # 解决 RabbitVCS 无法自动保存…...

OpenCV 模板匹配全解析:从单模板到多模板的实战指南

简介&#xff1a;本文深入探讨 OpenCV 中的模板匹配技术。详细介绍构建输入图像与模板图像的步骤&#xff0c;包括读取、截取、滤波与存储等操作。剖析 cv2.matchTemplate 语法及其参数含义&#xff0c;阐述不同匹配方法下结果值的意义。同时讲解 cv2.minMaxLoc 语法&#xff0…...

手机控制载货汽车一键启动无钥匙进入广泛应用

移动管家载货汽车一键启动无钥匙进入手机控车系统‌&#xff0c; 该系统广泛应用于物流运输、工程作业等货车场景&#xff0c;为车主提供了高效、便捷的启动和熄火解决方案&#xff0c;体现了科技进步对物流行业的积极影响‌ 核心功能‌&#xff1a;简化启动流程&#xff0c;提…...

Springboot——SseEmitter流式输出

文章目录 前言SseEmitter 简介测试demo注意点异常一 ResponseBodyEmitter is already set complete 前言 最近做AI类的开发&#xff0c;看到各大AI模型的输出方式都是采取的一种EventStream的方式实现。 不是通常的等接口处理完成后&#xff0c;一次性返回。 而是片段式的处理…...

【人工智能数学基础篇】线性代数基础学习:深入解读矩阵及其运算

矩阵及其运算&#xff1a;人工智能入门数学基础的深入解读 引言 线性代数是人工智能&#xff08;AI&#xff09;和机器学习的数学基础&#xff0c;而矩阵作为其核心概念之一&#xff0c;承担着数据表示、变换和运算的重任。矩阵不仅在数据科学中广泛应用&#xff0c;更是神经…...

idea 自动导包,并且禁止自动导 *(java.io.*)

自动导包配置 进入 idea 设置&#xff0c;可以按下图所示寻找位置&#xff0c;也可以直接输入 auto import 快速定位到配置。 Add unambiguous imports on the fly&#xff1a;自动帮我们优化导入的包Optimize imports on the fly&#xff1a;自动去掉一些没有用到的包 禁止导…...

奇怪的编码2

1.当铺密码 当铺密码的标志是“田由中人工大王夫井羊” 口 0 田 0 由 1 中 2 人 3 工 4 大 5 王 6 夫 7 井 8 羊 9 解密脚本&#xff1a; s 田由中人工大王夫井羊 codeinput("请输入当铺密码&#xff1a;") code code.split(" ") w for i in code:k…...

AI服务器从HBM到CXL的技术变革

AI服务器从HBM到CXL变革 本文探讨了AI产业的新范式&#xff0c;特别是服务器变革。传统服务器价格通常在1万美金以内&#xff0c;而搭载8张H100算力卡的DGX H100AI服务器价值高达40万美金(约300万人民币)。这一变化将对AI产业产生深远影响。 自然语言和图形处理依赖大量存储器…...

将自定义 AWS S3 快照存储库连接到 Elastic Cloud

作者&#xff1a;来自 Elastic Annie Hansen, Stef Nestor 在本博客中&#xff0c;我们将介绍如何通过 Elasticsearch 的快照将我们已提交的集群数据备份到 AWS S3 存储桶中。在 Elastic Cloud&#xff08;企业版&#xff09;中&#xff0c;Elastic 在其 found-snapshots 存储…...

Java 多线程编程核心要点全解析:深度探秘关键方法与同步机制

1.Thread 类中的start() 和 run() 方法有什么区别&#xff1f; 在Java编程语言中&#xff0c;Thread 类的 start() 和 run() 方法有重要的区别&#xff1a; start() 方法&#xff1a; 当你调用 start() 方法时&#xff0c;它会启动一个新的线程&#xff0c;并且这个新线程会…...

个人博客接入github issue风格的评论,utteranc,gitment

在做个人博客的时候&#xff0c;如果你需要评论功能&#xff0c;但是又不想构建用户体系和评论模块&#xff0c;那么可以直接使用github的issue提供的接口&#xff0c;对应的开源项目有utteranc和gitment&#xff0c;尤其是前者。 它们的原理是一样的&#xff1a;在博客文章下…...