1、set和map的基础用法
stl中,像vector、list、deque等这样的容器,称之为序列式容器,其底层是线性序列的数据结构,里面存储的元素本身。
关联式容器,关联式容器不同的是,其存储是一个<key, value>键值对。
STL中关于键值对的定义:
简单来说就是可以通过一个pair对象访问其两个成员变量first和second,达到键值对的目的。
树型结构中的关联式容器主要有set、multiset、map和multimap。这四个容器的底层都是用的红黑树。
下面先一个一个看。
1.1 set的基本使用
set存储的是一值,和大部分容器一样,有着基本的begin、end、insert、erase、find、empty、size,这也是主要的功能。
值得说的是set由于底层是一个红黑树,其存储的数据没有重复的,并且set不允许修改数据,所以相较于map缺少了[]访问符,因为会打乱其结构。
如果需要重复的数据,multiset就派上了用场,multiset除了存储的数据可以重复外,其它的用法和set基本一致。
#include <iostream>
#include <set>
using std::cout;
using std::endl;
template<class T>
void testCont();
void testSet()
{
//set默认less->升序 greater->降序
testCont< std::set<int, std::greater<int>> >();
}
void testMultiset()
{
testCont< std::multiset<int, std::greater<int>> >();
}
template<class T>
void testCont()
{
T s;
int arr[] = { 1,4,6,7,9,2,5,6 };
//set不仅可以排序,也有去重功能
for (auto& e : arr)
{
s.insert(e);
}
typename T::iterator it = s.begin();
while (it != s.end())
{
cout << *it << " ";
++it;
}
cout << endl;
it = s.find(4);
s.erase(it);
for (auto& k : s)
{
cout << k << " ";
}
cout << endl;
}
int main()
{
testSet();
testMultiset();
return 0;
}
1.2 map的基本使用
map存储的是一个键值对,有着基本的begin、end、insert、erase、find、empty、size、[],这也是主要的功能。
multimap和map基本上一样,除了可以存储重复数据以及没有[]操作符。
值得注意的是map的[]操作符:
在官方文档中,将[]操作符等价于
其中insert,返回一个键值对,如果插入新值,返回新结点的迭代器和true,如果已有结点返回指向已有结点的迭代器和false。
简化后如下
value& operator[](const K& key)
{
pair<iterator, bool> ret = this->insert(make_pair(key, V()));
return *(ret.first).second;
}
常规用法
#include <iostream>
#include <map>
using std::cout;
using std::endl;
template<class T>
void testCont();
void testMap()
{
testCont< std::map<int, int, std::greater<int>> >();
}
void testMultimap()
{
testCont< std::multimap<int, int, std::greater<int>> >();
}
template<class T>
void testCont()
{
T s;
int arr[] = { 1,4,6,7,9,2,5,6 };
//map不仅可以排序,也有去重功能
for (auto& e : arr)
{
s.insert(std::make_pair(e, e));
}
typename T::iterator it = s.begin();
while (it != s.end())
{
cout << (*it).first << ":" << (*it).second << " ";
++it;
}
cout << endl;
it = s.find(4);
s.erase(it);
for (auto& kv : s)
{
cout << kv.first << ":" << kv.second << " ";
}
cout << endl;
cout << endl;
}
void testinsert()
{
std::map<int, int, std::greater<int>> m;
std::multimap<int, int, std::greater<int>> ml;
//multimap 不支持[]操作符,因为其存储重复数据
int arr[] = { 1,4,6,7,9,2,5,6 };
for (auto& e : arr)
{
m[e] = e;
//ml[e] = e; error
ml.insert(std::make_pair(e, e));
}
for (auto& kv : m)
{
cout << kv.first << ":" << kv.second << " ";
}
cout << endl;
for (auto& kv : ml)
{
cout << kv.first << ":" << kv.second << " ";
}
}
int main()
{
testMap();
testMultimap();
testinsert();
return 0;
}
2、set和map的模拟实现
为了更好的认识set和map,通过了解它们的stl源码,简单实现它们的功能。
set和map底层都是通过红黑树实现的。
2.1 建立map和set类
1、首先为了避免于std中重名,将创建的类放入test命名空间。
2、在stl源码中,map给红黑树传的模板参数是一个key和一个键值对
3、为了应对对红黑树结点取值不同的情况,通过传一个keyofValue类型,再通过仿函数的形式区分map和set访问的不同类型。
4、定义map迭代器的时候,typaname是为了表示
RBTree<K, pair<const K, V>, mapKeyOfValue>::iterator
是一个类型而不是一个其他的具体的值。(因为类::这样是可以访问类中定义的static类型和静态方法的)
5、注意键值对中的key不能修改。
//map.h
#pragma once
#include "RBTree.h"
namespace test
{
template<class K, class V>
class map
{
struct mapKeyOfValue
{
//专门放在mapKeyOfValue中,这样可读性好
const K& operator()(const pair<const K, V>& kv)
{
return kv.first;
}
};
public:
typedef typename RBTree<K, pair<const K, V>, mapKeyOfValue>::iterator iterator;
typedef typename RBTree<K, pair<const K, V>, mapKeyOfValue>::const_iterator const_iterator;
pair<iterator, bool> insert(const pair<const K, V>& kv)
{
return _t.insert(kv);
}
iterator begin()
{
return _t.begin();
}
iterator end()
{
return _t.end();
}
const_iterator begin() const
{
return _t.begin();
}
const_iterator end() const
{
return _t.end();
}
V& operator[](const K& k)
{
pair<iterator, bool> ret = insert(make_pair(k, V()));
return ret.first->second;
}
iterator find(const K& k)
{
return _t.find(k);
}
/*const_iterator find(const K& k) const
{
return _t.find(k);
}*/
private:
RBTree<K, pair<const K, V>, mapKeyOfValue> _t;
};
1、set要求的是迭代器返回指向的结点不能被修改,所以定义的iterator和const_iterator,都用的是红黑树的const_iterator进行定义。
2、set中的insert调用红黑树中的insert会返回一个键值对,这个键值对里面的iterator返回的是红黑树中的普通迭代器,这个普通迭代器再返回给set中的insert键值对时,会发生键值对的拷贝构造,对应红黑树的普通迭代器需要转换成const迭代器(因为set中iterator是用红黑树const迭代器实现的)。(我们在红黑树迭代器类中看具体是怎么处理的)
#pragma once
#include "RBTree.h"
namespace test
{
template<class K>
class set
{
struct setKeyOfValue
{
const K& operator()(const K& k)
{
return k;
}
};
public:
typedef typename RBTree<K, K, setKeyOfValue>::const_iterator iterator;
typedef typename RBTree<K, K, setKeyOfValue>::const_iterator const_iterator;
pair<iterator, bool> insert(const K& k)
{
//这里返回的一个pair,然后调用pair的拷贝构造,pair拷贝构造又要调用iterator的拷贝构造(调用默认拷贝构造),
//因为这里的iterator是RBTree中的const迭代器,返回的pair里是RBTree的普通迭代器
//所以出现了如何将普通迭代器转换成const迭代器的问题
//return _t.insert(k);
//这样写就好理解一点
pair<typename RBTree<K, K, setKeyOfValue>::iterator, bool> ret = _t.insert(k);
return pair<iterator, bool>(ret.first, ret.second);
}
//这里只需要提供普通迭代器版本就行,因为const this指针会调用RBTree里的const迭代器
iterator begin() const
{
return _t.begin();
}
iterator end() const
{
return _t.end();
}
iterator find(const K& k)
{
return _t.find(k);
}
private:
RBTree<K, K, setKeyOfValue> _t;
};
2.1 红黑树的完善
在红黑树的结点中,只存一个值,map中存键值对,set中存一个值。
//RBTree.h
#pragma once
#include <iostream>
#include <string>
#include <assert.h>
using namespace std;
enum Color { RED = 0, BLACK };
template<class T>
struct RBTreeNode
{
struct RBTreeNode<T>* _left;
struct RBTreeNode<T>* _right;
struct RBTreeNode<T>* _parent;
T _data;
Color _col;
RBTreeNode(const T& data)
:_data(data)
, _left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _col(BLACK)
{}
};
实现红黑树迭代器
迭代器中通过模板参数有这几个妙手:
1、比较常见的,对应普通迭代器需要返回T&、T*,对应const迭代器需要返回const T&、const T*,通过template<class T, class Ref, class Ptr>,就能实现普通迭代器和const迭代器的转换。
2、如果需要将普通迭代器转换成const迭代器,通过
typedef RBTreeIterator<T, T&, T*> iterator;
表示普通迭代器。
在需要用普通迭代器构造成const迭代器的时候,通过调用RBTreeIterator(const iterator& s)
这个构造就能实现。
//RBTree.h
// 对于红黑树类中定义迭代器
// typedef RBTreeIterator<T, T&, T*> iterator;
// typedef RBTreeIterator<T, const T&, const T*> const_iterator;
template<class T, class Ref, class Ptr>
struct RBTreeIterator
{
typedef RBTreeNode<T> Node;
typedef RBTreeIterator<T, Ref, Ptr> Self;
typedef RBTreeIterator<T, T&, T*> iterator;
RBTreeIterator(Node* node)
:_node(node)
{}
//当是普通迭代器的时候,Self和iterator都一样这时候下面的函数是一个拷贝构造
//当是const迭代器的时候,Ref是const T&,Ptr是const T*
//这个时候,Self代表const迭代器,iterator还是一个普通迭代器下面的函数就成了一个构造函数(用普通构造const的构造函数)。
RBTreeIterator(const iterator& s)
:_node(s._node)
{}
Ref operator*()
{
return _node->_data;
}
Ptr operator->()
{
return &_node->_data;
}
Self& operator++()
{
if (_node->_right)
{
Node* minRight = _node->_right;
while (minRight->_left)
{
minRight = minRight->_left;
}
_node = minRight;
}
else
{
Node* cur = _node;
Node* parent = cur->_parent;
while (parent && parent->_right == cur)
{
cur = parent;
parent = parent->_parent;
}
_node = parent;
}
return *this;
}
Self& operator--()
{
if (_node->_left)
{
Node* maxLeft = _node->_left;
while (maxLeft->_right)
{
maxLeft = maxLeft->_right;
}
_node = maxLeft;
}
else
{
Node* cur = _node;
Node* parent = cur->_parent;
while (parent && parent->_left == cur)
{
cur = parent;
parent = parent->_parent;
}
_node = parent;
}
return *this;
}
bool operator!=(const Self& s) const
{
return _node != s._node;
}
bool operator==(const Self& s) const
{
return _node == s._node;
}
Node* _node;
};
改造红黑树插入
template<class K, class T, class KeyOfValue>
class RBTree
{
typedef RBTreeNode<T> Node;
public:
typedef RBTreeIterator<T, T&, T*> iterator;
typedef RBTreeIterator<T, const T&, const T*> const_iterator;
//树的最左结点
iterator begin()
{
if (_root == nullptr)
{
return iterator(nullptr);
}
Node* pbegin = _root;
while (pbegin->_left)
{
pbegin = pbegin->_left;
}
return iterator(pbegin);
}
iterator end()
{
return iterator(nullptr);
}
//迭代器指向的结点不能改变
const_iterator begin() const
{
if (_root == nullptr)
{
return const_iterator(nullptr);
}
Node* pbegin = _root;
while (pbegin->_left)
{
pbegin = pbegin->_left;
}
return const_iterator(pbegin);
}
const_iterator end() const
{
return const_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);
}
//类实例化
KeyOfValue kov;
Node* cur = _root;
Node* parent = _root;
while (cur)
{
//仿函数
if (kov(cur->_data) < kov(data))
{
parent = cur;
cur = cur->_right;
}
else if (kov(cur->_data) > kov(data))
{
parent = cur;
cur = cur->_left;
}
else
{
return make_pair(iterator(cur), false);
}
}
cur = new Node(data);
Node* ret = cur;
cur->_col = RED;
if (kov(parent->_data) < kov(cur->_data))
{
parent->_right = cur;
cur->_parent = parent;
}
else
{
parent->_left = cur;
cur->_parent = parent;
}
while (parent && parent->_col == RED)
{
Node* grandparent = parent->_parent;
if (parent == grandparent->_left)
{
Node* uncle = grandparent->_right;
if (uncle && uncle->_col == RED)
{
//第一种情况
parent->_col = BLACK;
uncle->_col = BLACK;
grandparent->_col = RED;
cur = grandparent;
parent = cur->_parent;
}
else
{
//需要判断cur在parent的哪边
if (parent->_right == cur)
{
//情况四
RotateL(parent);
RotateR(grandparent);
cur->_col = BLACK;
grandparent->_col = RED;
}
else
{
//情况二/三
RotateR(grandparent);
parent->_col = BLACK;
grandparent->_col = RED;
}
break;
}
}
else
{
Node* uncle = grandparent->_left;
if (uncle && uncle->_col == RED)
{
//第一种情况
parent->_col = BLACK;
uncle->_col = BLACK;
grandparent->_col = RED;
cur = grandparent;
parent = cur->_parent;
}
else
{
//需要判断cur在parent的哪边
if (parent->_right == cur)
{
//情况二/三
RotateL(grandparent);
parent->_col = BLACK;
grandparent->_col = RED;
}
else
{
//情况四
RotateR(parent);
RotateL(grandparent);
cur->_col = BLACK;
grandparent->_col = RED;
}
break;
}
}
}
_root->_col = BLACK;
return make_pair(iterator(ret), true);
}
void RotateL(Node* parent)
{
Node* sub = parent->_right;
Node* subL = sub->_left;
parent->_right = subL;
if (subL)
{
subL->_parent = parent;
}
sub->_left = parent;
Node* ppnode = parent->_parent;
parent->_parent = sub;
if (ppnode == nullptr)
{
_root = sub;
_root->_parent = nullptr;
}
else
{
if (ppnode->_right == parent)
{
ppnode->_right = sub;
}
else
{
ppnode->_left = sub;
}
sub->_parent = ppnode;
}
}
void RotateR(Node* parent)
{
Node* sub = parent->_left;
Node* subR = sub->_right;
parent->_left = subR;
if (subR)
{
subR->_parent = parent;
}
sub->_right = parent;
Node* ppnode = parent->_parent;
parent->_parent = sub;
if (ppnode == nullptr)
{
_root = sub;
_root->_parent = nullptr;
}
else
{
if (ppnode->_right == parent)
{
ppnode->_right = sub;
}
else
{
ppnode->_left = sub;
}
sub->_parent = ppnode;
}
}
iterator find(const K& k)
{
if (_root == nullptr)
{
return iterator(nullptr);
}
KeyOfValue kov;
Node* cur = _root;
while (cur)
{
if (kov(cur->_data) < k)
{
cur = cur->_right;
}
else if (kov(cur->_data) > k)
{
cur = cur->_left;
}
else
{
return iterator(cur);
}
}
return iterator(nullptr);
}
private:
Node* _root = nullptr;
};
结果测试
#include "map.h"
#include "set.h"
void maptest()
{
test::map<int, int> m;
int arr[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
for (auto& e : arr)
{
m[e] = e;
}
test::map<int, int>::iterator it = m.begin();
while (it != m.end())
{
cout << (*it).first << ":" << (*it).second << endl;
++it;
}
test::map<string, int> countMap;
string arr1[] = { "苹果", "西瓜", "香蕉", "草莓", "苹果", "西瓜", "苹果", "苹果", "西瓜", "苹果", "香蕉", "苹果", "香蕉" };
for (auto& e : arr1)
{
countMap[e]++;
}
for (auto& kv : countMap)
{
cout << kv.first << ":" << kv.second << endl;
}
test::map<string, int>::iterator it1 = countMap.find("西瓜");
cout << "西瓜: " << (*it1).second << endl;
}
void settest()
{
test::set<int> s;
int arr[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
for (auto& e : arr)
{
s.insert(e);
}
test::set<int>::iterator it = s.begin();
while (it != s.end())
{
//*it += 10; //set不能修改数据
cout << *it << " ";
++it;
}
cout << endl;
it = s.find(4);
cout << *it << endl;
}
int main()
{
settest();
maptest();
return 0;
}
本节完~