STL中的set

本文介绍了C++ STL中set容器的基本概念、内部结构及常用函数。解释了set作为一种关联容器,采用红黑树实现高效查找、插入和删除操作的特点。并通过对比其他容器,解析了set在内存管理和迭代器维护方面的优势。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.关于set

关于set,必须说明的是,set是一种关联式容器。set作为一个容器也是用来存储同一数据类型的数据类型,并且能从一个数据集合中取出数据,在set中每个元素的值都唯一,而且能根据元素的值自动进行排序。应该注意的是set中数元素的值不能直接被改变。
在C++ STL中,标准关联容器如:set, multiset, map, multimap,内部采用的是一种非常高效的平衡检索二叉树:红黑树,也被称为RB树(Red-Black Tree)。RB树的统计性能要好于一般平衡二叉树,所以被STL选择作为了关联容器的内部结构。

关于set有下面几个问题:

(1)为何map和set的插入删除效率比用其他序列容器高?

这个就比较简单,因为对于这种关联容器来说,不需要做内存拷贝和内存移动。set容器内所有元素都是以节点的方式来存储,其节点结构和链表差不多,指向父节点和子节点。结构图可能如下:

  A
  / \
  B C
 / \ / \
D E F G

因此插入的时候只需要稍做变换,把节点的指针指向新的节点就可以了。删除的时候类似,稍做变换后把指向删除节点的指针指向其他节点也OK了。这里的一切操作就是指针换来换去,和内存移动没有关系。

(2)为何每次insert之后,以前保存的iterator不会失效?

iterator这里就相当于指向节点的指针,内存没有变,指向这个内存的指针当然不会失效,和vector这种容器不同,vector在push_back()时,如果超过了一定内存大小,vector中的算法会自动寻找另一块更大的空间去存放现在的数据。所以当发生这样的事情时,迭代器所指向的内存空间已经被释放并且可能被其他进程所使用,进而导致迭代器失效。但set不同,每次insert操作,只是分配了一块新的节点空间,然后将各种结点内部指针指向不同的位置,但迭代器所指向的空间是不变的,所以不会发生迭代器失效的情况。

(3)当数据元素增多时,set的插入和搜索速度变化如何?

在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()相同

count() 用来查找set中某个某个键值出现的次数。这个函数在set并不是很实用,因为一个键值在set只可能出现0或1次,这样就变成了判断某一键值是否在set出现过了。

equal_range() ,返回一对定位器,分别表示第一个大于或等于给定关键值的元素和 第一个大于给定关键值的元素,这个返回值是一个pair类型,如果这一对定位器中哪个返回失败,就会等于end()的值。

erase(iterator) ,删除定位器iterator指向的值

erase(first,second),删除定位器first和second之间的值

erase(key_value),删除键值key_value的值

find() ,返回给定值值得定位器,如果没找到则返回end()。

insert(key_value); 将key_value插入到set中 ,返回值是pair

### C++ STL 中 `set` 的使用方法和特性 #### 1. 基本概念 `set` 是 C++ 标准模板库(STL中的一个关联式容器,它存储的是唯一的键值集合,并自动保持有序状态。其底层实现通常基于红黑树,因此提供了高效的查找、插入和删除操作。 #### 2. 头文件与命名空间 为了使用 `set` 容器,需要包含头文件 `<set>` 并使用标准命名空间 `std`[^1]。 ```cpp #include <set> using namespace std; ``` #### 3. 创建与初始化 可以通过多种方式创建并初始化一个 `set`: - **默认构造函数** ```cpp set<int> s; // 创建一个空的整数集 ``` - **范围初始化** ```cpp vector<int> v = {4, 5, 6}; set<int> s(v.begin(), v.end()); // 将 vector 初始化到 set 中 ``` - **列表初始化** ```cpp set<int> s = {1, 2, 3, 4}; // 列表初始化 ``` 以上三种方式均会去除重复元素并按升序排列。 #### 4. 插入与删除 ##### 插入元素 可以使用 `insert()` 方法向 `set` 中添加新元素。如果元素已存在,则不会再次插入。 ```cpp s.insert(10); ``` ##### 删除元素 支持通过迭代器或具体值来删除元素: - 使用迭代器删除: ```cpp auto it = s.find(10); // 查找值为10的元素 if (it != s.end()) { s.erase(it); // 删除找到的元素 } ``` - 使用值删除: ```cpp size_t count = s.erase(10); // 返回实际删除的数量(0 或 1) ``` 当尝试删除不存在的元素时,`erase()` 不会产生错误,仅返回 0 表示未成功删除[^4]。 #### 5. 遍历 由于 `set` 自动维护顺序,遍历时可以直接按照从小到大的顺序访问所有元素: ```cpp for(auto& elem : s){ cout << elem << ' '; } ``` 或者使用迭代器: ```cpp for(set<int>::iterator it = s.begin(); it != s.end(); ++it){ cout << *it << ' '; } ``` #### 6. 查找与统计 ##### 查找特定元素 可通过 `find()` 函数定位某个值是否存在: ```cpp auto pos = s.find(10); if(pos != s.end()){ cout << "Found!" << endl; } else{ cout << "Not Found!" << endl; } ``` ##### 统计某值数量 尽管 `set` 只允许唯一值,仍可调用 `count()` 来确认某一数值的存在情况: ```cpp cout << s.count(10) << endl; // 输出要么是0(不存在),要么是1(存在) ``` #### 7. 键值对形式——`multiset` 和 `map` 除了普通的 `set`,还有其他变种用于更复杂的数据管理需求。例如,`multiset` 支持相同键多次出现;而 `map` 提供了键值映射功能[^3]。 对于键值对的需求,推荐使用 `pair` 结构配合自定义比较规则构建复合型数据结构[^2]: ```cpp struct Student { string name; int id; }; bool operator<(const Student& lhs, const Student& rhs){ return lhs.id < rhs.id; } // 应用场景实例化 set<Student> students; students.insert({"Alice", 1}); students.insert({"Bob", 2}); ``` 上述代码展示了如何定制小于运算符以便于将学生对象存入 `set` 中。 --- ### 总结 综上所述,C++ STL 中的 `set` 是一种高效且灵活的工具,适用于处理无重复且需维持自然排序的任务。无论是基本的操作还是高级的应用场景,掌握这些技巧都将极大提升程序设计能力。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值