1)Set是一種關聯容器,它用於存儲數據,而且能從一個數據集合中取出數據。它的每一個元素的值必須惟一,並且系統會根據該值來自動將數據排序。每一個元素的值不能直接被改變。【重點】內部結構採用紅黑樹的平衡二叉樹。multiset 跟set 相似,惟一的區別是容許鍵值重複!!!linux
如: 爲什麼map和set的插入刪除效率比用其餘序列容器高?ios
爲什麼每次insert以後,之前保存的iterator不會失效?程序員
爲什麼map和set不能像vector同樣有個reserve函數來預分配數據?算法
當數據元素增多時(10000到20000個比較),map和set的插入和搜索速度變化如何?數組
或許有得人能回答出來大概緣由,但要完全明白,還須要瞭解STL的底層數據結構。 C++ STL 之因此獲得普遍的讚譽,也被不少人使用,不僅是提供了像vector, string, list等方便的容器,更重要的是STL封裝了許多複雜的數據結構算法和大量經常使用數據結構操做。vector封裝數組,list封裝了鏈表,map和 set封裝了二叉樹等,在封裝這些數據結構的時候,STL按照程序員的使用習慣,以成員函數方式提供的經常使用操做,如:插入、排序、刪除、查找等。讓用戶在 STL使用過程當中,並不會感到陌生。 C++ STL中標準關聯容器set, multiset, map, multimap內部採用的就是一種很是高效的平衡檢索二叉樹:紅黑樹,也成爲RB樹(Red-Black Tree)。RB樹的統計性能要好於通常的平衡二叉樹(有些書籍根據做者姓名,Adelson-Velskii和Landis,將其稱爲AVL-樹),因此被STL選擇做爲了關聯容器的內部結構。本文並不會介紹詳細AVL樹和RB樹的實現以及他們的優劣,關於RB樹的詳細實現參看紅黑樹: 理論與實現(理論篇)。本文針對開始提出的幾個問題的回答,來向你們簡單介紹map和set的底層數據結構。數據結構
爲什麼map和set的插入刪除效率比用其餘序列容器高? 大部分人說,很簡單,由於對於關聯容器來講,不須要作內存拷貝和內存移動。說對了,確實如此。map和set容器內全部元素都是以節點的方式來存儲,其節點結構和鏈表差很少,指向父節點和子節點。less
結構圖可能以下:函數
A性能
/ /學習
B C
/ / / /
D E F G
所以插入的時候只須要稍作變換,把節點的指針指向新的節點就能夠了。刪除的時候相似,稍作變換後把指向刪除節點的指針指向其餘節點就OK了。這裏的一切操做就是指針換來換去,和內存移動沒有關係。 爲什麼每次insert以後,之前保存的iterator不會失效? 看見了上面答案的解釋,你應該已經能夠很容易解釋這個問題。iterator這裏就至關於指向節點的指針,內存沒有變,指向內存的指針怎麼會失效呢(固然 被刪除的那個元素自己已經失效了)。相對於vector來講,每一次刪除和插入,指針都有可能失效,調用push_back在尾部插入也是如此。由於爲了 保證內部數據的連續存放,iterator指向的那塊內存在刪除和插入過程當中可能已經被其餘內存覆蓋或者內存已經被釋放了。即便時push_back的時 候,容器內部空間可能不夠,須要一塊新的更大的內存,只有把之前的內存釋放,申請新的更大的內存,複製已有的數據元素到新的內存,最後把須要插入的元素放 到最後,那麼之前的內存指針天然就不可用了。特別時在和find等算法在一塊兒使用的時候,牢記這個原則:不要使用過時的iterator。 爲什麼map和set不能像vector同樣有個reserve函數來預分配數據? 我之前也這麼問,究其原理來講時,引發它的緣由在於在map和set內部存儲的已經不是元素自己了,而是包含元素的節點。也就是說map內部使用的Alloc並非map聲明的時候從參數中傳入的Alloc。例如: map, Alloc > intmap; 這時候在intmap中使用的allocator並非Alloc, 而是經過了轉換的Alloc,具體轉換的方法時在內部經過Alloc::rebind從新定義了新的節點分配器,詳細的實現參看完全學習STL中的Allocator。其實你就記住一點,在map和set內面的分配器已經發生了變化,reserve方法你就不要奢望了。 當數據元素增多時(10000和20000個比較),map和set的插入和搜索速度變化如何? 若是你知道log2的關係你應該就完全瞭解這個答案。在map和set中查找是使用二分查找,也就是說,若是有16個元素,最多須要比較4次就能找到結 果,有32個元素,最多比較5次。那麼有10000個呢?最多比較的次數爲log10000,最多爲14次,若是是20000個元素呢?最多不過15次。 看見了吧,當數據量增大一倍的時候,搜索次數只不過多了1次,多了1/14的搜索時間而已。你明白這個道理後,就能夠安心往裏面放入元素了。 最後,對於map和set Winter還要提的就是它們和一個c語言包裝庫的效率比較。在許多unix和linux平臺下,都有一個庫叫isc,裏面就提供相似於如下聲明的函數: void tree_init(void **tree); void *tree_srch(void **tree, int (*compare)(), void *data); void tree_add(void **tree, int (*compare)(), void *data, void (*del_uar)()); int tree_delete(void **tree, int (*compare)(), void *data,void (*del_uar)()); int tree_trav(void **tree, int (*trav_uar)()); void tree_mung(void **tree, void (*del_uar)()); 許多人認爲直接使用這些函數會比STL map速度快,由於STL map中使用了許多模板什麼的。其實否則,它們的區別並不在於算法,而在於內存碎片。若是直接使用這些函數,你須要本身去new一些節點,當節點特別多, 並且進行頻繁的刪除和插入的時候,內存碎片就會存在,而STL採用本身的Allocator分配內存,之內存池的方式來管理這些內存,會大大減小內存碎 片,從而會提高系統的總體性能。Winter在本身的系統中作過測試,把之前全部直接用isc函數的代碼替換成map,程序速度基本一致。當時間運行很長 時間後(例如後臺服務程序),map的優點就會體現出來。從另一個方面講,使用map會大大下降你的編碼難度,同時增長程序的可讀性。何樂而不爲?
/* set/multiset會根據待定的排序準則,自動將元素排序。二者不一樣在於前者不容許元素重複,然後者容許。 1) 不能直接改變元素值,由於那樣會打亂本來正確的順序,要改變元素值必須先刪除舊元素,則插入新元素 2) 不提供直接存取元素的任何操做函數,只能經過迭代器進行間接存取,並且從迭代器角度來看,元素值是常數 3) 元素比較動做只能用於型別相同的容器(即元素和排序準則必須相同) set模板原型://Key爲元素(鍵值)類型 template <class Key, class Compare=less<Key>, class Alloc=STL_DEFAULT_ALLOCATOR(Key) > 從原型能夠看出,能夠看出比較函數對象及內存分配器採用的是默認參數,所以若是未指定,它們將採用系統默認方式, 另外,利用原型,能夠有效地輔助分析建立對象的幾種方式 */ #include <iostream> #include <string> #include <set> using namespace std; struct strLess { bool operator() (const char *s1, const char *s2) const { return strcmp(s1, s2) < 0; } }; void printSet(set<int> s) { copy(s.begin(), s.end(), ostream_iterator<int>(cout, ", ") ); // set<int>::iterator iter; // for (iter = s.begin(); iter != s.end(); iter++) // //cout<<"set["<<iter-s.begin()<<"]="<<*iter<<", "; //Error // cout<<*iter<<", "; cout<<endl; } void main() { //建立set對象,共5種方式,提示若是比較函數對象及內存分配器未出現,即表示採用的是系統默認方式 //建立空的set對象,元素類型爲int, set<int> s1; //建立空的set對象,元素類型char*,比較函數對象(即排序準則)爲自定義strLess set<const char*, strLess> s2( strLess); //利用set對象s1,拷貝生成set對象s2 set<int> s3(s1); //用迭代區間[&first, &last)所指的元素,建立一個set對象 int iArray[] = {13, 32, 19}; set<int> s4(iArray, iArray + 3); //用迭代區間[&first, &last)所指的元素,及比較函數對象strLess,建立一個set對象 const char* szArray[] = {"hello", "dog", "bird" }; set<const char*, strLess> s5(szArray, szArray + 3, strLess() ); //元素插入: //1,插入value,返回pair配對對象,能夠根據.second判斷是否插入成功。(提示:value不能與set容器內元素重複) //pair<iterator, bool> insert(value) //2,在pos位置以前插入value,返回新元素位置,但不必定能插入成功 //iterator insert(&pos, value) //3,將迭代區間[&first, &last)內全部的元素,插入到set容器 //void insert[&first, &last) cout<<"s1.insert() : "<<endl; for (int i = 0; i <5 ; i++) s1.insert(i*10); printSet(s1); cout<<"s1.insert(20).second = "<<endl;; if (s1.insert(20).second) cout<<"Insert OK!"<<endl; else cout<<"Insert Failed!"<<endl; cout<<"s1.insert(50).second = "<<endl; if (s1.insert(50).second) {cout<<"Insert OK!"<<endl; printSet(s1);} else cout<<"Insert Failed!"<<endl; cout<<"pair<set<int>::iterator::iterator, bool> p;\np = s1.insert(60);\nif (p.second):"<<endl; pair<set<int>::iterator::iterator, bool> p; p = s1.insert(60); if (p.second) {cout<<"Insert OK!"<<endl; printSet(s1);} else cout<<"Insert Failed!"<<endl; //元素刪除 //1,size_type erase(value) 移除set容器內元素值爲value的全部元素,返回移除的元素個數 //2,void erase(&pos) 移除pos位置上的元素,無返回值 //3,void erase(&first, &last) 移除迭代區間[&first, &last)內的元素,無返回值 //4,void clear(), 移除set容器內全部元素 cout<<"\ns1.erase(70) = "<<endl; s1.erase(70); printSet(s1); cout<<"s1.erase(60) = "<<endl; s1.erase(60); printSet(s1); cout<<"set<int>::iterator iter = s1.begin();\ns1.erase(iter) = "<<endl; set<int>::iterator iter = s1.begin(); s1.erase(iter); printSet(s1); //元素查找 //count(value)返回set對象內元素值爲value的元素個數 //iterator find(value)返回value所在位置,找不到value將返回end() //lower_bound(value),upper_bound(value), equal_range(value) 略 cout<<"\ns1.count(10) = "<<s1.count(10)<<", s1.count(80) = "<<s1.count(80)<<endl; cout<<"s1.find(10) : "; if (s1.find(10) != s1.end()) cout<<"OK!"<<endl; else cout<<"not found!"<<endl; cout<<"s1.find(80) : "; if (s1.find(80) != s1.end()) cout<<"OK!"<<endl; else cout<<"not found!"<<endl; //其它經常使用函數 cout<<"\ns1.empty()="<<s1.empty()<<", s1.size()="<<s1.size()<<endl; set<int> s9; s9.insert(100); cout<<"s1.swap(s9) :"<<endl; s1.swap(s9); cout<<"s1: "<<endl; printSet(s1); cout<<"s9: "<<endl; printSet(s9); //lower_bound,upper_bound,equal_range(略) } ///////////////i測試結果///////////////////////// s1.insert() : 0, 10, 20, 30, 40, s1.insert(20).second = Insert Failed! s1.insert(50).second = Insert OK! 0, 10, 20, 30, 40, 50, pair<set<int>::iterator::iterator, bool> p; p = s1.insert(60); if (p.second): Insert OK! 0, 10, 20, 30, 40, 50, 60, s1.erase(70) = 0, 10, 20, 30, 40, 50, 60, s1.erase(60) = 0, 10, 20, 30, 40, 50, set<int>::iterator iter = s1.begin(); s1.erase(iter) = 10, 20, 30, 40, 50, s1.count(10) = 1, s1.count(80) = 0 s1.find(10) : OK! s1.find(80) : not found! s1.empty()=0, s1.size()=5 s1.swap(s9) : s1: 100, s9: 10, 20, 30, 40, 50,