⭐️今天我要给大家介绍两个新的容器,它们都是关联式容器——map和set,我会先介绍它们的使用方法,然后带大家用上一篇博客中的红黑树封装出map和set。
⭐️博客代码已上传至gitee:https://gitee.com/byte-binxin/cpp-class-code
关联式容器也是用来存储数据的,与序列式容器(如vector、list等)不同的是,其里面存储的是
键值对: 用来表示具有一一对应关系的一种结构,该结构中一般只包含两个成员变量key和value,key代表键值,value表示与key对应的信息。
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)
{}
};
一般的两种方式创建键值对对象:
第一种: pair
第二种: make_pair(x, y) 是一个函数模板,其中返回的是一个pair的匿名对象
实例演示:
void test()
{
// pair(T1(), T2()) 通过构造函数构造一个匿名对象
// make_pair(T1() , T2()) 是一个模板函数,返回的是pair的匿名对象,用起来更方便
pair<int, int>(1, 1);
make_pair(1, 1);
}
和之前几个容器一样,有正向迭代器和反向迭代器,还有const迭代器。这里用法也和之前的类似,不过多介绍。下面会给大家演示。
find 查找某个元素。这里find的时间复杂度为O(logN),比算法中的find(时间复杂是O(N))更高效,所以set容器一般室友自己的find进行查找。
实例演示:
实例1 插入、删除、查找和迭代器遍历
void test_set1()
{
set<int> s;
s.insert(5);
s.insert(1);
s.insert(6);
s.insert(3);
s.insert(6);
s.insert(s.begin(), 10);
set<int>::iterator pos = s.find(15);// 底层是搜索二叉树,时间复杂度是O(logN)
// set::iterator pos = find(s.begin(), s.end(), 3);// 遍历查找,时间复杂度是O(N)
if (pos != s.end())
{
// cout << *pos << endl;
s.erase(pos);// 没有会报错
}
//s.erase(1); // 没找到不会报错
set<int>::iterator it = s.begin();
while (it != s.end())
{
cout << *it << " ";
++it;
}
cout << endl;
for (auto e : s)
{
cout << e << " ";
}
cout << endl;
}
代码运行结果如下:
实例2 下面是对算法中的find和set中的find进行效率比较的小测试
void test_set2()
{
srand((size_t)time(nullptr));
set<int> s;
for (size_t i = 0; i < 10000; ++i)
{
s.insert(rand());
}
cout << "个数:" << s.size() << endl;
int begin1 = clock();
for (auto e : s)
{
s.find(e);
}
int end1 = clock();
int begin2 = clock();
for (auto e : s)
{
find(s.begin(), s.end(), e);
}
int end2 = clock();
cout << "用时1:" << end1 - begin1 << "ms" << endl;
cout << "用时2:" << end2 - begin2 << "ms" << endl;
}
operator[]函数的定义如下:
mapped_type& operator[] (const key_type& k)
{
return (*((this->insert(make_pair(k,mapped_type()))).first)).second;
}
其中,mapped_type是KV模型中V的类型,也就是返回value值得引用。我们可以对这个value进行修改。
分析:((this->insert(make_pair(k,mapped_type()))).first这是一个迭代器,迭代器指向键值对中的第二个元素就是value。所以operato[]的底层是用到了插入,同时可以对value进行修改和访问。
总结: operator[]的三个用处:插入、修改和访问。
实例演示:
实例1 用map统计水果个数,以下用了3种方式,同时还对operator的几种作用进行了说明
void test_map2()
{
map<string, int> countMap;
string fruitArray[] = { "西瓜","桃子","香蕉","桃子","苹果","西瓜", "香蕉","苹果", "香蕉","西瓜","桃子", "西瓜", "西瓜","桃子",
"桃子", "桃子", "西瓜","桃子","香蕉","桃子","苹果","西瓜" };
// 方法一
//for (auto& e : fruitArray)
//{
// map::iterator ret = countMap.find(e);
// if (ret != countMap.end())// 找到了,说明容器里有,第二个参数加1即可
// {
// ++ret->second;
// }
// else
// {
// // 没有就插入,第二个参数记为1
// countMap.insert(make_pair(e, 1));
// }
//}
// 方法二
//for (auto& e : fruitArray)
//{
//
// // countMap无此元素,pair的第一个参数返回新的迭代器,第二个参数返回true
// // countMap有此元素,pair的第一个参数返回旧的迭代器,第二个参数返回false
// pair
// // 插入失败,只需要++value即可
// if (ret.second == false)
// {
// ++ret.first->second;
// }
//}
// 方法三
for (auto& e : fruitArray)
{
// mapped_type& operator[] (const key_type& k) ;
// mapped_type& operator[] (const key_type& k) { return (*((this->insert(make_pair(k,mapped_type()))).first)).second; }
// ((this->insert(make_pair(k,mapped_type()))).first 迭代器
// (*( (this->insert(make_pair(k,mapped_type()))).first )).second 返回value的值的引用 operator[]的原型
countMap[e]++;// 有插入、查找和修改的功能 返回value的值的引用
}
countMap["梨子"];// 插入
countMap["梨子"] = 5;// 修改
cout << countMap["梨子"] << endl;// 查找 一般不会用 operator[] 来进行查找,因为没找到会进行插入
countMap["哈密瓜"] = 3;// 插入+修改
for (auto& e : countMap)
{
cout << e.first << ":" << e.second << endl;
}
}
实例2 测试map的插入、删除和迭代器的使用
void test_map1()
{
map<int, int> m;
// 键值对
// pair(T1(), T2()) 通过构造函数构造一个匿名对象
// make_pair(T1() , T2()) 是一个模板函数,返回的是pair的匿名对象,用起来更方便
//m.insert(pair(1, 1));
m.insert(make_pair(1, 1));
m.insert(pair<int, int>(2, 2));
m.insert(pair<int, int>(3, 3));
m.insert(pair<int, int>(4, 4));
map<int, int>::iterator it = m.begin();
while (it != m.end())
{
// *it 返回 值得引用
cout << (*it).first << ":" << (*it).second << endl;
// it-> 返回 值的地址 -> 解引用访问两个元素
// cout << it->first << ":" << it->second << endl;
++it;
}
// e是自定义类型,传引用防止有拷贝构造发生
for (auto& e : m)
{
cout << e.first << ":" << e.second << endl;
}
}
总结几点:
与set的接口基本相似,直接上演示。
实例演示:
void test_multiset()
{
multiset<int> ms;
// multiset 和 set 的接口基本一致,multiset可以插入重复的
ms.insert(1);
ms.insert(5);
ms.insert(3);
ms.insert(2);
ms.insert(3);
multiset<int>::iterator pos = ms.find(3);// 返回的是第一个3
cout << *pos << endl;
++pos;
cout << *pos << endl;
++pos;
cout << *pos << endl;
++pos;
for (auto e : ms)
{
cout << e << " ";
}
cout << endl;
}
实例演示:
void test_multimap()
{
// multimap 和 map 的区别:可以有不同的key
// 不支持operator[] 因为有多个key时,不知道返回哪个key对应的value的引用
multimap<int, int> mm;
mm.insert(make_pair(1, 1));
mm.insert(make_pair(1, 2));
mm.insert(make_pair(1, 3));
mm.insert(make_pair(2, 1));
mm.insert(make_pair(2, 2));
for (auto& e : mm)
{
cout << e.first << ":" << e.second << endl;
}
}
这里是我上一篇关于红黑树的博客——红黑树
这里红黑树完整代码——红黑树完整代码
大概框架:
template<class K, class V>
class RBTree
{
typedef RBTreeNode<K, V> Node;
private:
Node* _root = nullptr;
};
这里的红黑树是一个KV模型,我们要用这个红黑树同时封装出map和set两个容器,直接使用这棵红黑树显然是不行的,set属于是K模型的容器,我们要做怎样的改造才能够同时封装出这两个容器呢?
这里我们参考STL源码的处理方式,下面是源码的部分截图:
可以看出这里,红黑树的第一个类模板参数和之前是一样的,但是第二个参数value和之前是不一样的,这里的直接把value存放在节点里面,通过map和set构造红黑树可以看出value存的是pair
template<class K, class T>
class RBTree
{
typedef RBTreeNode<T> Node;// 根据T的类型判断是map还是set 可能是pair或K
public:
private:
Node* _root = nullptr;
};
同时,我们还会发现,上面的红黑树的类模板中有第三个参数是什么呢?
为了获取value中的key值,我们可以让map和set各自传一个仿函数过来,以便获得各自的key值。
两个仿函数如下:
template<class K, class V>
class map
{
struct MAPOFV
{
const K& operator()(const pair<K, V>& kv)
{
return kv.first;
}
};
};
template<class K>
class set
{
struct SETOFV
{
const K& operator()(const K& key)
{
return key;
}
};
};
第四个类模板参数是一个空间配置器,这里也不实现了,我们实现主体内容即可。后面会介绍空间配置器相关内容。
迭代器的实现
其中operato++就是通过非递归中序遍历的方式走一遍红黑树,走到空就结束
template<class T, class Ptr, class Ref>
struct __rbtree_iterator
{
typedef __rbtree_iterator<T, Ptr, Ref> Self;
typedef RBTreeNode<T> Node;
Node* _node;
__rbtree_iterator(Node* node)
:_node(node)
{}
// 返回值(data)的地址
Ptr operator->()
{
return &_node->_data;
}
// 返回值(data)的引用
Ref operator*()
{
return _node->_data;
}
Self& operator++()
{
// 1.先判断右子树是否为空,不为空就去右子树找最左节点
// 2.右子树为空,去找孩子是其左孩子的祖先
Node* cur = _node;
if (cur->_right)
{
cur = cur->_right;
while (cur->_left)
{
cur = cur->_left;
}
}
else
{
Node* parent = cur->_parent;
while (parent && parent->_right == cur)
{
cur = parent;
parent = parent->_parent;
}
cur = parent;
}
_node = cur;
return *this;
}
Self& operator--()
{
// 1.先判断左子树是否为空,不为空就去左子树找最右节点
// 2.右子树为空,去找孩子是其右孩子的祖先
Node* cur = _node;
if (cur->_left)
{
cur = cur->_left;
while (cur->_right)
{
cur = cur->_right;
}
}
else
{
Node* parent = cur->_parent;
while (parent && parent->_left == cur)
{
cur = parent;
parent = parent->_parent;
}
cur = parent;
}
_node = cur;
return *this;
}
bool operator!=(const Self& s)
{
return _node != s._node;
}
};
红黑树修改后的代码: (留下了主体内容,有些部分删去了,不然有点长,可以去我的gitee上面看)
#pragma once
#include
#include
#include
using namespace std;
enum Color
{
RED,
BLACK
};
template<class T>
struct RBTreeNode
{
RBTreeNode<T>* _left;
RBTreeNode<T>* _right;
RBTreeNode<T>* _parent;
T _data;
Color _color;
RBTreeNode(const T& data, Color color = RED)
:_left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _data(data)
, _color(color)
{}
};
template<class T, class Ptr, class Ref>
struct __rbtree_iterator
{
typedef __rbtree_iterator<T, Ptr, Ref> Self;
typedef RBTreeNode<T> Node;
Node* _node;
__rbtree_iterator(Node* node)
:_node(node)
{}
// 返回值(data)的地址
Ptr operator->()
{
return &_node->_data;
}
// 返回值(data)的引用
Ref operator*()
{
return _node->_data;
}
Self& operator++()
{
// 1.先判断右子树是否为空,不为空就去右子树找最左节点
// 2.右子树为空,去找孩子是其左孩子的祖先
Node* cur = _node;
if (cur->_right)
{
cur = cur->_right;
while (cur->_left)
{
cur = cur->_left;
}
}
else
{
Node* parent = cur->_parent;
while (parent && parent->_right == cur)
{
cur = parent;
parent = parent->_parent;
}
cur = parent;
}
_node = cur;
return *this;
}
Self& operator--()
{
// 1.先判断左子树是否为空,不为空就去左子树找最右节点
// 2.右子树为空,去找孩子是其右孩子的祖先
Node* cur = _node;
if (cur->_left)
{
cur = cur->_left;
while (cur->_right)
{
cur = cur->_right;
}
}
else
{
Node* parent = cur->_parent;
while (parent && parent->_left == cur)
{
cur = parent;
parent = parent->_parent;
}
cur = parent;
}
_node = cur;
return *this;
}
bool operator!=(const Self& s)
{
return _node != s._node;
}
};
// KOFV是一个仿函数,返回的是对应类型的值 map返回pair中的key set也返回key
template<class K, class T, class KOFV>
class RBTree
{
typedef RBTreeNode<T> Node;// 根据T的类型判断是map还是set 可能是pair或K
public:
typedef __rbtree_iterator<T, T*, T&> iterator;
typedef __rbtree_iterator<T, const T*, const T&> const_iterator;
iterator begin()
{
Node* cur = _root;
while (cur && cur->_left)
{
cur = cur->_left;
}
return iterator(cur);
}
iterator end()
{
return iterator(nullptr);
}
iterator begin() const
{
Node* cur = _root;
while (cur && cur->_left)
{
cur = cur->_left;
}
return const_iterator(cur);
}
iterator end() const
{
return const_iterator(nullptr);
}
pair<iterator, bool> Insert(const T& data)
{
if (_root == nullptr)
{
_root = new Node(data, BLACK);// 根节点默认给黑
return make_pair(iterator(_root), true);
}
Node* cur = _root;
Node* parent = nullptr;
KOFV kofv;
while (cur)
{
parent = cur;
if (kofv(data) < kofv(cur->_data))
cur = cur->_left;
else if (kofv(data) > kofv(cur->_data))
cur = cur->_right;
else
return make_pair(iterator(cur), false);;
}
// 节点默认给红节点,带来的影响更小
// 给黑节点的话会影响 每条路径的黑节点相同这条规则
cur = new Node(data);
Node* newnode = cur;
if (kofv(cur->_data) < kofv(parent->_data))
{
parent->_left = cur;
cur->_parent = parent;
}
else
{
parent->_right = cur;
cur->_parent = parent;
}
while (parent && parent->_color == RED)
{
Node* grandfather = parent->_parent;
// 左边
if (grandfather->_left == parent)
{
// 红黑色的条件关键看叔叔
Node* uncle = grandfather->_right;
// u存在且为红
if (uncle && uncle->_color == RED)
{
// 调整 p和u改成黑,g改成红
parent->_color = uncle->_color = BLACK;
grandfather->_color = RED;
// 迭代 向上调整
cur = grandfather;
parent = cur->_parent;
}
else// u存在为黑/u不存在
{
// 折线用一个左单旋处理 1.p左单旋 2.g右单旋 3.把cur改成黑,g改成红 cur p g 三个是一条折线
if (cur == parent->_right)
{
RotateL(parent);
swap(parent, cur);
}
// 直线 cur p g 把p改成黑,g改成红
// 右单旋 有可能是第三种情况
RotateR(grandfather);
parent->_color = BLACK;
grandfather->_color = RED;
}
}
// uncle在左边
else
{
Node* uncle = grandfather->_left;
if (uncle && uncle->_color == RED)
{
parent->_color = uncle->_color = BLACK;
grandfather->_color = RED;
// 迭代 向上调整
cur = grandfather;
parent = cur->_parent;
}
else
{
// 折线用一个右单旋处理 g p cur g变红p边黑
if (cur == parent->_left)
{
RotateR(parent);
swap(parent, cur);
}
// 直线 g p cur 把p改成黑,g改成红
// 左单旋 有可能是第三种情况
RotateL(grandfather);
parent->_color = BLACK;
grandfather->_color = RED;
}
}
}
_root->_color = BLACK;
return make_pair(iterator(newnode), true);
}
bool Erase(const K& key)
{
// 如果树为空,删除失败
if (_root == nullptr)
return false;
KOFV kofv;
Node* parent = nullptr;
Node* cur = _root;
Node* delNode = nullptr;
Node* delNodeParent = nullptr;
while (cur)
{
// 小于往左边走
if (key < kofv(cur->_data))
{
parent = cur;
cur = cur->_left;
}
else if (key > kofv(cur->_data))
{
parent = cur;
cur = cur->_right;
}
else
{
// 删除...
}
}
}
iterator Find(const K& key)
{
if (_root == nullptr)
return iterator(nullptr);
KOFV kofv;
Node* cur = _root;
while (cur)
{
// 小于往左走
if (key < kofv(cur->_data))
{
cur = cur->_left;
}
// 大于往右走
else if (key > kofv(cur->_data))
{
cur = cur->_right;
}
else
{
// 找到了
return iterator(cur);
}
}
return iterator(nullptr);
}
private:
Node* _root = nullptr;
};
总结(改造的几个点):
template<class K, class V>
class map
{
struct MAPOFV
{
const K& operator()(const pair<K, V>& kv)
{
return kv.first;
}
};
typedef RBTree<K, pair<K, V>, MAPOFV> RBTree;
public:
// typename 告诉编译器这只是一个名字,暂时不用堆模板进行实例化
typedef typename RBTree::iterator iterator;
typedef typename RBTree::const_iterator const_iterator;
iterator begin()
{
return _rbt.begin();
}
iterator end()
{
return _rbt.end();
}
const_iterator begin() const
{
return _rbt.begin();
}
const_iterator end() const
{
return _rbt.end();
}
pair<iterator, bool> insert(const pair<K, V>& kv)
{
return _rbt.Insert(kv);
}
bool erase(const K& key)
{
return _rbt.Erase(key);
}
V& operator[](const K& key)
{
pair<iterator, bool> ret = insert(make_pair(key, V()));
return ret.first->second;
}
private:
RBTree _rbt;
};
---------------------------------------------------------------------------
template<class K>
class set
{
struct SETOFV
{
const K& operator()(const K& key)
{
return key;
}
};
typedef RBTree<K, K, SETOFV> RBTree;
public:
// typename 告诉编译器这只是一个名字,暂时不用堆模板进行实例化
typedef typename RBTree::iterator iterator;
typedef typename RBTree::const_iterator const_iterator;
iterator begin()
{
return _rbt.begin();
}
iterator end()
{
return _rbt.end();
}
const_iterator begin() const
{
return _rbt.begin();
}
const_iterator end() const
{
return _rbt.end();
}
pair<iterator, bool> insert(const K& key)
{
return _rbt.Insert(key);
}
bool erase(const K& key)
{
return _rbt.Erase(key);
}
private:
RBTree _rbt;
};
测试map:
void test_map()
{
map<string, int> countMap;
string strArr[] = { "香蕉","水蜜桃","西瓜","苹果","香蕉" ,"西瓜","香蕉" ,"苹果","西瓜","苹果","苹果","香蕉" ,"水蜜桃" };
for (auto& e : strArr)
{
countMap[e]++;
}
countMap["芒果"] = 10;
countMap.erase("水蜜桃");
countMap.erase("西瓜");
countMap.erase("芒果");
/*countMap.erase("香蕉");
countMap.erase("香蕉");
countMap.erase("苹果");*/
for (auto& e : countMap)
{
cout << e.first << ":" << e.second << endl;
}
}
void test_set()
{
set<int> s;
int arr[] = { 1,3,2,3,4,5,1,5,5,8,3,3,2 };
for (auto e : arr)
{
s.insert(e);
}
for (auto& e : s)
{
cout << e << endl;
}
}
map和set的内容就介绍到这,这一块相对还是比较复杂的,但是理解了就不会觉得很复杂,其中用到的东西都很巧妙,体现了泛型编程的特性。今天的内容就先介绍到这,喜欢的话,欢迎点赞支持~