1. 程式人生 > >校招準備系列4-STL容器、演算法

校招準備系列4-STL容器、演算法

STL(Standard Template Library),即標準模板庫,是一個具有工業強度的,高效的C++程式庫。它是ANSI/ISO C++標準中的一部分。該庫包含了諸多在電腦科學領域裡所常用的基本資料結構和基本演算法。堆、棧、佇列、連結串列,演算法包括查詢、排序、排列、集合操作。 STL不只是提供了像vector, string, list等方便的容器,更重要的是STL封裝了許多複雜的資料結構演算法和大量常用資料結構操作。vector封裝陣列,list封裝了連結串列,map和set封裝了二叉樹等

STL相關問題

vector記憶體如何管理,空間是否會自動減小,是否可以顯式減小?

不會自動減小,可以用swap來減小空間。

智慧指標,區別,實現,可放入容器的型別約束,如果放入了會怎麼樣

STL的容器由於要支援新增,排序等,所以元素之間 經常需要複製,所以想要放入STL標準容器的物件,都必須能拷貝及賦值。如果不能存入物件,可以考慮儲存物件的指標。

auto_ptr可以做vector的元素呢?為什麼?

不能。因為STL的標準容器規定它所容納的元素必須是可以拷貝構造和可被轉移賦值的。而auto_ptr不能,可以用shared_ptr智慧指標代替。

順序容器

概述 vector:可變大小陣列。支援快速隨機訪問。在尾部之外的位置插入或刪除元素可能很慢。 deque:雙端佇列。支援快速隨機訪問。在頭、尾部插入/刪除速度很快 list:雙向連結串列。只支援雙向順序訪問。在list中任何位置進行插入/刪除操作速度都很快。 forward_list:單向連結串列。 array(C++11新增):固定大小陣列。支援快速隨機訪問。不能新增或刪除元素 string:與vector相似的容器,但專門用於儲存字元。隨機訪問快。在尾部插入/刪除速度快。 特性

vector和string

底層實現:底層資料結構為陣列 將元素儲存在連續的記憶體空間中。因為是連續儲存,能非常好的支援隨機存取,下標運算是非常快速的,O(1)。 在中間位置或頭部插入/刪除元素會非常耗時,需要移動插入/刪除位置之後的所有元素。新增元素甚至可能會導致要分配額外的儲存空間,這時,所有元素都要移動到新的儲存空間中。 在尾部插入/刪除元素比較快。 vector在堆中分配記憶體,元素連續存放(即vector中儲存的元素是在堆上,而vector變數本身是在棧上)。會預先分配多於初始vector大小的記憶體。當size超過容量capacity時,會擴容成2倍。一旦擴容,即使進行clear,erase操作,記憶體也不會釋放,除非用swap。 對中間和開始處進行新增刪除元素操作需要移動記憶體,如果你的元素是結構或是類,那麼移動的同時還會進行構造和析構操作,所以效能不高 (最好將結構或類的指標放入vector中,而不是結構或類物件本身,這樣可以避免移動時的構造與析構)。

capacity()返回vector所能容納的元素數量(在不重新分配記憶體的情況下) size()是當前元素個數(string還有個length(),和size()是一樣的)

list和forwoar_list

底層實現:底層資料結構為雙向連結串列 在任何位置的插入/刪除操作都很快,O(1)。 但是隨機訪問元素的成本很高,需要遍歷整個容器,O(n)。(除了頭和尾元素的訪問比較快) list是雙向連結串列,元素也是在堆中存放。 list沒有空間預留習慣,所以每插入一個元素都會從記憶體中分配空間,每刪除一個元素都會釋放它佔用的記憶體。 list在哪裡新增刪除元素效能都很高,不需要移動記憶體,當然也不需要對每個元素都進行構造與析構了,所以常用來做隨機操作容器。

deque

底層實現:底層資料結構為一箇中央控制器和多個緩衝區 支援快速的隨機訪問,但是沒有vector快 在中間位置插入/刪除元素會非常耗時 在兩端插入/刪除元素很快,與list或forward_list速度相當。 deque是一個雙端佇列(double-ended queue),也是在堆中儲存內容的.它的儲存形式如下:

[堆1] --> [堆2] -->[堆3] --> … 每個堆儲存好幾個元素,然後堆和堆之間有指標指向,看起來像是list和vector的結合品。

在標準庫中vector和deque提供幾乎相同的介面,在結構上它們的區別主要在於這兩種容器在組織記憶體上不一樣,deque是按頁或塊來分配儲存器的,每頁包含固定數目的元素.相反vector分配一段連續的記憶體,vector只是在序列的尾段插入元素時才有效率,而deque的分頁組織方式即使在容器的前端也可以提供常數時間的insert和erase操作,而且在體積增長方面也比vector更具有效率

array

底層實現:陣列 是一種更安全、更容易使用的陣列型別 大小固定 不支援插入/刪除元素及改變容器大小的操作。

stack

底層一般用list或deque實現,封閉頭部即可,不用vector的原因應該是容量大小有限制,擴容耗時

queue

底層一般用list或deque實現,封閉頭部即可,不用vector的原因應該是容量大小有限制,擴容耗時 (stack和queue其實是介面卡,而不叫容器,因為是對容器的再封裝)

討論

vector每次insert或erase之後,以前儲存的iterator會不會失效?

理論上會失效,理論上每次insert或者erase之後,所有的迭代器就重新計算的,所以都可以看作會失效,原則上是不能使用過期的記憶體 但是vector一般底層是用陣列實現的,我們仔細考慮陣列的特性,不難得出另一個結論 insert時,假設insert位置在p,分兩種情況: a) 容器還有空餘空間,不重新分配記憶體,那麼p之前的迭代器都有效,p之後的迭代器都失效 b) 容器重新分配了記憶體,那麼p之後的迭代器都無效咯 erase時,假設erase位置在p,則p之前的迭代器都有效並且p指向下一個元素位置(如果之前p在尾巴上,則p指向無效尾end),p之後的迭代器都無效

vector中的reserve函式和resize函式的區別?

reserve並不改變容器內實際元素數量,它僅影響vector預先分配多大的記憶體空間,而且只有當需要的記憶體空間超過當前容量時,reserve呼叫才會改變vector的容量,此時一般將容量擴充1倍; resize只改變容器中元素的數目,而不是容器的容量。

vector擴容

如果以 大於2 倍的方式擴容,下一次申請的記憶體會大於之前分配記憶體的總和,導致之前分配的記憶體不能再被使用。 當 k =1.5 時,在幾次擴充套件以後,可以重用之前的記憶體空間了。

關聯容器

概述

按關鍵字有序儲存元素 map:關聯陣列;儲存key-value對 set:key即value,即只儲存key的容器 multimap:key可重複出現的map multiset:key可重複出現的set 無序集合 unordered_map:用雜湊函式組織的map unordered_set:用雜湊函式組織的set unordered_multimap:用雜湊函式組織的map,key可重複 unordered_multiset:用雜湊函式組織的set,key可重複

特性

set、map、multiset、multimap內部實現是用紅黑樹(一種非常高效的平衡二叉查詢樹,也稱為RB樹)。RB樹的統計效能要好於一般的 平衡二叉樹。最壞情況下的插入、刪除、查詢時間是O(logn)

unordered_set、unordered_map、unordered_multiset、unordered_multimap底層資料結構為hash表。查詢元素的時間是O(1),但是如果hash函式選擇不好的話(碰撞過多)可能會退化成O(n)。插入刪除也是O(1)的

unordered_map等hash_XXX是非STL標準的,在C++11加入?存疑

set, multiset

set和multiset會根據特定的排序準則(operator< 決定)自動將元素排序,set中元素不允許重複,multiset可以重複。 因為是排序的,所以set中的元素不能被修改,只能刪除後再新增。(set::iterator is const) set中判斷元素是否相等: if(!(A<B || B<A)),當A<B和B<A都為假時,它們相等。

map, multimap

map和multimap將key和value組成的pair作為元素,根據key的排序準則自動將元素排序,map中元素的key不允許重複,multimap可以重複。 因為是排序的,所以map中元素的key不能被修改,只能刪除後再新增。key對應的value可以修改。 向map中新增的元素的key型別必須過載<操作符用來排序。排序與set規則一致。 討論

hash_map和map的區別在哪裡?

建構函式:hash_map需要hash函式,operator==;map只需要比較函式(operator<). 儲存結構:hash_map採用hash表儲存,map一般採用 紅黑樹(RB Tree) 實現。因此其memory資料結構是不一樣的。 什麼時候需要用hash_map,什麼時候需要用map?

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

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

map和set內部儲存的已經不是元素本身了,而是包含元素的節點。也就是說map內部使用的Alloc並不是map<key, data,="" compare,="" alloc="">宣告的時候從引數中傳入的Alloc。例如: map<int, int,="" less<int="">, Alloc > intmap; 這時候在intmap中使用的allocator並不是Alloc, 而是通過了轉換的Alloc,具體轉換的方法時在內部通過Alloc::rebind重新定義了新的節點分配器,詳細的實現參看徹底學習STL中的Allocator。 其實你就記住一點,在map和set裡面的分配器已經發生了變化,reserve方法你就不要奢望了。 map的底層是一棵紅黑樹,在對節點的插入或是刪除操作中,通過旋轉來保持平衡性,最壞情況下的插入、刪除、查詢時間是O(logn)

迭代器失效

容器的插入insert和erase操作可能導致迭代器失效,對於erase操作不要使用操作之前的迭代器,因為erase的那個迭代器一定失效了,正確的做法是返回刪除操作時候的那個迭代器。

STL容器中,在迭代器刪除元素時會發生什麼?

可能發生迭代器失效

容器的插入insert和erase操作可能導致迭代器失效,對於erase操作不要使用操作之前的迭代器,因為erase的那個迭代器一定失效了,正確的做法是返回刪除操作時候的那個迭代器。

容器類 插入 刪除
list 所有迭代器不失效 有且僅有被刪除節點的迭代器失效
vector vector的容量(capacity)未改變時,僅插入位置之後的迭代器失效 被刪除節點之後的迭代器失效
set/map 所有迭代器不失效 有且僅有被刪除節點的迭代器失效
deque 在頭尾位置插入元素,可能指向其他元素的迭代器失效。 在除頭尾位置外刪除元素,所有迭代器失效。在除頭尾位置外插入元素,其他迭代器失效。

STL演算法

快排演算法的樞軸位置是怎麼選擇的?

三點中值法,取整個序列的頭、尾、中央三個位置的元素,以其中值作為樞軸。

簡單說一下next_permutation和partition的實現?

  • next_permutation(下一個排列) 首先,從最尾端開始往前尋找兩個相鄰元素,另第一個元素為i,第二個元素為ii,且滿足i<ii。找到這樣一組相鄰元素後,再從尾端開始往前檢驗,找出第一個大於i的元素j,將i,j元素對調,再將ii之後的所有元素顛倒排列。此即所求“下一個”排列組合。
  • partition 令頭端迭代器first向尾部移動,尾部迭代器last向頭部移動。當first所指的值大於或等於樞軸時就停下來,當last所指的值小於或等於樞軸時也停下來,然後檢驗兩個迭代器是否交錯。如果first仍然在last左邊,就將連著元素互換,然後各自調整一個位置(向中央逼近),再繼續進行相同的行為。如果發現兩個迭代器叫錯了,表示整個序列已經調整完畢。

partial_sort演算法

接受一個middle的index,該middle位於[first, last)的元素序列範圍內,然後重新安排[first, last),使得序列中的middle-first個最小元素以指定順序排序最終放置在[first, middle)中, 其餘的元素安置在[middle, last)內,不保證有任何指定的順序。