set / multiset 容器
一:set / multiset 容器基本概念
(1)set / multiset 容器基本概念:
set 和 multiset 是 C++ 标准库中的关联容器,它们使用红黑树(Red-Black Tree)作为底层数据结构来存储元素。这些容器中的元素是自动按照一定的规则进行排序的,不允许有重复的元素(std::set),或允许有重复的元素(std::multiset)。
红黑树是一种平衡二叉搜索树,具有以下特性:
- 每个节点是红色或黑色。
- 根节点是黑色。
- 每个叶子节点(NIL 节点)是黑色。
- 如果一个节点是红色,则其子节点必须是黑色。
- 从任一节点到其每个叶子节点的路径都包含相同数量的黑色节点。
这些特性保证了红黑树的平衡性,从而保证了插入、查找和删除操作的时间复杂度都可以在对数时间内完成。
(2)set / multiset 容器特点:
1. 元素唯一:
set 中的元素都是唯一的,即不允许重复的元素出现在集合中。
multiset 中的元素允许重复,可以插入多个相同的值。这是multiset和set主要的不同之处,其他地方功能和 set 类似。
2. 自动排序(从小到大):
set 中的元素按照升序(默认情况下)或根据用户自定义的比较函数进行排序。每次插入新元素时,集合会自动调整元素的位置,以保持有序性。
3. 插入和查找效率较高:
由于 set 使用红黑树作为底层实现,插入和查找的时间复杂度都是 O(log n),其中 n 是集合中的元素个数。
4. 不支持修改元素:
由于元素是有序的,不允许直接修改集合中的元素。若要修改元素,需要先删除原有元素,然后再插入新的元素。
二:set / multiset 模板原型
1. set 容器模板原型:
_EXPORT_STD template <class _Kty, class _Pr = less<_Kty>, class _Alloc = allocator<_Kty>>
class set : public _Tree<_Tset_traits<_Kty, _Pr, _Alloc, false>> {
// ordered red-black tree of key values, unique keys
}
_Tree:用来实现红黑树的模板,它内部定义了红黑树的各种操作,包括插入、删除、查找等。所以,实际上 set 和 multiset 都是通过继承 _Tree 并传递不同的类型特征来实现的。
_Kty:这是存储在 set 中的元素类型,也就是键(key)的数据类型。
_Pr:这是比较函数(比较器)的类型,用于确定元素之间的顺序。默认情况下,使用 less<_Kty> 作为比较函数,它用于对键进行排序。
_Alloc:这是分配器的类型,用于分配和释放内存。默认情况下,使用 allocator<_Kty> 作为分配器。
false: 这个参数表示 set 不允许重复的键。这是因为 set 是一个有序集合,每个键只能出现一次。
2. multiset 容器模板原型:
_EXPORT_STD template <class _Kty, class _Pr = less<_Kty>, class _Alloc = allocator<_Kty>>
class multiset : public _Tree<_Tset_traits<_Kty, _Pr, _Alloc, true>>{
// ordered red-black tree of key values, non-unique keys
}
与set容器不同之处在于最后一个参数:
true: 这个参数表示 multiset 允许重复的键。与 set 不同,multiset 可以包含多个相同的键。
三:set 容器成员函数使用示例
注意: 使用时包含头文件< set >头文件可以使用 set / multiset;
(1)set 容器常用成员函数汇总
1. s.begin() // 起始迭代器,指向第一个元素
2. s.end() // 结束迭代器,指向最后一个元素的后面一个虚拟位置,用于辅助遍历
3. s.clear() // 删除set容器中的所有的元素
4. s.empty() // 判断set容器是否为空
5. s.insert() // 插入一个元素
6. s.erase() // 删除一个元素
7. s.size() // 返回当前 set 容器中的元素个数
8. s.swap() // 交换两个集合
9. s.find() // 查找 key值元素
10. s.count() // 统计 key值元素,主要在 multiset 容器上使用
11. s.empty() // 判断set容器是否为空
set/multiset 容器打印输出函数:
// set/multset容器打印输出
void printSet(set<int>& s1){
for (const auto& num : s1){
cout << num << " ";
}
cout << endl;
}
(2)set 容器的创建操作
代码示例:
void test01()
{
set<int>s1; // 默认构造
//插入数据,只有用 insert方式
for (int i = 10; i >0; i--){
//源码定义返回pair<iterator, bool> //pair是对组,一对 pair<>
pair<set<int>::iterator, bool> re = s1.insert(i);
if (re.second){
cout << "插入元素" << i << "成功!" << endl;
}
else {
cout << "插入元素" << i << "失败!" << endl;
}
}
cout << "set插入数据,自动从小到大排序:";
printSet(s1);
// 使用greater<int>排序方式改为 从大到小
set<int,greater<int>>s;
for (int i = 10; i > 0; i--){
s.insert(i);
}
cout << "s: ";
printSet(s); // 10 9 8 7 6 5 4 3 2 1
set<int>s2(s1); // 拷贝构造
cout << "拷贝构造:";
printSet(s2); // 1 2 3 4 5 6 7 8 9 10
set<int>s4(s1.begin(), s1.end());// 区间拷贝
cout << "区间赋值:";
printSet(s4);
set<int>s5;
cout<<"s1是否为空: " << s1.empty() << endl; //0 不为空
cout<<"s5是否为空: " << s5.empty() << endl; //1 为空
multiset<int>s3 = { 4,4,5,1,6 }; // multiset 容器可有重复值!
cout << "mutilset s3初始化:";
printMultiSet(s3); // 1 4 4 5 6
}
(3)增删查改操作
1. 插入操作
函数原型:
insert(val) :插入单个元素val
代码示例:
void test02(){
set<int>s1; // 默认构造
//插入数据,只有insert方式
for (int i = 10; i >0; i--){
s1.insert(i);
}
printSet(s1);
}
2. 删除操作
函数原型:
erase(pos):删除迭代器指定位置的元素
erase(elem):删除指定元素
erase(begin,end):删除该区间内的元素
clear():删除set容器中的所有的元素,清空操作
代码示例:
void test03()
{
set<int>s1;
for (int i = 10; i > 0; i--){
s1.insert(i);
}
printSet(s1);// 1 2 3 4 5 6 7 8 9 10
// erase(pos)
cout << "删除迭代器指向的位置元素:";
s1.erase(s1.begin());
printSet(s1); //2 3 4 5 6 7 8 9 10
// erase(elem)
cout << "删除指定元素:";
s1.erase(10);
printSet(s1); // 2 3 4 5 6 7 8 9
// erase(begin,end)
cout << "区间删除元素:";
set<int>::iterator it = s1.begin();
it++;
s1.erase(it, s1.end()); // 删除 2 - end
printSet(s1);
s1.clear();
cout <<"s1 元素大小为:" << s1.size() << endl; // 0
}
3. 查找操作
函数原型:
find(key):查找 key 值,如果容器中不存在该元素,返回 s.end() 。
代码示例:
void test04(){
set<int>s1;
for (int i = 10; i > 0; i--){
s1.insert(i);
}
printSet(s1);
//find(key) //查找 key元素
set<int>::iterator it = s1.find(-1); //查找key成功,返回该键的迭代器,否则,返回set.end()
if (it != s1.end()){
cout << "查找到该元素:" << *it << endl;
}
else cout << "查找不成功" << endl;
}
4. 修改操作
不能直接修改容器内数据,只能删除某元素再插入要修改的数值。
int main() {
std::set<int> mySet = {1, 2, 3, 4, 5};
// 修改元素 3 为 6
auto it = mySet.find(3);
if (it != mySet.end()) {
mySet.erase(it); // 先删除元素 3
mySet.insert(6); // 再插入元素 6
}
for (const auto& num : mySet) {
std::cout << num << " ";
} // 输出:1 2 4 5 6
return 0;
}
(4)set容器统计元素个数
代码示例:
//count()
void test05(){
set<int>s1;
for (int i = 10; i > 0; i--){
s1.insert(i);
}
printSet(s1);
// count(key) 统计key元素数量 对set 0 或者 1;
// multiset 就是正常情况计数
cout << "元素1的数量为:" << s1.count(1) << endl; // 1
}
(5)set 容器交换操作
函数原型:
swap();
代码示例:
void test06() {
set<int>s1;
for (int i = 10; i > 0; i--){
s1.insert(i);
}
cout << "s1: ";
printSet(s1);
set<int>s4 = { 4,5,6,8,1,3 };
cout << "s4和s1交换:" << endl;
s4.swap(s1);
}
(6)set 容器修改排序规则
1. 内置数据类型指定排序规则
//从大到小排序 -- 仿函数
class CompareValue{
public:
bool operator()(const int& v1,const int& v2)const {
return v1 > v2;
}
};
void test07(){
set<int,CompareValue>s1;
for (int i = 10; i > 0; i--){
s1.insert(i);
}
for (const auto& num : s1){
cout << num << " " ;
}
cout << endl; // 10 9 8 7 6 5 4 3 2 1
}
2. 自定义数据类型指定排序规则
//自定义数据类型
class myInfo {
public:
myInfo(string name,int age){
this->Age = age;
this->Name = name;
}
string Name;
int Age;
};
// 自定义比较函数,实现从大到小排序
struct ascend {
bool operator()(const myInfo& m1,const myInfo& m2)const {
return m1.Age > m2.Age;
}
};
void test08() {
set<myInfo,ascend> m;
myInfo m1("zhan", 10);
myInfo m2("zhan1", 64);
myInfo m3("zhan2", 84);
myInfo m4("zhan3", 53);
myInfo m5("zhan4", 38);
m.insert(m1);
m.insert(m2);
m.insert(m3);
m.insert(m4);
m.insert(m5);
for (const auto& num : m) {
cout << num.Name << " " << num.Age << endl;
}
}
输出结果:
10 9 8 7 6 5 4 3 2 1
zhan2 84
zhan1 64
zhan3 53
zhan4 38
zhan 10
四:总结
- 使用 set 当你需要一个容器存储唯一元素,且不允许重复,默认升序。
- 使用 multiset 当你需要一个容器存储元素,允许重复,默认升序排序。
在选择使用 set 还是 multiset 时,根据你的需求来决定是否允许重复元素。
此外还有 unordered_set / multiset,是set 和 multiset的无序版,具体内容可以查看博客内容。
希望能更好的掌握 set/multiset 的特性及使用方法。
欢迎关注🔎点赞👍收藏⭐️留言📝