C++ set和multiset的使用小結(jié)
1. 序列式容器和關(guān)聯(lián)式容器(了解)
前面我們已經(jīng)接觸過STL中的部分容器如:string、vector、list、deque、array、forward_list等,這些容器統(tǒng)稱為序列式容器,因為邏輯結(jié)構(gòu)為線性序列的數(shù)據(jù)結(jié)構(gòu),兩個位置存儲的值之間一般沒有緊密的關(guān)聯(lián)關(guān)系,比如交換一下,他依舊是序列式容器。順序容器中的元素是按他們在容器中的存儲位置來順序保存和訪問的。
關(guān)聯(lián)式容器也是用來存儲數(shù)據(jù)的,與序列式容器不同的是,關(guān)聯(lián)式容器邏輯結(jié)構(gòu)通常是非線性結(jié)構(gòu),兩個位置有緊密的關(guān)聯(lián)關(guān)系,交換一下,他的存儲結(jié)構(gòu)就被破壞了。順序容器中的元素是按關(guān)鍵字來保存和訪問的。關(guān)聯(lián)式容器有map/set系列和unordered_map/unordered_set系列。
map和set底層是紅黑樹,紅黑樹是一顆平衡二叉搜索樹。set是key搜索場景的結(jié)構(gòu),map是key/value搜索場景的結(jié)構(gòu)。
說人話 就是map set的值不能改 改了結(jié)構(gòu)會被破壞。
2. set系列的使用
2.1 set類的介紹
set的聲明如下,T就是set底層關(guān)鍵字的類型set默認(rèn)要求T支持小于比較,如果不支持或者想按自己的需求走可以自行實現(xiàn)仿函數(shù)傳給第二個模版參數(shù)。set底層存儲數(shù)據(jù)的內(nèi)存是從空間配置器申請的,如果需要可以自己實現(xiàn)內(nèi)存池,傳給第三個參數(shù)。- 一般情況下,我們都不需要傳后兩個模版參數(shù)。
set底層是用紅黑樹實現(xiàn),增刪查效率是 O ( l o g N ) O(logN) O(logN),迭代器遍歷是走的搜索樹的中序,所以是有序的。- 與
vector/list等容器的使用,STL容器接口設(shè)計,高度相似,所以這里我們就不再一個接口一個接口的介紹,挑比較重要的接口進行介紹。

2.2 set的構(gòu)造和迭代器
0.構(gòu)造:
set 的支持正向和反向迭代遍歷,遍歷默認(rèn)按升序順序,因為底層是二叉搜索樹,迭代器遍歷走的中序;支持迭代器就意味著支持范圍 for,set 的 iterator 和 const_iterator 都不支持迭代器修改數(shù)據(jù),修改關(guān)鍵字?jǐn)?shù)據(jù),防止破壞底層搜索樹的結(jié)構(gòu)。

1. 空構(gòu)造(empty (1))
explicit set (const key_compare& comp = key_compare(),
const allocator_type& alloc = allocator_type());
- 作用:創(chuàng)建空的
set容器。 - 參數(shù):
comp:可選,自定義的鍵比較規(guī)則(默認(rèn)使用key_compare,即<比較);alloc:可選,內(nèi)存分配器(默認(rèn)使用allocator_type)。
2. 范圍構(gòu)造(range (2))
template <class InputIterator>
set (InputIterator first, InputIterator last,
const key_compare& comp = key_compare(),
const allocator_type& alloc = allocator_type());
- 作用:將迭代器
[first, last)范圍內(nèi)的元素插入set(自動去重并按規(guī)則排序)。 - 參數(shù):
first/last:輸入迭代器,指定待插入元素的范圍;comp/alloc:同空構(gòu)造的可選參數(shù)。
3. 拷貝構(gòu)造(copy (3))
set (const set& x);
- 作用:創(chuàng)建一個與已有
set對象x內(nèi)容完全相同的新set。
4. 初始化列表(C++11)
void test_set1()
{
set<int> s = { 5,1,5,3,4,2,6,83,9,10,22 };
// 中序,排序+去重
set<int>::iterator it = s.begin();
while (it != s.end())
{
// 普通迭代器也不支持修改
// *it = 1;
cout << *it << " ";
++it;
}
cout << endl;
}

2.3 修改器(Modifiers)的成員函數(shù)

這是C++ std::set的修改器(Modifiers)成員函數(shù),負(fù)責(zé)對set的元素進行增刪等操作。以下結(jié)合代碼示例逐一講解:
0. 迭代器

這個太基礎(chǔ)了 我個人感覺實在沒什么可以說的 唯一要注意的就是 不能通過迭代器修改里面的值。
1. insert:插入元素
功能:向set中插入鍵值(自動去重、按規(guī)則排序)。
代碼示例:
#include <set>
#include <iostream>
using namespace std;
int main() {
set<int> s;
// 插入單個元素
s.insert(3);
s.insert(1);
s.insert(2);
s.insert(2); // 重復(fù)元素,插入失敗(set自動去重)
// 遍歷輸出:1 2 3(默認(rèn)升序)
for (int val : s) cout << val << " ";
return 0;
}
2. erase:刪除元素
功能:刪除set中的元素(支持按鍵值、迭代器、范圍刪除)。
代碼示例:
int main() {
set<int> s = {1,2,3,4,5};
// 1. 按鍵值刪除
s.erase(3);
// 2. 按迭代器刪除
auto it = s.find(4);
if (it != s.end()) s.erase(it);
// 3. 按范圍刪除(刪除[begin, end))
s.erase(s.begin(), s.end()); //左閉右開
cout << s.size(); // 輸出0
return 0;
}
3. swap:交換兩個set的內(nèi)容(與算法庫swap對比)
功能:交換當(dāng)前set與另一個set的所有元素(底層僅交換內(nèi)部指針,效率高,而算法庫中swap則涉及深層拷貝等)。
代碼示例:
int main() {
set<int> s1 = {1,2,3};
set<int> s2 = {4,5,6};
s1.swap(s2);
// s1變?yōu)閧4,5,6},s2變?yōu)閧1,2,3}
for (int val : s1) cout << val << " "; // 輸出4 5 6
return 0;
}
4. clear:清空所有元素
功能:刪除set中的所有元素,使其變?yōu)榭杖萜鳌?br />代碼示例:
int main() {
set<int> s = {1,2,3};
s.clear();
cout << s.empty(); // 輸出1(表示容器為空)
return 0;
}
5. emplace:構(gòu)造并插入元素(C++11+)
功能:直接在set中構(gòu)造元素(避免臨時對象拷貝,比insert更高效)。
代碼示例:
int main() {
set<pair<int, string>> s;
// emplace直接構(gòu)造pair(無需手動創(chuàng)建臨時pair)
s.emplace(1, "apple");
// 等價于insert,但emplace更高效
s.insert(pair<int, string>(2, "banana"));
return 0;
}
6. emplace_hint:帶位置提示的構(gòu)造插入(C++11+)
功能:在指定迭代器位置附近構(gòu)造并插入元素(若位置合理,可提升插入效率)。
代碼示例:
int main() {
set<int> s = {1,3,5};
// 提示在3的位置附近插入2(實際插入到1和3之間)
auto it = s.find(3);
s.emplace_hint(it, 2);
for (int val : s) cout << val << " "; // 輸出1 2 3 5
return 0;
}
2.4 find(與算法庫find的對比)

這是C++標(biāo)準(zhǔn)庫中std::set::find成員函數(shù)的聲明(支持C++98及以上版本),其核心信息與使用說明如下:
iterator find (const value_type& val) const;
- 返回值:
iterator(set的迭代器),指向找到的鍵值val;若val不存在,返回set::end()(尾后迭代器)。 - 參數(shù):
const value_type& val,待查找的鍵值(value_type即set的關(guān)鍵字類型)。 - 特性:
const修飾表示該函數(shù)不會修改set本身。
find是set的查找接口,基于底層紅黑樹的特性,能以 O ( log ? N ) O(\log N) O(logN)的時間復(fù)雜度快速定位鍵值,常用于判斷元素是否存在、獲取元素迭代器。
- 效率:由于
set底層是有序的紅黑樹,find通過二分查找邏輯實現(xiàn),效率遠高于算法庫的find( O ( N ) O(N) O(N))。 - 迭代器特性:
set的迭代器是雙向迭代器,且不可修改(因為修改鍵值會破壞set的有序性)。
#include <set>
#include <iostream>
using namespace std;
int main() {
set<int> s = {1, 2, 3, 4, 5};
// 查找鍵值3
auto it = s.find(3);
if (it != s.end()) {
cout << "找到元素:" << *it << endl; // 輸出“找到元素:3”
}
// 查找不存在的鍵值6
it = s.find(6);
if (it == s.end()) {
cout << "未找到元素" << endl; // 輸出“未找到元素”
}
return 0;
}
2.5 key_comp && value_comp
| 函數(shù)名 | 功能描述 |
|---|---|
| key_comp | 返回set用于比較**鍵(key)**的函數(shù)對象,是set模板參數(shù)中指定的比較類型(默認(rèn)是less<key_type>)。 |
| value_comp | 功能與key_comp一致(因為set的鍵和值是同一類型),返回的比較對象邏輯等同于key_comp。 |
set的底層紅黑樹依賴比較規(guī)則維持有序性,這兩個函數(shù)可以獲取當(dāng)前set使用的比較邏輯,常用于:
- 自定義比較規(guī)則時,驗證或復(fù)用set的排序邏輯;
- 對set的元素進行外部排序(保持與set內(nèi)部一致的規(guī)則)。
代碼示例
#include <set>
#include <iostream>
using namespace std;
int main() {
// 定義一個按降序排序的set
set<int, greater<int>> s = {3, 1, 2};
// 獲取key_comp比較對象
auto comp = s.key_comp();
// 使用comp判斷兩個鍵的大小關(guān)系(符合set的降序規(guī)則)
bool res = comp(1, 2); // 等價于greater<int>()(1,2),結(jié)果為false
cout << "1 > 2 ? " << boolalpha << res << endl; // 輸出“1 > 2 ? false”
return 0;
}
2.6 count(與find比較)
- 聲明:
size_type count (const value_type& val) const; - 功能:統(tǒng)計
set中值為val的元素個數(shù)(由于set不允許重復(fù)元素,返回值只能是0或1)。 - 參數(shù):
const value_type& val,待統(tǒng)計的目標(biāo)值; - 返回值:
size_type(無符號整數(shù)類型),表示val在set中的出現(xiàn)次數(shù)。
由于set的“唯一性”特性,count的實際作用是判斷元素是否存在(返回1表示存在,0表示不存在),效果等價于find(val) != end(),但語義更偏向“計數(shù)”。
#include <set>
#include <iostream>
using namespace std;
int main() {
set<int> s = {1, 2, 3, 4};
// 統(tǒng)計存在的元素
size_t cnt1 = s.count(3);
cout << "元素3的出現(xiàn)次數(shù):" << cnt1 << endl; // 輸出1
// 統(tǒng)計不存在的元素
size_t cnt2 = s.count(5);
cout << "元素5的出現(xiàn)次數(shù):" << cnt2 << endl; // 輸出0
return 0;
}
與find的區(qū)別
| 函數(shù) | 功能 | 返回值類型 | 適用場景 |
|---|---|---|---|
| find | 查找元素并返回迭代器 | 迭代器 | 需要獲取元素的位置時 |
| count | 統(tǒng)計元素出現(xiàn)次數(shù) | 無符號整數(shù) | 僅需判斷元素是否存在時 |
綜合對比 在判斷元素是否存在時 count 更加方便!
2.7 lower_bound && upper_bound
| 函數(shù)名 | 功能描述 |
|---|---|
| lower_bound | 返回指向**第一個不小于(≥)目標(biāo)值val**的元素的迭代器;若所有元素都小于val,返回end()。 |
| upper_bound | 返回指向**第一個大于(>)目標(biāo)值val**的元素的迭代器;若所有元素都不大于val,返回end()。 |
由于set是有序容器(默認(rèn)升序),這兩個函數(shù)通過二分查找(時間復(fù)雜度 O ( log ? N ) O(\log N) O(logN))快速定位邊界,常用于獲取“等于val的元素區(qū)間”([lower_bound, upper_bound))。
#include <set>
#include <iostream>
using namespace std;
int main() {
set<int> s = {1, 3, 5, 7, 9};
int val = 5;
// 獲取lower_bound:第一個≥5的元素(即5)
auto lb = s.lower_bound(val);
cout << "lower_bound(" << val << "): " << *lb << endl; // 輸出5
// 獲取upper_bound:第一個>5的元素(即7)
auto ub = s.upper_bound(val);
cout << "upper_bound(" << val << "): " << *ub << endl; // 輸出7
// 若val不存在(如val=4)
val = 4;
lb = s.lower_bound(val); // 第一個≥4的元素是5
ub = s.upper_bound(val); // 第一個>4的元素是5
cout << "val=4時,[lb, ub)區(qū)間長度:" << distance(lb, ub) << endl; // 輸出0(無元素)
return 0;
}
用處: 刪除或者遍歷 某一區(qū)間的值:

3. multiset
multiset和set,核心差異集中在元素唯一性、接口行為、使用場景三個維度,但是multiset不用單獨包含頭文件,二者基本完全類似。
3.1 核心特性差異
| 維度 | set | multiset |
|---|---|---|
| 元素唯一性 | 不允許重復(fù)元素(鍵唯一) | 允許重復(fù)元素(鍵可重復(fù)) |
| 底層實現(xiàn) | 紅黑樹(平衡二叉搜索樹) | 紅黑樹(平衡二叉搜索樹) |
| 有序性 | 元素按鍵有序排列 | 元素按鍵有序排列 |
3.2 接口行為差異
以常用成員函數(shù)為例,兩者行為因“唯一性”產(chǎn)生區(qū)別:
| 接口 | set的行為 | multiset的行為 |
|---|---|---|
| insert | 插入重復(fù)元素時返回失?。▋H插入一次) | 插入重復(fù)元素時成功(可插入多次) |
| find | 返回第一個匹配鍵的迭代器 | 返回第一個匹配鍵的迭代器(切記是中序遍歷的第一個) |
| count | 返回0或1(僅表示存在性) | 返回鍵的實際出現(xiàn)次數(shù) |
| lower_bound/upper_bound | 區(qū)間[lb, ub)長度最多為1 | 區(qū)間[lb, ub)包含所有匹配鍵的元素 |
| erase | 按鍵刪除時,刪除所有匹配的元素(僅1個) | 按鍵刪除時,刪除所有匹配的元素(可能多個) |
3.3 使用場景差異
set:適用于需要唯一鍵的場景(如存儲不重復(fù)的ID、去重后的數(shù)據(jù)集);multiset:適用于需要統(tǒng)計鍵出現(xiàn)次數(shù)的場景(如統(tǒng)計單詞頻率、存儲可重復(fù)的有序數(shù)據(jù))。
#include <set>
#include <iostream>
using namespace std;
int main() {
// set:鍵唯一
set<int> s = {1, 2, 2, 3};
cout << "set大小:" << s.size() << endl; // 輸出3(自動去重)
// multiset:鍵可重復(fù)
multiset<int> ms = {1, 2, 2, 3};
cout << "multiset大?。? << ms.size() << endl; // 輸出4(保留重復(fù))
// count接口差異
cout << "set中2的數(shù)量:" << s.count(2) << endl; // 輸出1
cout << "multiset中2的數(shù)量:" << ms.count(2) << endl; // 輸出2
return 0;
}
4. 例題部分
4.1 環(huán)形鏈表 II
題目鏈接: 點此跳轉(zhuǎn)


我們之前C語言階段是使用快慢指針完成的 其實我們可以用ste<Node*> s來做 遍歷鏈表,每個節(jié)點是否在s中,不在就插入,在的第一個點就是入口點 , 要說這道題唯一的缺陷 就是有 O ( N ) O(N) O(N)的空間復(fù)雜度。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode *detectCycle(ListNode* head)
{
set<ListNode*> s;
ListNode* tmp=head;
while(tmp!=NULL)
{
if(s.count(tmp)==0)
{
s.insert(tmp);
tmp=tmp->next;
}
else
{
return tmp;
}
}
return NULL;
}
};
4.2 兩個數(shù)組的交集
題目鏈接: 點此轉(zhuǎn)跳

這題也挺簡單的 其實就是拿set去重就可以了 然后用一個set的count去遍歷另一個set 把重復(fù)的插入vector即可。
class Solution {
public:
vector<int> intersection(vector<int>& nums1, vector<int>& nums2)
{
//去重
set<int> s1(nums1.begin(),nums1.end());
set<int> s2(nums2.begin(),nums2.end());
vector<int> v;
for(auto e : s1)
{
if(s2.count(e))
{
v.push_back(e);
}
}
return v;
}
};
補充:
這么做 其實復(fù)雜度還是高的 因為count的特性 時間復(fù)雜度可能要 O ( N ∗ l o g N ) O(N*logN) O(N∗logN)
但是利用雙指針特性就可以把復(fù)雜度壓縮到 O ( N ) O(N) O(N)。這個方法不光能找交集,也能找差集。

到此這篇關(guān)于C++ set和multiset的使用小結(jié)的文章就介紹到這了,更多相關(guān)C++ set和multiset內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Linux下Select多路復(fù)用實現(xiàn)簡易聊天室示例
大家好,本篇文章主要講的是Linux下Select多路復(fù)用實現(xiàn)簡易聊天室示例,感興趣的同學(xué)趕快來看一看吧,對你有幫助的話記得收藏一下,方便下次瀏覽2021-12-12
C語言實現(xiàn)簡單學(xué)生成績管理系統(tǒng)項目
這篇文章主要為大家詳細(xì)介紹了C語言實現(xiàn)簡單學(xué)生成績管理系統(tǒng)項目,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-07-07
Mac OS X 10.8 中編譯APUE(Unix環(huán)境高級編程)的源代碼過程
這篇文章主要介紹了Mac OS X 10.8 中編譯APUE(Unix環(huán)境高級編程)的源代碼過程,對于用MAC學(xué)習(xí)Unix環(huán)境高級編程的同學(xué)會有些作用,需要的朋友可以參考下2014-09-09
C++?STL標(biāo)準(zhǔn)庫之std::list使用介紹及用法詳解
std::list是支持常數(shù)時間從容器任何位置插入和移除元素的容器,下面這篇文章主要給大家介紹了關(guān)于C++?STL標(biāo)準(zhǔn)庫之std::list使用介紹及用法詳解的相關(guān)資料,文中通過實例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-11-11
C語言中結(jié)構(gòu)體struct編寫的一些要點解析
這篇文章主要介紹了C語言中結(jié)構(gòu)體struct編寫的一些要點解析,談到了結(jié)構(gòu)體的聲明和指針指向等重要知識點,需要的朋友可以參考下2016-04-04
VS C++頭文件引用提示“未定義標(biāo)識符”的問題解決
本文主要介紹了VS C++頭文件引用提示“未定義標(biāo)識符”的問題解決,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-07-07
C++實現(xiàn)簡單的學(xué)生成績管理系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了C++實現(xiàn)簡單的學(xué)生成績管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-03-03

