Renewed Structure

This commit is contained in:
e2hang
2025-08-28 21:17:28 +08:00
parent b97f348a51
commit 1a5c4329b2
163 changed files with 312 additions and 0 deletions

View File

@@ -0,0 +1,504 @@
当然可以!以下是 **C++ STL 中 `set` 的详细介绍和使用方法大全**,包括:
---
## 🧱 1. 基本概念
`std::set` 是一个 **有序集合**,底层实现是 **红黑树RB-tree**。
特点如下:
* 所有元素 **自动升序排列(默认 `<`**
* 所有元素 **唯一**,不能重复
* 元素类型必须支持 `<` 操作符(可自定义)
* **查找、插入、删除**操作都是 **O(log n)** 的时间复杂度
---
## 📦 2. 头文件
```cpp
#include <set>
```
---
## 🧪 3. 声明与初始化
```cpp
std::set<int> s1; // 空集合
std::set<int> s2 = {4, 1, 3}; // 初始化,自动排序去重
std::set<std::string> s3{"apple", "banana"};
std::set<int, std::greater<int>> s4; // 降序排列
```
---
## 🧾 4. 常用成员函数汇总
| 函数 | 作用 |
| ------------------ | -------------------------------- |
| `insert(val)` | 插入元素(成功返回 pair\<iterator, true> |
| `emplace(val)` | 就地构造元素,性能比 `insert` 更高 |
| `find(val)` | 返回指向该元素的迭代器,找不到返回 `end()` |
| `count(val)` | 返回该值的数量(对于 set 只能是 0 或 1 |
| `erase(val)` | 删除指定元素 |
| `erase(it)` | 删除指定迭代器处元素 |
| `clear()` | 清空集合 |
| `size()` | 元素个数 |
| `empty()` | 是否为空 |
| `begin(), end()` | 起始、结束迭代器 |
| `lower_bound(val)` | 第一个 `>= val` 的位置 |
| `upper_bound(val)` | 第一个 `> val` 的位置 |
| `equal_range(val)` | 返回一个 pair表示 `[lower, upper)` 区间 |
---
## 🔨 5. 插入与查找示例
```cpp
std::set<int> s;
s.insert(5);
s.insert(3);
s.insert(8);
s.insert(3); // 自动忽略重复
// 查找
if (s.find(3) != s.end()) {
std::cout << "Found 3\n";
}
// 遍历
for (int x : s)
std::cout << x << " "; // 输出3 5 8
```
---
## ⛏ 6. 删除元素
```cpp
s.erase(5); // 删除值为5的元素
auto it = s.find(8);
if (it != s.end()) s.erase(it); // 用迭代器删除
```
---
## ⏬ 7. 自定义排序规则
```cpp
struct MyCmp {
bool operator()(const int &a, const int &b) const {
return a > b; // 降序
}
};
std::set<int, MyCmp> s = {1, 5, 3}; // 输出顺序5 3 1
```
---
## 🌈 8. 自定义结构体作为元素
### ✅ 正确写法:重载 `<`
```cpp
struct Person {
std::string name;
int age;
bool operator<(const Person &other) const {
return age < other.age; // 按 age 排序
}
};
std::set<Person> people;
people.insert({"Tom", 25});
people.insert({"Jerry", 20});
```
---
## 🔄 9. 遍历方式
```cpp
// 方法1范围 for
for (int x : s) std::cout << x << " ";
// 方法2迭代器
for (auto it = s.begin(); it != s.end(); ++it)
std::cout << *it << " ";
```
---
## 🧵 10. lower\_bound / upper\_bound 示例
```cpp
std::set<int> s = {2, 4, 6, 8};
auto lb = s.lower_bound(5); // 指向6
auto ub = s.upper_bound(6); // 指向8
```
---
## 🪄 11. set 和 multiset 区别
| 容器 | 是否允许重复 | 底层实现 |
| ---------- | ------- | ---- |
| `set` | ❌ 不允许重复 | 红黑树 |
| `multiset` | ✅ 允许重复 | 红黑树 |
---
## 📚 12. 常见用法技巧
### 用 set 去重:
```cpp
std::vector<int> v = {1, 2, 2, 3};
std::set<int> s(v.begin(), v.end()); // 自动去重
```
### 用 set 排序:
```cpp
std::set<int> s = {5, 1, 3};
// 自动升序排列1 3 5
```
### 交换两个 set
```cpp
std::set<int> a = {1, 2};
std::set<int> b = {3, 4};
a.swap(b);
```
---
## 🧮 13. 高级操作C++17 起)
```cpp
if (auto [it, ok] = s.insert(10); ok) {
std::cout << "插入成功:" << *it << "\n";
}
```
---
## 🚨 14. 注意事项
* `insert` 不会覆盖已有元素,若想更新值,请先 `erase()` 再 `insert()`
* 遍历时 **不要修改元素值**(因为会破坏红黑树结构)
* `set` 的 `[]` 操作符不存在(不同于 `map`
---
如需,我还可以为你提供:
* 📘 set 应用实战代码示例
* 🧠 使用场景解析(如 top-k、排名、自动去重等
* 🧩 `unordered_set` 的比较与选择策略
是否要我继续讲 `multiset` 和 `unordered_set`
<h1>C++ STL set容器完全攻略超级详细</h1>
<div class="pre-next-page clearfix">&nbsp;</div>
<div id="arc-body">前面章节讲解了 map 容器和 multimap 容器的用法,类似地,<a href='/cplus/' target='_blank'>C++</a> <a href='/stl/' target='_blank'>STL</a> 标准库中还提供有 set 和 multiset 这 2 个容器,它们也属于关联式容器。不过,本节先讲解 set 容器,后续章节再讲解 multiset 容器。<br />
<br />
和 map、multimap 容器不同,使用 set 容器存储的各个键值对,要求键 key 和值 value 必须相等。<br />
<br />
举个例子,如下有 2 组键值对数据:
<p class="info-box">
{&lt;&#39;a&#39;, 1&gt;, &lt;&#39;b&#39;, 2&gt;, &lt;&#39;c&#39;, 3&gt;}<br />
{&lt;&#39;a&#39;, &#39;a&#39;&gt;, &lt;&#39;b&#39;, &#39;b&#39;&gt;, &lt;&#39;c&#39;, &#39;c&#39;&gt;}</p>
显然,第一组数据中各键值对的键和值不相等,而第二组中各键值对的键和值对应相等。对于 set 容器来说,只能存储第 2 组键值对,而无法存储第一组键值对。<br />
<br />
基于 set 容器的这种特性,当使用 set 容器存储键值对时,只需要为其提供各键值对中的 value 值(也就是 key 的值)即可。仍以存储上面第 2 组键值对为例,只需要为 set 容器提供 {&#39;a&#39;,&#39;b&#39;,&#39;c&#39;} ,该容器即可成功将它们存储起来。<br />
<br />
通过前面的学习我们知道map、multimap 容器都会自行根据键的大小对存储的键值对进行排序set 容器也会如此,只不过 set 容器中各键值对的键 key 和值 value 是相等的,根据 key 排序,也就等价为根据 value 排序。<br />
<br />
另外,使用 set 容器存储的各个元素的值必须各不相同。更重要的是,从语法上讲 set 容器并没有强制对存储元素的类型做 const 修饰,即 set 容器中存储的元素的值是可以修改的。但是C++ 标准为了防止用户修改容器中元素的值,对所有可能会实现此操作的行为做了限制,使得在正常情况下,用户是无法做到修改 set 容器中元素的值的。<br />
<blockquote>
<p>
对于初学者来说,切勿尝试直接修改 set 容器中已存储元素的值,这很有可能破坏 set 容器中元素的有序性,最正确的修改 set 容器中元素值的做法是:先删除该元素,然后再添加一个修改后的元素。</p>
</blockquote>
值得一提的是set 容器定义于<code>&lt;set&gt;</code>头文件,并位于 std 命名空间中。因此如果想在程序中使用 set 容器,该程序代码应先包含如下语句:
<pre class="cpp">
#include &lt;set&gt;
using namespace std;</pre>
注意,第二行代码不是必需的,如果不用,则后续程序中在使用 set 容器时,需手动注明 std 命名空间(强烈建议初学者使用)。<br />
<br />
set 容器的类模板定义如下:
<pre class="cpp">
template &lt; class T, // 键 key 和值 value 的类型
class Compare = less&lt;T&gt;, // 指定 set 容器内部的排序规则
class Alloc = allocator&lt;T&gt; // 指定分配器对象的类型
&gt; class set;</pre>
注意,由于 set 容器存储的各个键值对,其键和值完全相同,也就意味着它们的类型相同,因此 set 容器类模板的定义中,仅有第 1 个参数用于设定存储数据的类型。<br />
<blockquote>
<p>
对于 set 类模板中的 3 个参数,后 2 个参数自带默认值,且几乎所有场景中只需使用前 2 个参数,第 3 个参数不会用到。</p>
</blockquote>
<h2>
创建C++ set容器的几种方法</h2>
常见的创建 set 容器的方法,大致有以下 5 种。<br />
<br />
1) 调用默认构造函数,创建空的 set 容器。比如:
<pre class="cpp">
std::set&lt;std::string&gt; myset;</pre>
<blockquote>
<p>
如果程序中已经默认指定了 std 命令空间,这里可以省略 std::。</p>
</blockquote>
由此就创建好了一个 set 容器,该容器采用默认的<code>std::less&lt;T&gt;</code>规则,会对存储的 string 类型元素做升序排序。注意,由于 set 容器支持随时向内部添加新的元素,因此创建空 set 容器的方法是经常使用的。<br />
<br />
2) 除此之外set 类模板还支持在创建 set 容器的同时,对其进行初始化。例如:
<pre class="cpp">
std::set&lt;std::string&gt; myset{&quot;http://c.biancheng.net/java/&quot;,
&quot;http://c.biancheng.net/stl/&quot;,
&quot;http://c.biancheng.net/python/&quot;};</pre>
由此即创建好了包含 3 个 string 元素的 myset 容器。由于其采用默认的 std::less&lt;T&gt; 规则,因此其内部存储 string 元素的顺序如下所示:
<p class="info-box">
&quot;http://c.biancheng.net/java/&quot;<br />
&quot;http://c.biancheng.net/python/&quot;<br />
&quot;http://c.biancheng.net/stl/&quot;</p>
<br />
3) set 类模板中还提供了拷贝(复制)构造函数,可以实现在创建新 set 容器的同时,将已有 set 容器中存储的所有元素全部复制到新 set 容器中。<br />
<br />
例如,在第 2 种方式创建的 myset 容器的基础上,执行如下代码:
<pre class="cpp">
std::set&lt;std::string&gt; copyset(myset);
//等同于
//std::set&lt;std::string&gt; copyset = myset</pre>
该行代码在创建 copyset 容器的基础上,还会将 myset 容器中存储的所有元素,全部复制给 copyset 容器一份。<br />
<br />
另外C++ 11 标准还为 set 类模板新增了移动构造函数,其功能是实现创建新 set 容器的同时,利用临时的 set 容器为其初始化。比如:
<pre class="cpp">
set&lt;string&gt; retSet() {
std::set&lt;std::string&gt; myset{ &quot;http://c.biancheng.net/java/&quot;,
&quot;http://c.biancheng.net/stl/&quot;,
&quot;http://c.biancheng.net/python/&quot; };
return myset;
}
std::set&lt;std::string&gt; copyset(retSet());
//或者
//std::set&lt;std::string&gt; copyset = retSet();</pre>
<p>
注意,由于 retSet() 函数的返回值是一个临时 set 容器,因此在初始化 copyset 容器时,其内部调用的是 set 类模板中的移动构造函数,而非拷贝构造函数。</p>
<blockquote>
<p>
显然,无论是调用复制构造函数还是调用拷贝构造函数,都必须保证这 2 个容器的类型完全一致。</p>
</blockquote>
<br />
4) 在第 3 种方式的基础上set 类模板还支持取已有 set 容器中的部分元素,来初始化新 set 容器。例如:
<pre class="cpp">
std::set&lt;std::string&gt; myset{ &quot;http://c.biancheng.net/java/&quot;,
&quot;http://c.biancheng.net/stl/&quot;,
&quot;http://c.biancheng.net/python/&quot; };
std::set&lt;std::string&gt; copyset(++myset.begin(), myset.end());</pre>
由此初始化的 copyset 容器,其内部仅存有如下 2 个 string 字符串:
<p class="info-box">
&quot;http://c.biancheng.net/python/&quot;<br />
&quot;http://c.biancheng.net/stl/&quot;</p>
<br />
5) 以上几种方式创建的 set 容器,都采用了默认的<code>std::less&lt;T&gt;</code>规则。其实,借助 set 类模板定义中第 2 个参数,我们完全可以手动修改 set 容器中的排序规则。比如:
<pre class="cpp">
std::set&lt;std::string,std::greater&lt;string&gt; &gt; myset{
&quot;http://c.biancheng.net/java/&quot;,
&quot;http://c.biancheng.net/stl/&quot;,
&quot;http://c.biancheng.net/python/&quot;};</pre>
通过选用 std::greater&lt;string&gt; 降序规则myset 容器中元素的存储顺序为:
<p class="info-box">
&quot;http://c.biancheng.net/stl/&quot;<br />
&quot;http://c.biancheng.net/python/&quot;<br />
&quot;http://c.biancheng.net/java/&quot;</p>
<h2>
C++ STL set容器包含的成员方法</h2>
表 1 列出了 set 容器提供的常用成员方法以及各自的功能。<br />
<br />
<table>
<caption>
表 1 C++ set 容器常用成员方法</caption>
<tbody>
<tr>
<th>
成员方法</th>
<th>
功能</th>
</tr>
<tr>
<td>
begin()</td>
<td>
返回指向容器中第一个(注意,是已排好序的第一个)元素的双向迭代器。如果 set 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。</td>
</tr>
<tr>
<td>
end()</td>
<td>
返回指向容器最后一个元素(注意,是已排好序的最后一个)所在位置后一个位置的双向迭代器,通常和 begin() 结合使用。如果 set 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。</td>
</tr>
<tr>
<td>
rbegin()</td>
<td>
返回指向最后一个(注意,是已排好序的最后一个)元素的反向双向迭代器。如果 set 容器用 const 限定,则该方法返回的是 const 类型的反向双向迭代器。</td>
</tr>
<tr>
<td>
rend()</td>
<td>
返回指向第一个(注意,是已排好序的第一个)元素所在位置前一个位置的反向双向迭代器。如果 set 容器用 const 限定,则该方法返回的是 const 类型的反向双向迭代器。</td>
</tr>
<tr>
<td>
cbegin()</td>
<td>
和 begin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的元素值。</td>
</tr>
<tr>
<td>
cend()</td>
<td>
和 end() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的元素值。</td>
</tr>
<tr>
<td>
crbegin()</td>
<td>
和 rbegin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的元素值。</td>
</tr>
<tr>
<td>
crend()</td>
<td>
和 rend() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的元素值。</td>
</tr>
<tr>
<td>
find(val)</td>
<td>
在 set 容器中查找值为 val 的元素,如果成功找到,则返回指向该元素的双向迭代器;反之,则返回和 end() 方法一样的迭代器。另外,如果 set 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。</td>
</tr>
<tr>
<td>
lower_bound(val)</td>
<td>
返回一个指向当前 set 容器中第一个大于或等于 val 的元素的双向迭代器。如果 set 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。</td>
</tr>
<tr>
<td>
upper_bound(val)</td>
<td>
返回一个指向当前 set 容器中第一个大于 val 的元素的迭代器。如果 set 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。</td>
</tr>
<tr>
<td>
equal_range(val)</td>
<td>
该方法返回一个 pair 对象(包含 2 个双向迭代器),其中 pair.first 和 lower_bound() 方法的返回值等价pair.second 和 upper_bound() 方法的返回值等价。也就是说,该方法将返回一个范围,该范围中包含的值为 val 的元素set 容器中各个元素是唯一的,因此该范围最多包含一个元素)。</td>
</tr>
<tr>
<td>
empty()</td>
<td>
若容器为空,则返回 true否则 false。</td>
</tr>
<tr>
<td>
size()</td>
<td>
返回当前 set 容器中存有元素的个数。</td>
</tr>
<tr>
<td>
max_size()</td>
<td>
返回 set 容器所能容纳元素的最大个数,不同的操作系统,其返回值亦不相同。</td>
</tr>
<tr>
<td>
insert()</td>
<td>
向 set 容器中插入元素。</td>
</tr>
<tr>
<td>
erase()</td>
<td>
删除 set 容器中存储的元素。</td>
</tr>
<tr>
<td>
swap()</td>
<td>
交换 2 个 set 容器中存储的所有元素。这意味着,操作的 2 个 set 容器的类型必须相同。</td>
</tr>
<tr>
<td>
clear()</td>
<td>
清空 set 容器中所有的元素,即令 set 容器的 size() 为 0。</td>
</tr>
<tr>
<td>
emplace()</td>
<td>
在当前 set 容器中的指定位置直接构造新元素。其效果和 insert() 一样,但效率更高。</td>
</tr>
<tr>
<td>
emplace_hint()</td>
<td>
在本质上和 emplace() 在 set 容器中构造新元素的方式是一样的,不同之处在于,使用者必须为该方法提供一个指示新元素生成位置的迭代器,并作为该方法的第一个参数。</td>
</tr>
<tr>
<td>
count(val)</td>
<td>
在当前 set 容器中,查找值为 val 的元素的个数,并返回。注意,由于 set 容器中各元素的值是唯一的,因此该函数的返回值最大为 1。</td>
</tr>
</tbody>
</table>
<br />
下面程序演示了表 1 中部分成员函数的用法:
<pre class="cpp">
#include &lt;iostream&gt;
#include &lt;set&gt;
#include &lt;string&gt;
using namespace std;
int main()
{
//创建空set容器
std::set&lt;std::string&gt; myset;
//空set容器不存储任何元素
cout &lt;&lt; &quot;1、myset size = &quot; &lt;&lt; myset.size() &lt;&lt; endl;
//向myset容器中插入新元素
myset.insert(&quot;http://c.biancheng.net/java/&quot;);
myset.insert(&quot;http://c.biancheng.net/stl/&quot;);
myset.insert(&quot;http://c.biancheng.net/python/&quot;);
cout &lt;&lt; &quot;2、myset size = &quot; &lt;&lt; myset.size() &lt;&lt; endl;
//利用双向迭代器遍历myset
<a href='/view/1811.html' target='_blank'>for</a> (auto iter = myset.begin(); iter != myset.end(); ++iter) {
cout &lt;&lt; *iter &lt;&lt; endl;
}
return 0;
}</pre>
程序执行结果为:
<p class="info-box">
1、myset size = 0<br />
2、myset size = 3<br />
http://c.biancheng.net/java/<br />
http://c.biancheng.net/python/<br />
http://c.biancheng.net/stl/</p>
<blockquote>
<p>
有关表 1 中其它成员方法的用法,后续章节会做详细讲解。</p>
</blockquote>
</div>

View File

@@ -0,0 +1,41 @@
#include <iostream>
#include <set>//Ҳ<><D2B2>set
using namespace std;
class compare{
public:
template <class T>
bool operator()(const T& a, const T& b){
return a > b;
}
};
int main(){
auto cmpl = [](auto a, auto b){
return a < b;
};
multiset<int> a{1,1,2,3,3,3,4,5,6,7,7,7,10};
multiset<int, decltype(cmpl)> b(cmpl);
b.insert({14,16,20,18,2,2,2,4,4,6,6,8,8,10,12});
multiset<int, compare> c{1,3,5,7,9,11,11,11,13};
for(int x : a){
cout << x << " ";
}cout << endl;
for(int x : b){
cout << x << " ";
}cout << endl;
for(int x : c){
cout << x << " ";
}cout << endl;
cout << b.count(2) << endl;
cout << *b.upper_bound(6) << " " << *b.lower_bound(6) << endl;
return 0;
}
/*
1 1 2 3 3 3 4 5 6 7 7 7 10
2 2 2 4 4 6 6 8 8 10 12 14 16 18 20
13 11 11 11 9 7 5 3 1
3
8 6
*/
//<2F><><EFBFBD><EFBFBD><EFBFBD>÷<EFBFBD><C3B7><EFBFBD>setһ<74>£<EFBFBD><C2A3><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ж<EFBFBD><D0B6><EFBFBD><EFBFBD>ظ<EFBFBD>Ԫ<EFBFBD><D4AA>

View File

@@ -0,0 +1,46 @@
#include <iostream>
#include <string>
#include <set>
using namespace std;
int main(){
auto cmp = [](auto a, auto b){
return a > b;
};
set<int> a;
set<int> b = {1, 5, 4, 3, 3, 2};
set<int, decltype(cmp)> c(cmp);
a.insert(1);a.insert(5);a.insert(3);a.insert(2);
c.insert(1);c.insert(5);c.insert(3);c.insert(2);
a.emplace(6);
for(auto x : a){
cout << x << " ";
}
cout << endl;
for(auto x : c){
cout << x << " ";
}
cout << endl;
cout << a.count(3) << endl;
a.erase(6);
//û<><C3BB>.at()<29><>a[i]<5D><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><E1B9B9><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ֱ<EFBFBD>ӷ<EFBFBD><D3B7>ʵ<EFBFBD>i<EFBFBD><69><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>it++
for(auto it = a.begin(); it != a.end(); it++){
cout << *it << " ";
}
//From "Smaller" To "Bigger", To compare big, we need "func:cmp"
cout << endl;
cout << *(c.find(5))<< endl;
//lower_bound : >= val ; upper_bound : > val ;
cout << *(c.lower_bound(2)) << " " << *(c.upper_bound(2)) << endl;
cout << *(a.lower_bound(2)) << " " <<*(a.upper_bound(2)) << endl;
return 0;
}
/*
COUT
1 2 3 5 6
5 3 2 1
1
1 2 3 5
5
2 1
2 3
*/