一、set的介绍
1.概念
在C++中,std::set
是标准模板库(STL)中的一个关联容器,用于存储唯一的元素,并且这些元素会按照特定顺序自动排序。
2.基本特性
1)唯一性:std::set
中的元素是唯一的,不允许重复。如果尝试插入重复的元素,插入操作会失败。
2)自动排序:std::set
内部会根据元素的值自动排序,排序规则默认是按照元素类型的 <
运算符来比较。也可以通过自定义比较函数来改变排序规则。
3)基于红黑树实现:std::set
内部通常使用红黑树(一种自平衡的二叉搜索树)来存储元素,因此插入、删除和查找操作的时间复杂度都是 O(log n),其中 n 是容器中元素的数量。
3.代码运用
由于此处内容较为简单且知识点零碎,我将知识点放到了代码的注释里
插入、查找的看这里:
#include<iostream>
#include<set>
using namespace std;
int main()
{
// 去重+升序排序
set<int> s = { 5,9,3,6,2,3,9,8,7,4,5 };
//支持迭代器
//set<int>::iterator it = s.begin();
auto it = s.begin();
while (it != s.end())
{
cout << *it <<" ";
++it;
}
cout << endl;
s.insert(18);
s.insert(2);
s.insert(4);
s.insert(10);
//支持范围for
for (auto e : s)
{
cout << e << " ";
}
cout << endl;
//插入一段initializer_list列表值,已经存在的值插入失败
s.insert({ 6,9,6,5,10,20,30,35 });
for (auto e : s)
{
cout << e << " ";
}
cout << endl;
// 还可以遍历string比较ascll码大小顺序遍历的
set<string> sset = { "hello","left","right","haha" };
for (auto e : sset)
{
cout << e <<" ";
}
cout << endl;
//与std标准库中的find的对比
//std标准库:暴力查找,O(N)
auto it1 = find(s.begin(), s.end(),3);
//set自带的:类似于二分查找,查找方式类比搜索二叉树,O(logN)
auto it2 = s.find(3);
return 0;
}
单个数据删除的内容看这里:
int main()
{
set<int> s = { 4,2,6,8,5,3,4,1,2,7 };
for (auto e : s)
{
cout << e << " ";
}
cout << endl;
// 删除最小值
s.erase(4);
for (auto e : s)
{
cout << e << " ";
}
cout << endl;
s.erase(s.begin()); //删除s的第一个元素,不要看你输入元素的顺序!!!
for (auto e : s)
{
cout << e << " ";
}
cout << endl;
// 直接删除x
int x;
cin >> x;
int num = s.erase(x);
if (num == 0)
{
cout << x << "不存在" << endl;
}
for (auto e : s)
{
cout << e << " ";
}
cout << endl;
// 直接查找在利用迭代器删除x
int y;
cin >> y;
auto pos = s.find(y);
if (pos != s.end())
{
s.erase(pos);
}
else
{
cout << y << "不存在!" << endl;
}
for (auto e : s)
{
cout << e << " ";
}
cout << endl;
//用count()函数统计z的出现次数,进而判断是否存在
int z = 0;
cin >> z;
if (s.count(z))
{
cout << x << "在!" << endl;
}
else
{
cout << x << "不存在!" << endl;
}
return 0;
}
区间数据删除看这里:
int main()
{
set<int> s = { 10,60,25,36,92,35,45,56,30,66,77,88,99 };
for (auto e : s)
{
cout << e << " ";
}
cout << endl;
// 实现查找到的[itlow,itup)包含[30, 60]区间
// 返回 >= 30
auto itlow = s.lower_bound(30);
// 返回 > 60
auto itup = s.upper_bound(60);
// 删除这段区间的值
s.erase(itlow, itup);
for (auto e : s)
{
cout << e << " ";
}
cout << endl;
return 0;
}
二、miltiset的介绍
1.概念
std::multiset
是 C++ STL 中的一个关联容器,用于存储可重复的元素,并且这些元素会根据指定的比较准则自动排序。基本用法跟set差不多,具体见代码示例
2.代码示例
int main()
{
//multiset也是在<set>头文件中
multiset<int> ms = { 1,2,6,5,3,2,7,8,9,7,2,7 };
//迭代器和范围for均支持
multiset<int>::iterator it = ms.begin();
while (it != ms.end())
{
cout << *it << " ";
it++;
}
cout << endl;
// 相比set不同的是,x可能会存在多个,find查找中序的第一个
int x;
cin >> x;
auto pos = ms.find(x);
while (pos != ms.end() && *pos == x)
{
cout << *pos << " ";
++pos;
}
cout << endl;
//相比set不同的是,count会返回x的实际个数
cout << ms.count(x)<<endl;
// 相比set不同的是,erase给值时会删除所有的x
ms.erase(x);
for (auto e : ms)
{
cout << e << " ";
}
cout << endl;
return 0;
}
三、map的介绍
1.概念
在C++中,std::map
是一个非常强大的容器,属于标准模板库(STL)的一部分。它是一个基于红黑树实现的关联容器,用于存储键值对(key-value
),并且保证键的唯一性。
2.基本特性:
1)键值对存储:std::map
存储的是键值对,每个键(key
)唯一,通过键可以快速访问对应的值(value
)。
2)有序性:std::map
是有序的,它会根据键的值自动排序。默认情况下,它使用键的 <
操作符来比较键的大小。
3)基于红黑树:std::map
内部是通过红黑树实现的,因此插入、删除和查找操作的时间复杂度都是 O(log n)。
3.代码示例
3.1 pair 的介绍
在C++中,std::pair
是一个非常实用的模板类,用于将两个不同类型的数据组合在一起。它属于标准模板库(STL)的一部分,通常用于需要同时处理两个相关数据的场景。
std::pair
提供了两个成员变量:
-
first
:存储第一个数据。 -
second
:存储第二个数据。
3.2 插入部分
#include<iostream>
#include<map>
using namespace std;
int main()
{
map<string, string> m;
//c++98
//方法一
pair<string, string> p1("left", "左边");
m.insert(p1);
//方法二
m.insert(pair<string, string>("right", "右边"));
//方法三
m.insert(make_pair("up", "上"));
//c++11
//方法四
m.insert({ "down","下" });
//map<string, string>::iterator it = m.begin();
auto it = m.begin();
while (it != m.end())
{
cout << it->first << " " << it->second << endl;
//实际上是这样的,重载->,为了可读性,省略一个->
//cout << it.operator->()->first << " " << it.operator->()->second << endl;
it++;
}
cout << endl;
return 0;
}
插入有一个点值得注意一下: 如果插入的“键值对”在原map中已经有了,则本次插入失败,即key相同,插入失败,value相同,可以插入
我们看如下示例:
运行结果如下:
四、运用
map这个数据结构是具有映射性质的,很多具有关联的东西都可以用map来解决,这里我们以计次问题为例,来感受一下map的妙处所在!
1.水果计次问题
现在我们给出一些水果,需要统计一下次数,这里给出代码示例
int main()
{
string arr[] = { "苹果", "西瓜", "香蕉", "苹果", "西瓜", "苹果", "苹果", "西瓜","苹果", "香蕉", "苹果", "香蕉" };
map<string, int> cmap;
for (auto& e : arr)
{
auto it = cmap.find(e);
if (it == cmap.end()) //证明之前插入的没有这个水果,我现在要插入
{
cmap.insert({e,1});
}
else //证明之前插入过这个水果了,次数增加一次即可
{
it->second++;
}
}
for (auto e : cmap)
{
cout << e.first << " " << e.second << endl;
}
return 0;
}
这种方法是大家都能想到的,但是可能代码要写好长,感觉费时间,因此我们改进一下
2.[ ]的介绍
std::map
的 []
运算符是一种非常重要的成员函数,用于访问或插入元素。
1)如果 key
已经存在于 map
中,[]
运算符会返回对应键的值的引用。
2)如果 key
不存在,[]
运算符会插入一个新的键值对,键为 key
,值为 ValueType
的默认构造值(例如,如果 ValueType
是 int
,则默认值为 0
),并返回这个新值的引用。
3.【】的应用
map<string, string> dict;
// 插入(基本不会这样用)
dict["left"];
// 插入+修改
dict["up"] = "上";
for (auto e : dict)
{
cout << e.first << " " << e.second << endl;
}
cout << endl;
// 修改
dict["left"] = "左边";
// 查找,输出对应的值
cout << dict["left"] << endl;
cout << dict["up"] << endl;
运行结果如下
4.代码更改
int main()
{
string arr[] = { "苹果", "西瓜", "香蕉", "苹果", "西瓜", "苹果", "苹果", "西瓜","苹果", "香蕉", "苹果", "香蕉" };
map<string, int> cmap;
for (auto e : arr)
{
cmap[e]++;
}
for (auto e : cmap)
{
cout << e.first << " " << e.second << endl;
}
return 0;
}
说明:在上述代码中,如果我的水果已经存在了,那么【】会返回对应水果(键)的次数(值)的引用,并通过++完成自增,如果我的水果在之前是没有出现过的,那cmap中会插入一个新的键值对,键为 水果名称,值为 次数,并返回这个新值的引用,在通过++完成自增计数
我们可以在插入几个水果试一试:
pair<map<string, int>::iterator, bool> r1 = cmap.insert({ "草莓", 1 });
pair<map<string, int>::iterator, bool> r2 = cmap.insert({ "西瓜", 1 });
pair<map<string,int>::iterator, int> r3 = cmap.insert({ "蓝莓",10 });
for (auto e : cmap)
{
cout << e.first << " " << e.second << endl;
}
五、multimap
multimap与map的区别我们可以类比mutiset和set的区别,这里我们不多说了,直接看一下代码!
int main()
{
map<string, string> ant;
ant.insert({ "black","white" });
ant.insert({ "dirty","clean" });
ant.insert({ "dirty","tidy" });
ant.insert(pair<string, string>("dirty", "neat"));
for (auto e : ant)
{
cout << e.first << "<->" << e.second << endl;
}
cout << endl;
multimap<string, string> mant;
mant.insert({ "black","white" });
mant.insert({ "dirty","clean" });
mant.insert({ "dirty","tidy" });
mant.insert(pair<string, string>("dirty", "neat"));
//以上两种方式都可以插入,但个人更倾向于隐式类型转换(第一种)
for (auto e : mant)
{
cout << e.first << "<->" << e.second << endl;
}
return 0;
}
结果如下:
下面这个大家看一下就好,这里不在多说了
int main()
{
std::multimap<char, int> mymm;
mymm.insert(std::pair<char, int>('a', 10));
mymm.insert(std::pair<char, int>('b', 20));
mymm.insert(std::pair<char, int>('b', 30));
mymm.insert(std::pair<char, int>('b', 40));
mymm.insert(std::pair<char, int>('c', 50));
mymm.insert(std::pair<char, int>('c', 60));
mymm.insert(std::pair<char, int>('d', 60));
std::cout << "mymm contains:\n";
for (char ch = 'a'; ch <= 'd'; ch++)
{
std::pair <std::multimap<char, int>::iterator, std::multimap<char, int>::iterator> ret;
ret = mymm.equal_range(ch);
std::cout << ch << " =>";
for (std::multimap<char, int>::iterator it = ret.first; it != ret.second; ++it)
std::cout << ' ' << it->second;
std::cout << '\n';
}
return 0;
}
结果如下: