演算法學習之路|用C++刷演算法會用到的STL(二)——set

kissjz發表於2018-02-09

二、set

1.set的自我介紹

 <li>set意思是集合,從初中就接觸到了集合的概念,真是的好東西。set是一個內部自動有序且不含重複元素的容器。</li>
 <li>set是一種關聯式容器,是用來儲存同一種資料型別的資料型別,有點繞口,就是sety也就是集合裡裡面要不然全部裝int型的要不然全部裝double型的要不然....就是這個意思。並且能從這個同一種資料型別構成的集合中取出元素,而且set中的每個元素都是唯一的,不能重複,好像是數學裡集合概念中的唯一性(哈哈哈,強大的數學功底)。更令人欣慰的是,能根據元素的值進行自動排序。</li>
 <li>一個集合(set)是一個容器,它其中所包含的元素的值是唯一的。這在收集一個資料的具體值的時候是有用的。集 閤中的元素按一定的順序排列,並被作為集合中的例項。如果你需要一個鍵/值對(pair)來儲存資料,map是一個更好的選擇。一個集合通過一個連結串列來組 織,在插入操作和刪除操作上比向量(vector)快,但查詢或新增末尾的元素時會有些慢。(原因後文簡單說說)</li>
 <li>要注意的是,set中書元素的值不能被直接改變。C++ STL中標準關聯容器set, multiset, map, multimap內部採用的就是一種非常高效的平衡檢索二叉樹,即一種自平衡二叉樹(以後會講到啥叫平衡二叉樹~~):紅黑樹,也成為RB樹(Red-Black Tree)。RB樹的統計效能要好於一般平衡二叉樹,所以被STL選擇作為了關聯容器的內部結構。</li>

2.set的定義

 <li>單獨定義一個set:</li>

          set<typename> setname;

 <li>其定義和大部分STL的定義都差不多,這裡的typename依然一樣可以是任何基本型別,比如int,double,char,結構體啊,還有STL的標準容器vector,set,queue啊等等。</li>
 <li>需要注意的是,比如<em>set&lt;vector&lt;int&gt; &gt; setname;</em>兩個`&gt;‘之間一定要加上空格,否則會編譯錯誤,原因是會誤以為位移運算(詳見上篇)。</li>
 <li>特別的,set陣列:</li>

set<typename> Arrayname[arraySize];

比如,set<int>a[100];定義了一個set型的陣列,陣列中每一個數都是一個集合,每一個集合裡都是int型的值。

3.set容器內元素的訪問形式

 <li>只有一種!</li>

          hhh,選擇

          困難症的同學是不是很開森呢?

          只能通過迭代器訪問(啥叫迭代器?詳見上篇):

          set<typename>::iterator it;(這個也不多說了,和vector是一模一樣滴)

          比如,set<int>::iterator it;

          set<double>::iterator it;

          同樣的,和vector一樣可以通過*it來訪問set裡的元素啦,忍不住再嘮叨一句,迭代器就是指標。

4.set中的基本操作

 <li>insert(x):    插入元素,並且自動遞增排序噢,而且去重,時間複雜度O(logN),N為set中元素的個數。</li>
 <li>insert(first,second):    將定位器first到second之間的元素插入到set中,返回值是void</li>
 <li>find(value):    返回set中對應值為value的迭代器,時間複雜度未O(logN)!!後文會重點講(見下文5.set的注意點和優良特性),有助於加深理解O(logN),舉一反三,以後也不過多強調了qwq。</li>
 <li>begin():    返回set容器的第一個元素//橋黑板!!begin() 和 end()函式是不檢查set是否為空的,使用前最好使用empty()檢驗一下set是否為空</li>
 <li>end():    返回set容器的最後一個元素</li>
 <li>clear():    刪除set容器中的所有的元素//時間複雜度O(N)</li>
 <li>empty():    判斷set容器是否為空</li>
 <li>max_size():    返回set容器可能包含的元素最大個數</li>
 <li>size() :    返回當前set容器中的元素個數//時間複雜度O(1),賊快!</li>
 <li>rbegin:    返回的值和end()相同</li>
 <li>rend():    返回的值和rbegin()相同</li>
 <li>count():    顧名思義嘛,用來查詢set中某個某個鍵值出現的次數。但是,這個函式在set並不是很實用,因為一個鍵值在set只可能出現0或1次,這樣就變成了判斷某一鍵值是否在set出現過了。(小技巧)</li>
 <li>erase(iterator):    刪除迭代器iterator指向的值//時間複雜度O(1),賊快!注意和erase(value)不同噢</li>
 <li>erase(first,second):    刪除定位器first和second之間的值(美國人的思維是左閉右開,最後一次強調)</li>
 <li>erase(value):    刪除值為value的元素,時間複雜度O(logN)//橋黑板!!set中的刪除操作是不進行任何的錯誤檢查的,比如定位器的是否合法等等,所以用的時候自己一定要注意。</li>
 <li>lower_bound(key_value):    返回第一個大於等於key_value的定位器</li>
 <li>upper_bound(key_value):    返回最後一個大於等於key_value的定位器//二分啦!</li>

5.set的注意點和優良特性

(加深對set的理解,初學者可以先跳過此部分)

 

 <li>不能直接改變元素值,因為那樣會打亂原本正確的順序,要改變元素值必須先刪除舊元素,則插入新元素</li>
 <li>不提供直接存取元素的任何操作函式,只能通過迭代器進行間接存取,而且從迭代器角度來看,元素值是常數</li>
 <li>元素比較動作只能用於型別相同的容器(即元素和排序準則必須相同)

因為set中重寫比較函式的用的真還不多,所以就不細細的講了,等降到後面的priority_queue的時候,我會重新講一下結構體的比較函式(其實也叫優先順序設定)。

從原型可以看出,可以看出比較函式物件及記憶體分配器採用的是預設引數,因此如果未指定,它們將採用系統預設方式,
另外,利用原型,可以有效地輔助分析建立物件的幾種方式。

之前提到了set(map也一樣哈)的插入刪除效率比用其他序列容器高,簡單的講set容器內所有元素都是以節點的方式來儲存,其節點結構和連結串列差不多,指向父節點和子節點。需要插入或刪除,只要改變指標指向的節點就可以啦。(其實本質上還是因為set自帶的紅黑樹的內部結構,就是外掛啊。氣哭!STL容器各個都是身懷絕技)

set中查詢是使用二分查詢,也就是說,如果有16個元素,最多需要比較4次就能找到結果,有32個元素,最多比較5次。那麼有10000個呢?最多比較的次數為log10000,最多為14次,如果是20000個元素呢?最多不過15次。看見了吧,當資料量增大一倍的時候,搜尋次數只不過多了1次,多了1/14的搜尋時間而已。你明白這個道理後,就可以安心往裡面放入元素了。

6.set的常見用途

set最主要的作用是自動去重,並在沒有寫比較函式的情況下,預設按升序排序,因此碰到需要去重但是卻不方便直接開陣列的情況,可以嘗試用set來解決噢。

set/multiset會根據待定的排序準則,自動將元素排序。兩者不同在於前者不允許元素重複,而後者允許。

7.練習題,橋黑板!!

PAT A1063. Set Similarity (25)

 

參考:C++中關於set的自定義排序函式的書寫STL中set容器的一點總結

《演算法筆記》(胡凡,曽磊)

 


相關文章