C++STL常見面試題

黨偉_90發表於2018-04-16

1.C++ STL 之所以得到廣泛的讚譽,也被很多人使用,不只是提供了像vector, string, list等方便的容器,更重要的是STL封裝了許多複雜的資料結構演算法和大量常用資料結構操作。vector封裝陣列,list封裝了連結串列,map和set封裝了二叉樹等

2.標準關聯容器set, multiset, map, multimap內部採用的就是一種非常高效的平衡檢索二叉樹:紅黑樹,也成為RB樹(Red-BlackTree)。RB樹的統計效能要好於一般的 平衡二叉樹

3.STL map和set的使用雖不復雜,但也有一些不易理解的地方,如:  

map: type pair <constKey, T> ,很多不同的 const Key 對應的 T 物件的一個集合,所有的記錄集中只要 const Key 不一樣就可以, T 無關! set: type const Key. 只存單一的對 const Key ,沒有 map  T 對像!可以看成 map 的一個特例

(1)為何map和set的插入刪除效率比用其他序列容器高?,樹

答:因為對於關聯容器來說,不需要做記憶體拷貝和記憶體移動。說對了,確實如此。map和set容器內所有元素都是以節點的方式來儲存,其節點結構和連結串列差不多,指向父節點和子節點

(2)為何每次insert之後,以前儲存的iterator不會失效?

答:iterator這裡就相當於指向節點的指標,記憶體沒有變,指向記憶體的指標怎麼會失效呢(當然被刪除的那個元素本身已經失效了)。相對於vector來說,每一次刪除和插入,指標都有可能失效,呼叫push_back在尾部插入也是如此。因為為了保證內部資料的連續存放,iterator指向的那塊記憶體在刪除和插入過程中可能已經被其他記憶體覆蓋或者記憶體已經被釋放了。即使時push_back的時候,容器內部空間可能不夠,需要一塊新的更大的記憶體,只有把以前的記憶體釋放,申請新的更大的記憶體,複製已有的資料元素到新的記憶體,最後把需要插入的元素放到最後,那麼以前的記憶體指標自然就不可用了。特別時在和find等演算法在一起使用的時候,牢記這個原則:不要使用過期的iterator。

(3)為何map和set不能像vector一樣有個reserve函式來預分配資料?

答:我以前也這麼問,究其原理來說時,引起它的原因在於在map和set內部儲存的已經不是元素本身了,而是包含元素的節點。也就是說map內部使用的Alloc並不是map<Key, Data, Compare, Alloc>宣告的時候從引數中傳入的Alloc。例如:

4.set, multiset 
set和multiset會根據特定的排序準則自動將元素排序,set中元素不允許重複,multiset可以重複。

因為是排序的,所以set中的元素不能被修改,只能刪除後再新增。 
向set中新增的元素型別必須過載<操作符用來排序。排序滿足以下準則: 
1、非對稱,若A<B為真,則B<A為假。 
2、可傳遞,若A<B,B<C,則A<C。 
3、A<A永遠為假。

set中判斷元素是否相等: 
if(!(A<B || B<A)),當A<B和B<A都為假時,它們相等。 

5.map,multimap 
map和multimap將key和value組成的pair作為元素,根據key的排序準則自動將元素排序,map中元素的key不允許重複,multimap可以重複。

map<key,value>

因為是排序的,所以map中元素的key不能被修改,只能刪除後再新增。key對應的value可以修改。

向map中新增的元素的key型別必須過載<操作符用來排序。排序與set規則一致。

6. List的功能方法 
實際上有兩種List: 一種是基本的ArrayList,其優點在於隨機訪問元素,另一種是更強大的LinkedList,它並不是為快速隨機訪問設計的,而是具有一套更通用的方法。 
List : 次序是List最重要的特點:它保證維護元素特定的順序。List為Collection新增了許多方法,使得能夠向List中間插入與移除元素(這隻推薦LinkedList使用。)一個List可以生成ListIterator,使用它可以從兩個方向遍歷List,也可以從List中間插入和移除元素。

ArrayList : 由陣列實現的List。允許對元素進行快速隨機訪問,但是向List中間插入與移除元素的速度很慢。ListIterator只應該用來由後向前遍歷ArrayList,而不是用來插入和移除元素。因為那比LinkedList開銷要大很多。 
LinkedList : 對順序訪問進行了優化,向List中間插入與刪除的開銷並不大。隨機訪問則相對較慢。(使用ArrayList代替。)還具有下列方法:addFirst(), addLast(), getFirst(),getLast(), removeFirst() 和 removeLast(), 這些方法 (沒有在任何介面或基類中定義過)使得LinkedList可以當作堆疊、佇列和雙向佇列使用

7..1 hash_map和map的區別在哪裡?

建構函式。hash_map需要hash函式,等於函式;map只需要比較函式(小於函式).

儲存結構。hash_map採用hash表儲存,map一般採用 紅黑樹(RB Tree) 實現。因此其memory資料結構是不一樣的。

7.2 什麼時候需要用hash_map,什麼時候需要用map?

總體來說,hash_map 查詢速度會比map快,而且查詢速度基本和資料資料量大小,屬於常數級別;而map的查詢速度是log(n)級別。並不一定常數就比log(n)小,hash還有hash函式的耗時,明白了吧,如果你考慮效率,特別是在元素達到一定數量級時,考慮考慮hash_map。但若你對記憶體使用特別嚴格,希望程式儘可能少消耗記憶體,那麼一定要小心,hash_map可能會讓你陷入尷尬,特別是當你的hash_map物件特別多時,你就更無法控制了,而且hash_map的構造速度較慢。

現在知道如何選擇了嗎?權衡三個因素: 查詢速度, 資料量, 記憶體使用。

8.一些使用上的建議: 
Level 1 - 僅僅作為Map使用:採用靜態陣列 
Level 2 - 儲存定長資料,使用時也是全部遍歷:採用動態陣列(長度一開始就固定的話靜態陣列也行) 
Level 3 - 儲存不定長陣列,需要動態增加的能力,側重於尋找資料的速度:採用vector 
Level 3 - 儲存不定長陣列,需要動態增加的能力,側重於增加刪除資料的速度:採用list 
Level 4 - 對資料有複雜操作,即需要前後增刪資料的能力,又要良好的資料訪問速度:採用deque 
Level 5 - 對資料中間的增刪操作比較多:採用list,建議在排序的基礎上,批量進行增刪可以對執行效率提供最大的保證 
Level 6 - 上述中找不到適合的:組合STL容器或者自己建立特殊的資料結構來實現

9.

(1).vector - 會自動增長的陣列

vector<int>vec(10) //一個有10個int元素的容器 
vector<float> vec(10, 0.5)//一個有10個float元素的容器,並且他們得值都是0.5 
vector<int>::iterator iter; 
for(iter = vec.begin(); iter != vec.end(); iter++) 

//. . . . . . . 
}

vector由於陣列的增長只能向前,所以也只提供了後端插入和後端刪除, 
也就是push_back和pop_back。當然在前端和中間要運算元據也是可以的, 
用insert和erase,但是前端和中間對資料進行操作必然會引起資料塊的移動, 
這對效能影響是非常大的。

最大的優勢就是隨機訪問的能力。

vector<T1>::iterator相關的方法有: 
begin():用來獲得一個指向vector第一個元素的指標 
end():用來獲得一個指向vector最後一個元素之後的那個位置的指標,注意不是指向最後一個元素。 
erase(vector<T1>::iterator):用來刪除作為引數所傳入的那個iterator所指向的那個元素。

(2).list - 擅長插入刪除的連結串列

list<string>Milkshakes; list<int> Scores;

push_back, pop_backpush_front. pop_front

list是一個雙向連結串列的實現。 
為了提供雙向遍歷的能力,list要比一般的資料單元多出兩個指向前後的指標

一個使用iterator來刪除元素的例子 
list<string> stringList; 
list<string>::iterator iter; 
advance(iter, 5); //將iterator指向stringList的第五個元素 
stringList.erase(iterator);//刪除 
那麼刪除操作進行以後,傳入erase()方法的iterator指向哪裡了呢?它指向了刪除操作前所指向的那個元素的後一個元素。

(3).deque - 擁有vector和list兩者優點的雙端佇列

(4).這三個模板的總結 比較和一般使用準則 
這三個模板都屬於序列類别範本,可以看到他們有一些通用方法 
size():得到容器大小 
begin():得到指向容器內第一個元素的指標(這個指標的型別依容器的不同而不同) 
end():得到指向容器內最後一個元素之後一個位置的指標 
erase():刪除傳入指標指向的那個元素 
clear():清除所有的元素 
==運算子:判斷兩個容器是否相等 
=運算子:用來給容器賦值 
上面的三個模板有各自的特點 
vector模板的資料在記憶體中連續的排列,所以隨機存取元素(即通過[]運算子存取)的速度最快,這一點和陣列是一致的。同樣由於它的連續排列,所以它在除尾部以外的位置刪除或新增元素的速度很慢,在使用vector時,要避免這種操作。 
list模板的資料是鏈式儲存,所以不能隨機存取元素。它的優勢在於任意位置新增 刪除元素的速度。 
deque模板是通過連結若干片連續的資料實現的,所以均衡了以上兩個容器的特點

相關文章