赞
踩
再谈
:
- 红黑树和AVL树都是高效的平衡二叉树,增删改查的时间复杂度都是O( l o g 2 N log_2 N log2N),红黑树不追求绝对平衡,其只需保证最长路径不超过最短路径的2倍,相对而言,降低了插入和旋转的次数,所以在经常进行增删的结构中性能比AVL树更优,而且红黑树实现比较简单,所以实际运用中红黑树更多。
- 而map与set底层结构就是红黑树。
- 关于红黑树,可参考之前文章:https://blog.csdn.net/m0_69597277/article/details/129897990
编译环境:VS2013
1.序列式容器:STL中的部分容器,比如:string、vector、list、deque、forward_list(C++11)等,这些容器统称为序列式容器,因为其底层为线性序列的数据结构,里面存储的是元素本身。
2.关联式容器:也是用来存储数据的,与序列式容器不同的是,其里面存储的是<key, value>结构的键值对,在数据检索时比序列式容器效率更高。
用来表示具有一一对应关系的一种结构,该结构中一般只包含两个成员变量key和value,key代表键值,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) {} };
- map是关联式容器,它按照特定的次序(按照key来比较)存储,由键值key和值value组合而成的键值对元素。
- 在map中,键值key通常用于排序和惟一地标识元素,而值value中存储与此键值key关联的内容。键值key和值value的类型可能不同,并且在map的内部,key与value通过成员类型value_type绑定在一起,为其取别名称为:
pair: typedef pair<const key, T> value_type
;- 在内部,map中的元素总是按照键值key进行比较排序的。
- map中通过键值访问单个元素的速度通常比unordered_map容器慢,但map允许根据顺序 对元素进行直接迭代(即对map中的元素进行迭代时,可以得到一个有序的序列)。
- map支持下标访问符,即在[]中放入key,就可以找到与key对应的value。
- map在底层是用二叉搜索树(红黑树)实现的。
- map中的的元素是键值对。
- map中的key是唯一的,并且不能修改。
- 默认按照小于(less)的方式对key进行比较,即升序。
- map中的元素如果用迭代器去遍历,可以得到一个有序的序列。
- map的底层为平衡搜索树(红黑树),查找效率比较高 O ( l o g 2 N ) O(log_2 N) O(log2N)
- 支持[]操作符,operator[]中实际进行插入查找。
为实现map与set,我们需改造红黑树,在红黑树基础上,增加迭代器,在涉及到元素的比较上,由于存储元素的不同,如map中为键值对中k来进行比较,而set中是直接k值来进行比较,所以红黑树中原来直接将存储元素取出来进行比较的方法便行不通。故:我们在红黑树的类中增加一模板参数KOFT,表示从存储元素中将要进行比较的k值提取出来。
(1)红黑树的迭代器
增加迭代器便于元素的遍历。
迭代器begin()与end()
STL明确规定,begin()与end()代表的是一段前闭后开的区间,而对红黑树进行中序遍历后,可以得到一个有序的序列。
故:begin()可以放在红黑树中最小节点(即最左侧节点)的位置,end()放在最大节点(最右侧节点)的下一个位置,那问题是最大节点的下一个位置在哪里呢?能否给成nullptr呢?
答案是行不通的,因为对end()位置的迭代器进行–操作,必须要能找最后一个元素,因此最好的方式是将end()放在头结点的位置。
这也解释了我们一开始在构造红黑树时增加一个头结点的原因。
封装类如下:
//封装红黑树迭代器的类 template<class T,class Ref,class Ptr> class RBTreeIterator { typedef RBTreeNode<T> Node; typedef RBTreeIterator<T, Ref, Ptr> Self; public: RBTreeIterator(Node* pNode = nullptr) :_pNode(pNode) {} //迭代器需有指针类似的操作 //解引用* 和指向-> Ref operator*() { return _pNode->_data; } Ptr operator->() { return &(operator*()); } //迭代器可比较 bool operator==(const Self& s)const { return _pNode == s._pNode; } bool operator!=(const Self& s)const { return _pNode != s._pNode; } //迭代器可移动 //++a Self& operator++() { Increment(); return *this; } //a++ Self operator++(int) { Self temp(*this); Increment(); return temp; } Self& operator--() { Decrement(); return *this; } Self operator--(int) { Self temp(*this); Decrement(); return temp; } private: Node* _pNode; };
(2)红黑树迭代器的移动operator++()与operator–()
迭代器的移动,依次找红黑树中的节点即可。
//迭代器移动 void Increment() { //迭代器往后移动 //要么根节点,要么右子树最左侧的节点(右子树最小节点) if (_pNode->_right) { //_pNode右子树存在,到右子树中找最左侧的节点 _pNode = _pNode->_right; while (_pNode->_left) { _pNode = _pNode->_left; } } else //若右子树存在,再往双亲里去找 { Node* parent = _pNode->_parent; while (parent->_right == _pNode) { _pNode = parent; parent = _pNode->_parent; } //特殊:根节点没有右子树,迭代器恰好在根的位置 //_pNode->_parent=头节点,而_pNode的右指针域是指向自己的 if (_pNode->_right != parent) { _pNode = parent; } } } void Decrement() { //迭代器往前移动 //要么根节点,要么左子树最右侧的节点(左子树最大的节点) //当迭代器在end的位置 if (_pNode->_parent->_parent = _pNode && _pNode->_color == RED) { _pNode = _pNode->_right; } else if (_pNode->_left) { //_pNode左子树存在,在左子树中找最大的(最右侧节点) _pNode = _pNode->_left; while (_pNode->_right) { _pNode = _pNode->_right; } } else { //左子树存在的情况下再往双亲里面去找 Node* parent = _pNode->_parent; while (_pNode = parent->_left) { _pNode = parent; parent = _pNode->_parent; } _pNode = parent; } }
(3)红黑树构造
红黑树中增加迭代器和KOFT,在元素有进行比较的地方,便不是直接将存储元素取出来进行比较,而是将从存储元素将比较值k提取出来进行比较。
类中其余方法同红黑树。
//规定:树中元素唯一 //红黑树封装map 和 set //T表示往红黑树当中存储的元素类型,map中T为键值对,set中T为k值 //参数模板中加一参数KOFT:从T中将比较值K值提取出来 template<class T,class KOFT> class RBTree { public: typedef RBTreeNode<T> Node; typedef RBTreeIterator<T, T&, T*> iterator; public: RBTree() { _head = new Node(); _head->_parent = nullptr; _head->_left = _head; _head->_right = _head; _size = 0; } ~RBTree() { Destroy(_head->_parent); } iterator Begin() { return iterator(_head->_left); } iterator End() { return iterator(_head); } bool Empty()const { return _head->_parent == nullptr; } size_t Size()const { return _size; } //红黑树中节点的插入 pair<iterator,bool> Insert(const T& data) { Node* newNode = nullptr; //1.按照二叉搜索树的方式进行节点的插入 Node*& root = _head->_parent; //树为空,插入节点为树的根 if (root == nullptr) { newNode = root = new Node(data, BLACK); root->_parent = _head; } else { //树非空,查找插入节点元素是否存在 Node* cur = root; Node* parent = _head; KOFT koft; while (cur) { parent = cur; if (koft(cur->_data) <koft(data)) { cur = cur->_right; } else if (koft(cur->_data)>koft(data)) { cur = cur->_left; } else { return make_pair(iterator(cur),false); } } //进行节点的插入 newNode = cur = new Node(data); if (koft(parent->_data) > koft(data)) { parent->_left = cur; } else { parent->_right = cur; } cur->_parent = parent; //2.验证红黑树的性质若遭到破坏则对树进行调整 //节点插入成功可能会违反不能有连在一起的红色节点 //即新节点颜色为红色,parent颜色也为红 while (parent != _head && parent->_color == RED) { Node* pParent = parent->_parent; //parent可能为pParent的左孩子或是右孩子 if (parent == pParent->_left) { //情况一:cur为红,parent为红,pParent为黑,uncle存在且为红 //处理:将parent和uncle颜色改为黑,将pParent颜色改为红,继续往上调整 Node* uncle = pParent->_right; if (uncle && uncle->_color == RED) { parent->_color = BLACK; uncle->_color = BLACK; pParent->_color = RED; cur = pParent; parent = cur->_parent; } else { //叔叔节点不存在 || 树树节点存在且颜色为黑 if (parent->_right == cur) { //情况三: cur为红,parent为红,pParent为黑,uncle不存在或uncle存在且为黑 //处理:若parent为pParent的左孩子,cur为parent的右孩子,则针对parent做左单旋转 //若parent为g的右孩子,cur为parent的左孩子,则针对parent做右单旋转,转化为情况二 RotateLeft(parent); swap(parent, cur); } parent->_color = BLACK; pParent->_color = RED; RotateRight(pParent); } } else { Node* uncle = pParent->_left; if (uncle && uncle->_color == RED) { parent->_color = BLACK; uncle->_color = BLACK; pParent->_color = RED; cur = pParent; parent = cur->_parent; } else { //叔叔节点不存在 || 树树节点存在且颜色为黑 if (parent->_left == cur) { RotateRight(parent); swap(cur, parent); } parent->_color = BLACK; pParent->_color = RED; RotateLeft(pParent); } } } } _head->_left = MostLeftNode(); //树的左子树最左侧的节点 _head->_right = MostRightNode(); //树的右子树最右侧的节点 //根节点必须为黑 _head->_parent->_color = BLACK; ++_size; return make_pair(iterator(newNode), true); } //查找元素 iterator Find(const T& data) { Node* cur = _head->_parent; KOFT koft; while (cur) { if (koft(data) == koft(cur->_data)) { return iterator(cur); } else if (koft(data) < koft(cur->_data)) { cur = cur->_left; } else { cur = cur->_right; } } return End(); } //清空元素 void Clear() { Destroy(_head->_parent); _head->_left = _head; _head->_right = _head; _size = 0; } //节点交换 void Swap(RBTree<T,KOFT>& t) { std::swap(_head->_parent, t._head->_parent); } private: Node* _head; //定义一个头结点 size_t _size; };
(4)map实现
#pragma once #include"RBTree.hpp" namespace ma { template<class K,class V> class map { typedef pair<K, V> KV; struct KOFKV { const K& operator()(const KV& data)const { return data.first; } }; typedef RBTree<KV, KOFKV> RBT; public: //此处需加上typename //因为静态成员变量是通过 类名::静态成员方式来访问,故编译器不确定iterator是否是RBT中的类型 //而typename明确地告诉编译器iterator是RBT中的一个类型,而不是静态成员变量 typename typedef RBT::iterator iterator; public: map() :_t() {} //Iterator iterator begin() { return _t.Begin(); } iterator end() { return _t.End(); } //Capacity bool empty()const { return _t.Empty(); } size_t size()const { return _t.Size(); } //Element access V& operator[](const K& x) { //make_pair(x,V()) 构造默认的键值对 return (*(insert(make_pair(x, V())).first)).second; } pair<iterator, bool> insert(const KV& data) { return _t.Insert(data); } iterator find(const K& data) { return _t.Find(KV(data, V())); } void swap(map<K, V>& m) { _t.Swap(m._t); } void clear() { _t.Clear(); } private: RBT _t; }; }
- set是按照一定次序存储元素的容器
- 在set中,元素的value也标识它(value就是key,类型为T),并且每个value必须是唯一的。 set中的元素不能在容器中修改(元素总是const),但是可以从容器中插入或删除它们。
- 在内部,set中的元素总是按照其内部比较对象(类型比较)所指示的特定严格弱排序准则进行排序。
- set容器通过key访问单个元素的速度通常比unordered_set容器慢,但它们允许根据顺序对子集进行直接迭代。
- set在底层是用二叉搜索树(红黑树)实现的。
- 与map/multimap不同,map/multimap中存储的是真正的键值对<key, value>,set中只放value,但在底层实际存放的是由<value, value>构成的键值对。
- set中插入元素时,只需要插入value即可,不需要构造键值对。
- set中的元素不可以重复(因此可以使用set进行去重)。
key唯一
。- 使用set的迭代器遍历set中的元素,可以得到有序序列(其底层实现为红黑树,而红黑树为二叉搜索树)
- set中的元素默认按照小于(less)来比较,即升序。
- set中查找某个元素,时间复杂度为: l o g 2 N log_2 N log2N
set大体同map。
#pragma once #include"RBTree.hpp" namespace se { template<class K> class set { struct KOFK { const K& operator()(const K& data)const { return data; } }; typedef RBTree<K, KOFK> RBT; public: //此处需加上typename //因为静态成员变量是通过 类名::静态成员方式来访问,故编译器不确定iterator是否是RBT中的类型 //而typename明确地告诉编译器iterator是RBT中的一个类型,而不是静态成员变量 typename typedef RBT::iterator iterator; public: set() :_t() {} //Iterator iterator begin() { return _t.Begin(); } iterator end() { return _t.End(); } //Capacity bool empty()const { return _t.Empty(); } size_t size()const { return _t.Size(); } //Element access pair<iterator, bool> insert(const K& data) { return _t.Insert(data); } iterator find(const K& data) { return _t.Find(data); } void swap(set<K>& s) { _t.Swap(s._t); } void clear() { _t.Clear(); } private: RBT _t; }; }
测试:
//RBTree测试函数 void TestRBTree() { struct KOFInt { int operator()(const int data)const { return data; } }; RBTree<int, KOFInt> t; int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 }; for (auto e : a) { t.Insert(e); } t.Inorder(); auto it = t.Begin(); while (it !=t.End()) { cout << *it << " "; ++it; } cout << endl; if (t.IsValidRBTree()) { cout << "t is valid tree" << endl; } else { cout << "t is invalid tree" << endl; } cout << t.Size() << endl; if (t.Find(18) != t.End()) { cout << "18 is in the RBTree." << endl; } else { cout << "18 is not in the RBTree." << endl; } if (!t.Empty()) { t.Clear(); } cout << t.Size() << endl; }
//map测试函数 void TestMap() { ma::map<string, string> m; m.insert(make_pair("apple", "苹果")); m.insert(make_pair("orange", "橙子")); m.insert(make_pair("pear", "鸭梨")); m.insert(make_pair("watermelon", "西瓜")); cout << m.size() << endl; cout << m["orange"] << endl; auto it = m.begin(); while (it != m.end()) { cout << it->first << "---->" << it->second << endl; ++it; } cout << endl; if (m.find("orange") != m.end()) { cout << "orange is in map." << endl; } else { cout << "orange is not in map." << endl; } }
//set测试函数 void TestSet() { se::set<string> s; s.insert("apple"); s.insert("orange"); s.insert("pear"); s.insert("watermelon"); cout << s.size() << endl; auto it = s.begin(); while (it != s.end()) { cout << *it << " "; ++it; } cout << endl; if (s.find("orange") != s.end()) { cout << "orange is in map." << endl; } else { cout << "orange is not in map." << endl; } }
结果:
map的底层结构就是红黑树,故在进行map的封装实现时,实质其实也就是将红黑树中的相应接口包装一下,当然,set同理。
完整代码可点击进入仓库查看:
https://gitee.com/confused-cat/code_-connection_point/tree/master/Map-and-Set
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。