目录
(2)为何每次insert之后,以前保存的iterator不会失效?
先来看看基本用法
#include<iostream>
#include<set>//set 需要的头文件
using namespace std;
int main(){
multiset<int> myset;//创建set
for(int i = 0; i < 10; i++){
myset.insert(10-i); //插入元素
}
cout<<"第一个元素:";
cout<<*myset.begin() <<endl; //返回set容器的返回指向第一个元素的迭代器 记得要加星号begin()返回的是一个指针
cout<<"最后一个元素:";
cout<< *myset.end() <<endl; //返回set容器的最后一个元素
cout<<"是否为空:";
cout<<boolalpha<< myset.empty() <<endl; //判断set容器是否为空
cout<< "元素个数:";
cout<<myset.size() <<endl; //返回当前set容器中的元素个数
cout<< "某个值元素的个数:";
cout<<myset.count(1) <<endl; //返回某个值元素的个数 由于set中不允许重复的元素所有它实际的作用就是判断元素是否存在
//删除
//set中的删除操作是不进行任何的错误检查的,比如定位器的是否合法等等,所以用的时候自己一定要注意
myset.erase(myset.begin()); //删除定位器iterator指向的值
myset.erase(2); //删除指定的值 这里是2
//遍历set集合
multiset<int>::iterator it1;//迭代器
for(it1 = myset.begin(); it1 != myset.end(); it1++){
cout<< *it1<<ends;
}
myset.clear(); //删除set容器中的所有的元素
myset.erase(myset.begin(),myset.end());// 删除定位器first和second之间的值 这句和上一句等效
}
关于set
C++ STL 之所以得到广泛的赞誉,也被很多人使用,不只是提供了像vector, string, list等方便的容器,更重要的是STL封装了许多复杂的数据结构算法和大量常用数据结构操作。vector封装数组,list封装了链表,map和set封装了二叉树等,在封装这些数据结构的时候,STL按照程序员的使用习惯,以成员函数方式提供的常用操作,如:插入、排序、删除、查找等。让用户在STL使用过程中,并不会感到陌生。
关于set,必须说明的是set关联式容器。set作为一个容器也是用来存储同一数据类型的数据类型,并且能从一个数据集合中取出数据,在set中每个元素的值都唯一,而且系统能根据元素的值自动进行排序。应该注意的是set中数元素的值不能直接被改变。C++ STL中标准关联容器set, multiset, map, multimap内部采用的就是一种非常高效的平衡检索二叉树:红黑树,也成为RB树(Red-Black Tree)。RB树的统计性能要好于一般平衡二叉树,所以被STL选择作为了关联容器的内部结构。
【关联型容器概述】
在学习序列式容器时,我们知道,容器中元素的顺序都是由程序决定的,程序可以随意指定新元素的插入位置,而对于关联型容器,它的所有元素都是经过排序的,关联型容器都是有序的。它的每一个元素都有一个键(键),容器中的元素是按照键的取值升序排列的。
关联型容器内部实现为一个二叉树,在二叉树中,每个元素都有一个父节点和两个子节点。左子树的所有元素都比自己小,右子树的所有元素都比自己大。
A
/ \
B C
/ \ / \
DE FG
关联型容器内部结构都是以这种一叉树结构实现,这也使得它可以高效地查找容器中的每一个元素,但却不能实现任意位置的操作。
标准库提供了四种关联型容器: set(集合),multiset(多重集合),map(映射),multimap(多重映射),其中set与multiset包含在头文件set中,map与multimap包含在头文件map中。
关于set有下面几个问题:
(1)为何map和set的插入删除效率比用其他序列容器高?
大部分人说,很简单,因为对于关联容器来说,不需要做内存拷贝和内存移动。说对了,确实如此。set容器内所有元素都是以节点的方式来存储,其节点结构和链表差不多,指向父节点和子节点。结构图可能如下:
A
/ \
B C
/ \ / \
D E F G
因此插入的时候只需要稍做变换,把节点的指针指向新的节点就可以了。删除的时候类似,稍做变换后把指向删除节点的指针指向其他节点也OK了。这里的一切操作就是指针换来换去,和内存移动没有关系。
(2)为何每次insert之后,以前保存的iterator不会失效?
iterator这里就相当于指向节点的指针,内存没有变,指向内存的指针怎么会失效呢(当然被删除的那个元素本身已经失效了)。相对于vector来说,每一次删除和插入,指针都有可能失效,调用push_back在尾部插入也是如此。因为为了保证内部数据的连续存放,iterator指向的那块内存在删除和插入过程中可能已经被其他内存覆盖或者内存已经被释放了。即使时push_back的时候,容器内部空间可能不够,需要一块新的更大的内存,只有把以前的内存释放,申请新的更大的内存,复制已有的数据元素到新的内存,最后把需要插入的元素放到最后,那么以前的内存指针自然就不可用了。特别时在和find等算法在一起使用的时候,牢记这个原则:不要使用过期的iterator。
(3)当数据元素增多时,set的插入和搜索速度变化如何?
如果你知道log2的关系你应该就彻底了解这个答案。在set中查找是使用二分查找,也就是说,如果有16个元素,最多需要比较4次就能找到结果,有32个元素,最多比较5次。那么有10000个呢?最多比较的次数为log10000,最多为14次,如果是20000个元素呢?最多不过15次。看见了吧,当数据量增大一倍的时候,搜索次数只不过多了1次,多了1/14的搜索时间而已。你明白这个道理后,就可以安心往里面放入元素了。
set使用方法:
begin() ,返回set容器的第一个元素
end() ,返回set容器的最后一个元素
clear() ,删除set容器中的所有的元素
empty() ,判断set容器是否为空
max_size() ,返回set容器可能包含的元素最大个数
size() ,返回当前set容器中的元素个数
rbegin ,返回的值和end()相同
rend() ,返回的值和rbegin()相同
find() ,返回给定值值得定位器,如果没找到则返回end()。
lower_bound(key_value) ,返回第一个大于等于key_value的定位器
UPPER_BOUND(key_value),返回最后一个大于等于key_value的定位器
equal_range() ,返回一对定位器,分别表示第一个大于或等于给定关键值的元素和第一个大于给定关键值的元素,这个返回值是一个对类型,如果这一对定位器中哪个返回失败,就会等于端()的值。
【创建】
除了一个一个的插入以外,设置还可以把一个数组添加进来。
#include <iostream>
#include <set>
using namespace std;
int main()
{
int a[] = {1,2,3,58,99,0};
set<int> s(a,a+6);
multiset<int>::iterator it;//迭代器
for(it = s.begin(); it != s.end(); it++)
{
cout<< *it<<ends;
}
cout<<endl;
return 0;
}
【删除】
擦除(迭代),删除定位器迭代器指向的值
擦除(第一,第二),删除定位器第一和第二之间的值
擦除(key_value),删除键值key_value的值
find() ,返回给定值值得定位器,如果没找到则返回end()。
lower_bound(key_value) ,返回第一个大于等于key_value的定位器
UPPER_BOUND(key_value),返回最后一个大于等于key_value的定位器
#include<iostream>
#include<set>//set 需要的头文件
using namespace std;
void show(multiset<int> myset)
{
if(myset.empty()){
cout<<"没有元素可以删除啦!"<<endl;
return;
}
multiset<int>::iterator it;//迭代器
for(it = myset.begin(); it != myset.end(); it++)
{
cout<< *it<<ends;
}
cout<<endl;
}
int main()
{
multiset<int> myset;//创建set
for(int i = 0; i < 10; i++)
{
myset.insert(10-i); //插入元素
}
show(myset);
//删除
//set中的删除操作是不进行任何的错误检查的,比如定位器的是否合法等等,所以用的时候自己一定要注意
myset.erase(myset.begin()); //删除定位器iterator指向的值
show(myset);
myset.erase(2); //删除指定的值 这里是2
show(myset);
myset.clear();
show(myset);
//删除set容器中的所有的元素
myset.erase(myset.begin(),myset.end());// 删除定位器first和second之间的值 这句和上一句等效
show(myset);
//lower_bound
for(int i = 0; i < 10; i++)
{
myset.insert(10-i); //插入元素
}
show(myset);
//lower_bound(key_value) ,返回第一个大于等于key_value的定位器
//upper_bound(key_value),返回最后一个大于等于key_value的定位器
myset.erase(myset.lower_bound(3));
show(myset);
//删除大于等于4的所有元素
myset.erase(myset.lower_bound(4),myset.end());
show(myset);
}
小结: set中的删除操作是不进行任何的错误检查的,比如定位器的是否合法等等,所以用的时候自己一定要注意。
【关于set中元素的顺序】
1. set中的元素不允许重复;
2.设置内部会维护一个严格的弱排序关系。
上述两个特点实际上都依赖设置的比较函数,比较函数判断两个元素相等就是相等,与元素本身没有直接的关系。
默认定义了比较函数
利用设置内部默认的比较函数,可以将整数从小到大排序,将字符串按字母序进行排序。
#include <string>
#include <set>
#include <iostream>
using namespace std;
int main() {
int a[] = {20,10,30,40,50};
set<int> s1(a, a + 5);
for (auto it = s1.cbegin(); it != s1.cend(); it++)
{
cout << *it << " ";
}
cout << endl;
string b[] = {"apple", "banana", "pear", "orange", "strawberry"};
set<string> s2(b, b + 5);
for (auto it = s2.cbegin(); it != s2.cend(); it++)
{
cout << *it << " ";
}
cout << endl;
system("pause");
return 0;
}
输出:
10 20 30 40 50
apple banana orange pear strawberry
自定义排序函数
可以通过定义结构体(或类),并在其中重载()运算符,来自定义排序函数。然后,在定义集的时候,将结构体加入其中
如如下代码中的set <int,intComp>和set <string,strComp>。
#include <iostream>
#include <string>
#include <set>
using namespace std;
//自己定义仿函数
struct intComp {
bool operator() (const int& lhs, const int& rhs) const{
return lhs > rhs;
}
};
//自己定义仿函数
struct strComp
{
bool operator() (const string& str1, const string& str2) const {
return str1.length() < str2.length();
}
};
int main() {
int a[] = {10, 20, 30, 40, 50};
set<int, intComp> s1(a, a + 5);
for (auto it = s1.cbegin(); it != s1.cend(); it++)
{
cout << *it << " ";
}
cout << endl;
string b[] = {"apple", "banana", "pear", "orange", "strawberry"};
set<string, strComp > s2(b, b + 5);
for (auto it = s2.cbegin(); it != s2.cend(); it++)
{
cout << *it << " ";
}
cout << endl;
system("pause");
return 0;
}
输出:
50 40 30 20 10
pear apple banana strawberry
从上述程序段的输出可见,定义整型的比较函数后,输出数字按从大到小排序。定义字符串的比较函数为比较字符串的长度,输出字符串则按长度从小到大排序,其中并未出现橙了,因为香蕉的长度是6,而橙色的长度也是6,设置的构造函数认为橙与香蕉相同,而橙色出现在后,因此不会有橙色。
【set中插入结构体】
#include<iostream>
#include<string>
#include<set>
using namespace std;
//定义一个学生类型的结构体
typedef struct student
{
string name; //学生姓名
int achievement; //学生成绩
} student;
struct setCmp {
bool operator()(student a, student b) {
return a.achievement>b.achievement;
}
};
int main()
{
student stu[] = { {"张三",99},{"李四",87},{"王二",100} ,{"麻子",60}};
set<student, setCmp>myStudent (stu ,stu+4);
/*
set<student, setCmp>myStudent;
myStudent.insert(stu[0]);
myStudent.insert(stu[1]);
myStudent.insert(stu[2]);
myStudent.insert(stu[3]);
*/
set<student>::iterator it;
for (it = myStudent.begin(); it != myStudent.end(); ++it) {
cout << it->name << " " << it->achievement << endl;
}
return 0;
}
设置中的第二个参数还可以不写
当设容器放入结构体内容是需要重载运算符<也就是写出比较规则(不然人家怎么比较大小嘛)
set<student>myStudent (stu ,stu+4);//定义的时候不需要传入第二个参数
bool operator<(const student & a, const student & b)
{
return a.achievement > b.achievement;
}
//这样写和上面一个意思
bool operator<( student a, student b)
{
return a.achievement > b.achievement;
}
重载的<运算符还可以放在结构体里面
typedef struct student
{
string name; //学生姓名
int achievement; //学生成绩
//放在里面只写一个参数
bool operator <(const student &a)const//使用sort 重载<号
{
return(achievement > a.achievement);
}
} student;
注意:这里的两个常量参数都必须写上。
C ++的设计哲学之一就是使得程序在对待自定义类型时和内置类型必须是一致的(甚至自定义类型的支持更好)。
“C ++标准程序库”中明确指出:“只要是可分配的,可复制,比较的(根据某个排序准则)的型别T,都可以成为组或多集的元素型别。
其中,所谓的可比指的是更小,即可进行<比较。
反之,则不被支持。
来源:CSDN
作者:底层_码农
链接:https://blog.csdn.net/qq_40794973/article/details/81389901