1. 程式人生 > >STL中關於map和set的四個問題?

STL中關於map和set的四個問題?

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

  1. 為何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容器內所有元素都是以紅黑樹節點(如下)的方式來儲存,其節點
結構和連結串列差不多,指向父節點和子節點。結構圖可能如下: A / \ B C / \ / \ D E F G 因此插入的時候只需要稍做變換,把節點的指標指向新的節點就可以了。刪除的時候類似,稍做變換後把指向刪除節點的指標指向其他節點就OK了。這裡的一切操作就是指標換來換
去,和記憶體移動沒有關係。

紅黑樹的節點實現:因此插入和刪除只是指標的變化:

struct __rb_tree_node_base
{
    typedef __rb_tree_color_type color_type;
    typedef __rb_tree_node_base* base_ptr;

    color_type color;  //節點顏色,非紅即黑
    base_ptr parent;   //父節點
    base_ptr left;     //左孩子
    base_ptr right;    //右孩子
}

  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。例如: map<int, int, less<int>, Alloc<int> > intmap; 這時候在intmap中使用的allocator並不是Alloc<int>, 而是通過了轉換的Alloc,具體轉換的方法時在內部通過Alloc<int>::rebind重新定義了新的節點分配器,詳細的實現
參看徹底學習STL中的Allocator。其實你就記住一點,在map和set內面的分配器已經發生了變化,reserve方法你就不要奢望了。

  4.當資料元素增多時(10000到20000個比較),map和set的插入和搜尋速度變化如何?

如果你知道log2的關係你應該就徹底瞭解這個答案。在map和set中查詢是使用二分查詢,也就是說,如果有16個元素,最多需要比較4次就能找到結果,有32個元素,最多比較5次
。那麼有10000個呢?最多比較的次數為log10000,最多為14次,如果是20000個元素呢?最多不過15次。看見了吧,當資料量增大一倍的時候,搜尋次數只不過多了1次,多了1/1
4的搜尋時間而已。你明白這個道理後,就可以安心往裡面放入元素了。最後,對於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中使用了許多模板什麼的。其實不然,它們的區別並不在於演算法,而在於記憶體碎片。如果直接使用這些函式,你需
要自ma己去new一些節點,當節點特別多,而且進行頻繁的刪除和插入的時候,記憶體碎片就會存在,而STL採用自己的Allocator分配記憶體,以記憶體池的方式來管理這些記憶體,會大
大減少記憶體碎片,從而會提升系統的整體效能。Winter在自己的系統中做過測試,把以前所有直接用isc函式的程式碼替換成map,程式速度基本一致。當時間執行很長時間後(例如
後臺服務程式),map的優勢就會體現出來。從另外一個方面講,使用map會大大降低你的編碼難度,同時增加程式的可讀性。何樂而不為?

相關推薦

STLmapset

C++11才開始支援括號初始化!!! 關於STL中的map和set 首先來看一下關聯容器吧 關聯容器迭代器:map的value_type是pair<const key_type, mapped_type>,所以map迭代器只能改變關鍵字對映的值(mapped_type),不能

STL map set 的知識點總結

STL中的容器有順序容器 (vector,list,deque),關聯容器(map,set)還有一些其他容器。 根據不同的場合選擇不同的容器,會有意想不到的收穫。 Map是單詞mapping(對映)的縮寫 Set是單詞set(集合)的意思; Map和set內部的元素不可以重

C++ STLmapunordered_map的區別

map與unordered_map對比 map unordered_map 紅黑樹(屬於非嚴格二叉平衡搜尋樹)實現 雜湊表實現 有序 無序 —— 查詢時間複雜度為O(1

STLmapset的資料結構及底層實現

本文分析了STL的map和set vector(向量)——STL中標準而安全的陣列。只能在vector 的“前面”增加資料。 deque(雙端佇列double-ended queue)——在功能上和vector相似,但是可以在前後兩端向其中新增資料。  list(列表)——遊標

C++mapset的使用與區別

set set是一種關聯式容器,其特性如下: set以RBTree作為底層容器 所得元素的只有key沒有value,value就是key 不允許出現鍵值重複 所有的元素都會被自動排序 不能通過迭代器來改變set的值,因為set的值就是鍵 針對這五點來說,

C++模版STL map string, vector 的用法詳解

參考: 1. map 用法詳解 std map是STL的一個關聯容器,它提供一對一(其中第一個可以稱為關鍵字,每個關鍵字只能在map中出現一次,第二個可能稱為該關鍵字的值)的資料處理能力,由於這個特性,它完成有可能在我們處理一對一資料的時候,在程式設計上提

JavaMapSet容器如何存物件的

 之前看到過這樣一個問題,大概是:能否把一個物件(自定義,沒有任何方法)的兩個例項同時存放到同一個Set中去?  當時很懵,但是這幾天偶然看了Set和Map的原始碼就比較明瞭了。這裡總結一下。  首先,大體上為了儲存,有通過樹來存資料的,也有通過雜湊(has

javaMapSet的底層分析

1.HashSet底層是使用HashMap實現的。當使用add方法將物件新增到Set當中時,實際上是將該物件作為底層所維護的Map物件的key,而value則都是同一個Object物件(該物件我們用不上);其他的都是通過定義的HashMap物件實現的。 2.HashMap

JavaMapSet之間的關係(及Map.Entry)

1、通過查詢API文件: 2、Map.Entry是一個介面,所以不能直接例項化。 3、Map.entrySet( )返回的是一個collection集合,並且,這個collection中的元素是Map.Entry型別,如下圖所示: 4、 Map是Java中的

STL關於mapset問題?

STL map和set的使用雖不復雜,但也有一些不易理解的地方,如: 為何map和set的插入刪除效率比用其他序列容器高? 或許有得人能回答出來大概原因,但要徹底明白,還需要了解STL的底層資料結構。 C++ STL 之所以得到廣泛的讚譽,也被很多人使用,不只是提供了像vector, str

JavaScriptMapSet

bob 二維數組 mic span 添加 ber {} trac 語言 JavaScript的默認對象表示方法{}可以視為其他語言中的Map或者Dictionary的數據結構,即一組鍵值對。 但是JavaScript的對象有個小問題,就是鍵必須是字符串,但實際上Number

MyBatis#{}${}的區別

區別1: #{} 相當於JDBC SQL語句中的佔位符 “?”(PreparedStatement) ${} 相當於JDBC SQL語句中的連線符號 “+” (Statement) 所以,${} 存在SQL注入問題 區別2: #{} 進行輸入對映的時候,會對引數進行型別解析

c++STL 關於mapset,vector的用法

  一、vector vector相當於是一個可以存放一個任意資料型別的一個容器,通過迭代器可以對其進行相應的操作,如下:   1 vector<CString> arrTest; 2 arrTest.push_back(L"你"); 3 arr

Java基礎知識回顧之 ----- 集合List、MapSet

linked 訪問速度 因此 比較 foreach循環 代碼示例 的區別 不同的 寫法 前言 在上一篇中回顧了Java的三大特性:封裝、繼承和多態。本篇則來介紹下集合。 集合介紹 我們在進行Java程序開發的時候,除了最常用的基礎數據類型和String對象外,也經常會用到集

STL原始碼剖析之mapset

之前分析二叉搜尋樹和平衡二叉樹時,真心感覺樹的實現真是難,特別是平衡二叉樹,不平衡之後需要調整,還要考慮各種情況,累感不愛.今天看到這個紅黑樹,發現比平衡二叉樹還難,但是紅黑樹比平衡二叉樹使用的場景更多,所以平常使用時,我們需要了解紅黑樹的實現原理,如果有能力,可以自己實現,但是如果實在做不出來,也

第014講:ScalaMapHashMap原始碼剖析及程式碼實踐(從1000程式碼案例學習人工智慧大資料實戰)

第014講:Scala中Map和HashMap原始碼剖析及程式碼實踐/** * A generic trait for immutable maps. Concrete classes have to provide * functionality for the abs

C++ STLMap的按Key排序按Value排序

map是用來存放<key, value>鍵值對的資料結構,可以很方便快速的根據key查到相應的value。假如儲存學生和其成績(假定不存在重名,當然可以對重名加以區分),我們用map來進行儲存就是個不錯的選擇。 我們這樣定義,map<string, i

STL的list/set/map等容器clear之後的記憶體佔用

最近在知乎上看到一個問題: 為什麼呼叫 std::map::clear() 後記憶體佔用率沒有降低? size很大的一個map,用完後呼叫了clear()函式,按說記憶體使用率應該能降低很多,top命令觀察,結果是記憶體使用率沒有降低,為什麼呢? 求解答,謝謝。

Java把物件、物件bean、list集合、物件陣列、MapSet以及字串轉換成Json

把Java對常用的一些資料轉換成Json,以便前臺的呼叫. 物件轉換為Json public class ConvertJson { /** * 物件轉換為Json * @param obj * @

JavaMap, List, SetQueue的區別使用場景

1. Java集合類基本概念 在程式設計中,常常需要集中存放多個數據。從傳統意義上講,陣列是我們的一個很好的選擇,前提是我們事先已經明確知道我們將要儲存的物件的數量。一旦在陣列初始化時指定了這個陣列長度,這個陣列長度就是不可變的,如果我們需要儲存一個可以動態增長的資