C++知識點 STL容器2—set
~set~
set可能算是一種比較冷門的STL容器了,
喜歡用它的人覺得set真牛逼
不喜歡它的人覺得set真垃圾
很不幸,我屬於第一種
set作為一種封裝好的資料容器
最吸引人的地方是它的自動排序功能
這也就是說你可以擁有一個實時的排好序的序列
或者可以用一個序列同時實現大根堆和小根堆
時間複雜度和空間都是兩者和的1/2
善於運用set的自動排序特性可以為解題省去不少麻煩
啊就爽,就很爽。
繼承STL容器的傳統
set也有它自己專屬的標頭檔案 <set>
這個標頭檔案內包含兩種容器
set和multiset
set是自動排序還附帶去重的序列(有序集合),其中的元素不可重複
multiset是自動排序不去重的序列(有序多重集),其中可以有許多相同的元素
個人感覺在實際OI問題中比較常用的是multiset這種不去重的
兩者的實現原理都是一顆我不會打的紅黑樹,它們支援的函式基本相同
步入正題
首先我們來看一下怎麼宣告一個set和multiset並往其中插入元素
#include <iostream>
#include <set>
using namespace std;
struct Node {
int l, r, val;
};
bool operator <(const Node &a, const Node &b)
{ return a.val < b.val; }
int main()
{
set<int> p;//宣告方式與其他容器並無太大差異
multiset<int> q;
set<Node> fuc;//壓入set的東西型別必須定義"<"
}
當然,你也可以這麼寫(友元版)
#include <iostream>
#include <set>
using namespace std;
struct Node {
int l, r, val;
bool friend operator <(const Node &a, const Node &b)
{return a.val < b.val;}
set<Node> fuc;
};
int main()
{
set<int> p;
multiset<int> q;
}
這樣就完成了以普通資料型別和結構體的sethemultiset的宣告
同時切記:set和multiset內的資料型別必須已定義小於號
學完了宣告就該學如何往容器內插入元素
(size函式,empty函式,clear函式在set中也有,與在vector中作用無異,故此處不提)
與vector不同,往set內插入元素不需要指定位置
元素進入set後自動根據“<”的定義找到自己的位置
set和multiset都使用insert函式插入元素
時間複雜度為O(logn)
程式碼如下
#include <iostream>
#include <set>
using namespace std;
int x;
int main()
{
set<int> p;
multiset<int> q;
int n;
cin>>n;
for(int i = 1; i <= n; i++)
{
cin>>x;
q.insert(x);
p.insert(x);
}
}
如此即可實現向set p和multiset q中插入n個元素
插入完元素後我們可以對這些元素進行操作
首先需要注意的是set不支援隨機訪問
也就是說不能通過下標的方式訪問set中的元素
想想也很好理解,即使這貨能用下標你也不知道排完序後哪個元素是哪個
所以set給了我們與之替代的兩種資料查詢方式
迭代器(可以理解為STL的指標)和find函式
set的迭代器宣告方式與在vector中的宣告方式無異
同樣是宣告一個名為it的迭代器
有set<int>::iterator it;
如果++it,
則it指向set中的下一個元素
因為set是有序集合,所以++it所表示的元素就是剛好比it所指向的元素大的第一個元素
同時set也有begin和end函式用來取隊首和隊尾(的迭代器)
begin在set中取的是最小元素的迭代器
end函式跟vector中一樣返回隊尾下個元素的迭代器
所以--end()表示的是隊尾元素的迭代器
我們看一段遍歷set的程式碼來幫助我們理解這些抽象的概念
#include <iostream>
#include <set>
using namespace std;
int main()
{
set<int> a;
multiset<int> b;
a.insert(1);
a.insert(2);
a.insert(3);
a.insert(1);
b.insert(1);
b.insert(2);
b.insert(3);
b.insert(1);
cout<<"set 中的元素依次是 ";
for(set<int>::iterator it = a.begin(); it != a.end(); it++)
cout<<*it<<" ";
cout<<endl;
cout<<"multiset 中的元素依次是 ";
for(multiset<int>::iterator it = b.begin(); it != b.end(); it++)
cout<<*it<<" ";
cout<<endl;
return 0;
}
程式輸出結果是
set 中的元素依次是 1 2 3
multiset 中的元素依次是 1 1 2 3
在這個例子中我們可以瞭解到set中迭代器的用法和set與multiset的本質區別
有助於我們搞明白並在程式上實現set的基本操作
瞭解了迭代器
我們再來看看我認為能與迭代器平起平坐幫了大忙的find函式
find函式能夠找到set中大小為x的元素並返回指向該元素的迭代器。
如果set中並不存在元素x則返回s.end()
x 多次出現則返回第一次出現時的迭代器
樣例如下
#include <iostream>
#include <set>
using namespace std;
int main()
{
set<int> p;
multiset<int> q;
p.insert(1);
p.insert(2);
p.insert(3);
p.insert(1);
q.insert(1);
q.insert(2);
q.insert(3);
q.insert(1);
int a = *p.find(1);
int b = *q.find(3);
cout<<a<<" "<<b<<endl;
return 0;
}
聰明的你已經看出來了
我這麼寫返回的一定是要查詢的那個數
其實就是沒屁用
啊就有的題可能會出現需要用到find函式的地方(裝不下去了)
在實際應用中,我們需要對不需要的元素進行刪除
set給了我們一個強大的資料刪除函式erase()
括號內既可以是一個迭代器
erase刪除這個迭代器所指的元素
括號內也可以是個值或者啥東西
erase在set或multiset中查詢並刪除和括號內這個東西一樣的全部元素
那問題來了
在用multiset解決實際問題時
很多情況下我們並不想把所有相同的元素都刪除
只想刪除一個兩個或幾個(erase說沒門兒
此時find函式突然出現
“勞資返回的是迭代器hahahahaha”
於是你就可以用find函式定位到元素x第一次出現的位置並返回此時的迭代器
再用erase通過迭代器刪除的功能刪除這個x
由於multiset恆為有序所以刪除哪個x無關緊要
反正修改後的multiset都一樣
所以我們就解決了這個問題(find函式大放異彩)