1. 程式人生 > >stl容器的實現原理-與資料結構的對應關係

stl容器的實現原理-與資料結構的對應關係

 STL容器的實現原理 

 
STL共有六大元件
 1、容器。2、演算法。3、迭代器。4、仿函式。6、介面卡。

STL容器的實現原理

STL來管理資料十分方便,省去了我們自己構建資料結構的時間.其實,STL的實現也是基於我們常見的資料結構.


序列式容器:
vector-陣列,元素不夠時再重新分配記憶體,拷貝原來陣列的元素到新分配的陣列中。
list-單鏈表。
deque-分配中央控制器map(並非map容器),map記錄著一系列的固定長度的陣列的地址.記住這個map僅僅儲存的是陣列的地址,真正的資料在陣列中存放著.deque先從map中央的位置(因為雙向佇列,前後都可以插入元素)找到一個數組地址,向該陣列中放入資料,陣列不夠時繼續在map中找空閒的陣列來存資料。當map也不夠時重新分配記憶體當作新的map,把原來map中的內容copy的新map中。所以使用deque的複雜度要大於vector,儘量使用vector。

stack-基於deque。

queue-基於deque。
heap-完全二叉樹,使用最大堆排序,以陣列(vector)的形式存放。
priority_queue-基於heap。
slist-雙向連結串列。

關聯式容器:
set,map,multiset,multimap-基於紅黑樹(RB-tree),一種加上了額外平衡條件的二叉搜尋樹。


hash table-散列表。將待存資料的key經過對映函式變成一個數組(一般是vector)的索引,例如:資料的key%陣列的大小=陣列的索引(一般文字通過演算法也可以轉換為數字),然後將資料當作此索引的陣列元素。有些資料的key經過演算法的轉換可能是同一個陣列的索引值(碰撞問題,可以用線性探測,二次探測來解決),STL是用開鏈的方法來解決的,每一個數組的元素維護一個list,他把相同索引值的資料存入一個list,這樣當list比較短時執行刪除,插入,搜尋等演算法比較快。


hash_map,hash_set,hash_multiset,hash_multimap-基於hash table。

綜上所述大家應該知道了什麼情況下該使用哪一個STL容器更合適,可以在適當時候避免使用一些影響效率的STL容器.


這裡我們不涉及容器的基本操作之類,只是要討論一下各個容器其各自的特點STL中的常用容器包括:順序性容器(vector、deque、list)、關聯容器(map、set)、容器介面卡(queue、stac)

STL是C/C++開發中一個非常重要的模板,而其中定義的各種容器也是非常方便我們大家使用。下面,我們就淺談某些常用的容器。這裡我們不涉及容器的基本操作之類,只是要討論一下各個容器其各自的特點。STL中的常用容器包括:順序性容器(vector、deque、list)、關聯容器(map、set)、容器介面卡(queue、stac)。

1、順序性容器

(1)vector
vector是一種動態陣列,在記憶體中具有連續的儲存空間,支援快速隨機訪問。由於具有連續的儲存空間,所以在插入和刪除操作方面,效率比較慢。vector有多個建構函式,預設的建構函式是構造一個初始長度為0的記憶體空間,且分配的記憶體空間是以2的倍數動態增長的,即記憶體空間增長是按照20,21,22,23.....增長的,在push_back的過程中,若發現分配的記憶體空間不足,則重新分配一段連續的記憶體空間,其大小是現在連續空間的2倍,再將原先空間中的元素複製到新的空間中,效能消耗比較大,尤其是當元素是非內部資料時(非內部資料往往構造及拷貝建構函式相當複雜)。vector的另一個常見的問題就是clear操作。clear函式只是把vector的size清為零,但vector中的元素在記憶體中並沒有消除,所以在使用vector的過程中會發現記憶體消耗會越來越多,導致記憶體洩露,現在經常用的方法是swap函式來進行解決:  

vector<int> V;V.push_back(1); V.push_back(2);V.push_back(1); V.push_back(2);
vector<int>().swap(V); 或者 V.swap(vector<int>());

利用swap函式,和臨時物件交換,使V物件的記憶體為臨時物件的記憶體,而臨時物件的記憶體為V物件的記憶體。交換以後,臨時物件消失,釋放記憶體。

-------------------

STL: 知道vector嗎,一個自然數陣列,要刪除其中的奇數,寫出程式碼。這個需要注意的是erase刪除結點,則該結點的iterator會失效,同時erase會返回下一個有效迭代器,所以iter++只有在偶數的時候才執行。 繼續問到iter++和++iter的區別,回答前者會產生一個臨時變數,後者的效率更高,如果前面有=的話,得到的值不一樣。 vector是怎麼儲存的,如果讓你實現vector,你怎麼做,首先說了下STL裡面的vector,記憶體如何分配的,建構函式等等。開始用陣列實現vector,然後問一定要寫成類似MS提供的STL裡面的vector,提示說不用,寫個基本框架就可以了,就直接寫vector程式碼,然後寫vector的插入操作,注意vector滿時

-------------------


(2)deque
deque和vector類似,支援快速隨機訪問。二者最大的區別在於,vector只能在末端插入資料,而deque支援雙端插入資料。deque的記憶體空間分佈是小片的連續,小片間用連結串列相連,實際上內部有一個map的指標。deque空間的重新分配要比vector快,重新分配空間後,原有的元素是不需要拷貝的。

(3)list
list是一個雙向連結串列,因此它的記憶體空間是可以不連續的,通過指標來進行資料的訪問,這使list的隨機儲存變得非常低效,因此list沒有提供[]操作符的過載。但list可以很好地支援任意地方的插入和刪除,只需移動相應的指標即可。

(4)在實際使用時,如何選擇這三個容器中哪一個,應根據你的需要而定,一般應遵循下面的原則:
    1) 如果你需要高效的隨即存取,而不在乎插入和刪除的效率,使用vector
    2) 如果你需要大量的插入和刪除,而不關心隨即存取,則應使用list
    3) 如果你需要隨即存取,而且關心兩端資料的插入和刪除,則應使用deque

2、關聯容器

(1)map
map是一種關聯容器,該容器用唯一的關鍵字來對映相應的值,即具有key-value功能。map內部自建一棵紅黑樹(一種自平衡二叉樹),這棵樹具有資料自動排序的功能,所以在map內部所有的資料都是有序的,以二叉樹的形式進行組織。這是map的模板:

template < class Key, class T, class Compare= less<Key>, class Allocator=allocator< pair<const Key,T> > > class map;

從模板中我們可以看出,再構造map時,是按照一定的順序進行的。map的插入和刪除效率比其他序列的容器高,因為對關聯容器來說,不需要做記憶體的拷貝和移動,只是指標的移動。由於map的每個資料對應紅黑樹上的一個節點,這個節點在不儲存你的資料時,是佔用16個位元組的,一個父節點指標,左右孩子指標,還有一個列舉值(標示紅黑色),所以map的其中的一個缺點就是比較佔用記憶體空間。

map簡介

 map是STL的一個關聯容器associativecontainer)之一,它提供一對一(其中第一個可以稱為關鍵字,每個關鍵字只能在map中出現一次,第二個可能稱為該關鍵字的值)的資料處理能力,由於這個特性,它完成有可能在我們處理一對一資料的時候,在程式設計上提供快速通道。這裡說下map內部資料的組織,map內部自建一顆紅黑樹(一種非嚴格意義上的平衡二叉樹),這顆樹具有對資料自動排序的功能,所以在map內部所有的資料都是有序的。

一個map是一個鍵值對序列,          即(key ,value)對。它提供基於key的快速檢索能力,在一個map中key值是唯一的。map提供雙向迭代器,即有從前往後的(iterator),也有從後往前的(reverse_iterator)。

map中key值是唯一 

    例子:如果已存在一個鍵值對(編號,使用者名稱):(1001,"jack"),而我們還想插入一個鍵值對(1001,"mike") 執行時會報錯(不是報錯,準確的說是,返回插入不成功!)。而我們又的確想這樣做,即一個鍵對應多個值,幸運的是multimap可是實現這個功能。

map中的型別定義

關聯陣列(associative array)是最有用的使用者定義型別之一,經常內建在語言中用於文字處理等。一個關聯陣列通常也稱為map,有時也稱字典(dictionary),儲存一對值。第一個值稱為key、第二個稱為對映值mapped-value。

[cpp] view plain copy print?在CODE上檢視程式碼片派生到我的程式碼片
  1. // map::begin/end
  2. #include <iostream>
  3. #include <map>
  4. int main ()  
  5. {  
  6.   std::map<char,int> mymap;  
  7.   std::map<char,int>::iterator it;  
  8.   mymap['z'] = 100;  
  9.   mymap['a'] = 200;  
  10.   mymap['z'] = 300;  
  11.   // show content:
  12.   for (std::map<char,int>::iterator it=mymap.begin(); it!=mymap.end(); ++it)  
  13.     std::cout << it->first << " => " << it->second << '\n';  
  14.   return 0;  
  15. }  
// map::begin/end
#include <iostream>
#include <map>

int main ()
{
  std::map<char,int> mymap;
  std::map<char,int>::iterator it;

  mymap['z'] = 100;
  mymap['a'] = 200;
  mymap['z'] = 300;

  // show content:
  for (std::map<char,int>::iterator it=mymap.begin(); it!=mymap.end(); ++it)
    std::cout << it->first << " => " << it->second << '\n';

  return 0;
}

[cpp] view plain copy print?在CODE上檢視程式碼片派生到我的程式碼片
  1. a => 200  
  2. z => 300  
a => 200
z => 300



(2)set
set也是一種關聯性容器,它同map一樣,底層使用紅黑樹實現,插入刪除操作時僅僅移動指標即可,不涉及記憶體的移動和拷貝,所以效率比較高。set中的元素都是唯一的,而且預設情況下會對元素進行升序排列。所以在set中,不能直接改變元素值,因為那樣會打亂原本正確的順序,要改變元素值必須先刪除舊元素,再插入新元素。不提供直接存取元素的任何操作函式,只能通過迭代器進行間接存取。set模板原型:

template <class Key, class Compare=class<Key>, class Alloc=STL_DEFAULT_ALLOCATOR(Key) > class set;
set支援集合的交(set_intersection)、差(set_difference)、並(set_union)及對稱差(set_symmetric_difference) 等一些集合上的操作。

3、容器介面卡

(1)queue
queue是一個佇列,實現先進先出功能,queue不是標準的STL容器,卻以標準的STL容器為基礎。queue是在deque的基礎上封裝的。之所以選擇deque而不選擇vector是因為deque在刪除元素的時候釋放空間,同時在重新申請空間的時候無需拷貝所有元素。其模板為:
template < TYPENAME _Sequence="deque<_TP" typeneam _Tp,> > class queue;

(2)stack
stack是實現先進後出的功能,和queue一樣,也是內部封裝了deque,這也是為啥稱為容器介面卡的原因吧(純屬猜測)。自己不直接維護被控序列的模板類,而是它儲存的容器物件來為它實現所有的功能。stack的原始碼原理和實現方式均跟queue相同。

下面是他們的區別:

我們常用到的STL容器有vector、list、deque、map、multimap、set和multiset,它們究竟有何區別,各自的優缺點是什麼,為了更好的揚長避短,提高程式效能,在使用之前需要我們瞭解清楚。

verctor

vector類似於C語言中的陣列,它維護一段連續的記憶體空間,具有固定的起始地址,因而能非常方便地進行隨機存取,即 [] 操作符,但因為它的記憶體區域是連續的,所以在它中間插入或刪除某個元素,需要複製並移動現有的元素。此外,當被插入的記憶體空間不夠時,需要重新申請一塊足夠大的記憶體並進行記憶體拷貝。值得注意的是,vector每次擴容為原來的兩倍,對小物件來說執行效率高,但如果遇到大物件,執行效率就低了。

list

list類似於C語言中的雙向連結串列,它通過指標來進行資料的訪問,因此維護的記憶體空間可以不連續,這也非常有利於資料的隨機存取,因而它沒有提供 [] 操作符過載。

deque

deque類似於C語言中的雙向佇列,即兩端都可以插入或者刪除的佇列。queue支援 [] 操作符,也就是支援隨機存取,而且跟vector的效率相差無幾。它支援兩端的操作:push_back,push_front,pop_back,pop_front等,並且在兩端操作上與list的效率
也差不多。或者我們可以這麼認為,deque是vector跟list的折中。

map

map類似於資料庫中的1:1關係,它是一種關聯容器,提供一對一(C++ primer中文版中將第一個譯為鍵,每個鍵只能在map中出現一次,第二個被譯為該鍵對應的值)的資料處理能力,這種特性了使得map類似於資料結構裡的紅黑二叉樹。

multimap

multimap類似於資料庫中的1:N關係,它是一種關聯容器,提供一對多的資料處理能力。

set

set類似於數學裡面的集合,不過set的集合中不包含重複的元素,這是和vector的第一個區別,第二個區別是set內部用平衡二叉樹實現,便於元素查詢,而vector是使用連續記憶體儲存,便於隨機存取。

multiset

multiset類似於數學裡面的集合,集合中可以包含重複的元素。

小結

在實際使用過程中,到底選擇這幾種容器中的哪一個,應該根據遵循以下原則:

1、如果需要高效的隨機存取,不在乎插入和刪除的效率,使用vector;

2、如果需要大量的插入和刪除元素,不關心隨機存取的效率,使用list;

3、如果需要隨機存取,並且關心兩端資料的插入和刪除效率,使用deque;

4、如果打算儲存資料字典,並且要求方便地根據key找到value,一對一的情況使用map,一對多的情況使用multimap;

5、如果打算查詢一個元素是否存在於某集合中,唯一存在的情況使用set,不唯一存在的情況使用multiset。

常用STL容器舉例

一 常用容器舉例 

1 vector:

       vector類似於動態陣列,直接訪問元素,從後面快速插入或者刪除,示例程式碼如下:

  1. #include <iostream>
  2. #include <vector>//包含vector
  3. usingnamespace std;//指定名稱空間
  4. int main()  
  5. {  
  6.     cout<<"----------vector test-----------"<<endl;  
  7.     //定義一個vector
  8.     vector <int> vect;  
  9.     vector <int> vect1(12);//12個int型別元素,每個元素的初始值均為0
  10.     vector <int> vect2(12,9);//12個int,初試值均為9
  11.     //使用陣列初始化vector
  12.     int a[]={0,1,2,3,4,5,6,7,8,9,0};  
  13.     //vector <資料型別> <容器名> (<開始地址>,<結束地址的下一個地址> )。執行過vt中元素為1,2,3
  14.     vector <int> vt(a+1,a+4);  
  15.     //在尾部壓入3個值
  16.     vt.push_back(1);  
  17.     vt.push_back(2);  
  18.     vt.push_back(3);  
  19.     //定義迭代器iterator
  20.     vector <int>::iterator iter=vt.begin();//起始地址
  21.     vector <int>::iterator iter_end=vt.end();//結束地址,兩個地址都是指標型別
  22.     //遍歷vt
  23.     for(;iter!=iter_end;iter++)  
  24.     {  
  25.         cout<<*iter<<endl;  
  26.     }  
  27.     //彈出一個元素
  28.     vt.pop_back();  
  29.     //以下兩行重新獲得起始和結尾地址
  30.     iter=vt.begin();  
  31.     iter_end=vt.end();  
  32.     cout<<"----------executed pop_back------"<<endl;  
  33.     for(;iter!=iter_end;iter++)  
  34.     {  
  35.         cout<<*iter<<endl;  
  36.     }  
  37.     //插入元素
  38.     cout<<"----------insert into------------"<<endl;  
  39.     //插入格式:vector.insert(<起始地址>,<插入的數量>,<元素值>);如果插入的數量為1,則第二個引數可以被省略
  40.     vt.insert(vt.begin()+1,3,9);  
  41.     iter=vt.begin();  
  42.     iter_end=vt.end();  
  43.     for(;iter!=iter_end;iter++)  
  44.     {  
  45.         cout<<*iter<<endl;  
  46.     }  
  47.     //刪除元素
  48.     cout<<"----------erase-------------------"<<endl;  
  49.     //刪除格式1為:vector.erase(<刪除元素的地址>);
  50.     //刪除格式2為:vector.erase(<刪除元素的起始地址>,<終止地址>);
  51.     iter=vt.begin();  
  52.     iter_end=vt.end();  
  53.     vt.erase(iter+1,iter_end);//刪除第二個到最後一個的元素
  54.     iter_end=vt.end();  
  55.     for(;iter!=iter_end;iter++)  
  56.     {  
  57.         cout<<*iter<<endl;  
  58.     }  
  59.     return 1;  
  60. }  

   2  list

  list 為雙向連結串列,可以從任何地方插入或者刪除的,其示例程式碼如下:

  1. #include <iostream>
  2. #include <list>
  3. usingnamespace std;  
  4. 相關推薦

    stl容器實現原理-資料結構對應關係

     STL容器的實現原理    STL共有六大元件  1、容器。2、演算法。3、迭代器。4、仿函式。6、介面卡。 STL容器的實現原理 STL來管理資料十分方便,省去了我們自己構建資料結構的時間.其實,STL的實現也是基於我們常見的資料結

    高階語言資料結構關係

    資料結構:邏輯結構+儲存結構,所以說資料結構是儲存資料的一種方式 邏輯結構:一對一+一對多+多對多       線性結構+樹形結構+圖形結構 儲存結構:連續儲存+鏈式儲存 邏輯結構和儲存結構的關係: 每一種邏輯結構都可以用對應的儲存結構實現儲存 線性結構:連續儲存+

    STL容器資料結構對應關係

    STL中的常用容器包括:順序性容器(vector、deque、list)、關聯容器(map、set)、容器介面卡(queue、stac)。 2、關聯容器 (1)map map是一種關聯容器,該容器用唯一的關鍵字來對映相應的值,即具有key-value功能。map內部自建一

    Hadoop技術內幕:深入解析MapReduce架構設計實現原理 (大資料技術叢書).epub

      【下載地址】 《Hadoop技術內幕:深入解析MapReduce架構設計與實現原理》內容簡介:“Hadoop技術內幕”共兩冊,分別從原始碼的角度對“Common+HDFS”和“MapReduce的架構設計和實現原理”進行了極為詳細的分析。《Hadoop技術內幕:深入解析M

    python演算法資料結構013--二叉樹的實現及按先序,後序,中序遍歷的遞迴實現

    二叉樹的深度優先遍歷: (可以用遞迴或者堆疊實現) 先序:根節點->左子樹->右子樹 中序: 左子樹->根節點->右子樹 後序:左子樹->右子樹->根節點 二叉樹按廣度優先遍歷:從上到下,從左到右遍歷按層次遍歷(利用佇列實現) cl

    【演算法資料結構專場】BitMap演算法基本操作程式碼實現

    上篇我們講了BitMap是如何對資料進行儲存的,沒看過的可以看一下【演算法與資料結構專場】BitMap演算法介紹 這篇我們來講一下BitMap這個資料結構的程式碼實現。 回顧下資料的儲存原理 一個二進位制位對應一個非負數n,如果n存在,則對應的二進位制位的值為1,否則為0。這個時候,我們的第一個問題:我們在

    《redis設計實現》1-資料結構物件篇

    前言 redis效能為什麼這麼出色?它與其他快取中介軟體有什麼區別? redis底層使用了哪些資料結構支撐它如此高效的效能? 內部豐富的資料型別底層為什麼都使用至少兩種資料結構實現?分別是什麼? 如果合理的使用redis才能發揮它最大的優勢? 學習完《redis設計與實現》前面關於資

    python演算法資料結構002--利用列表實現棧的功能

    class Statck(object): """ 棧:後進先出的資料結構 利用列表實現棧的基本功能。 """ def __init__(self): self.items=[] def

    C語言實現(摘自資料結構演算法分析 C語言描述)

    一、概述: 棧(stack)是限制插入和刪除只能在一個位置上進行的表,該位置是表的末端,叫做棧的頂(top)。對棧的基本操作有Push(進棧)和Pop(出棧),前者相當於插入,後者則是刪除最後插入的元素。 棧有時又叫做LIFO(後進先出)表。在圖1中描述的模型只象徵著Pus

    棧的鏈式結構表示實現——自己寫資料結構

    今天給大家介紹棧的鏈式結構,用dev-c++4.9.9.2除錯通過,少廢話直接上程式碼: 資料結構體存放檔案stacklist.h檔案如下 #ifndef _STACKLIST_H_ #define _STACKLIST_H_ typedef struct _No

    《 常見演算法資料結構》符號表ST(2)——初等實現分析和有序符號表

    符號表(Symbol Table) (2) 本系列文章主要介紹常用的演算法和資料結構的知識,記錄的是《Algorithms I/II》課程的內容,採用的是“演算法(第4版)”這本紅寶書作為學習教材的,語言是java。這本書的名氣我不用多說吧?豆瓣評分9.

    演算法資料結構-棧的基本操作C語言實現

    序言 複習棧的基本操作及其C語言實現,主要以鏈式棧為例。 本文將介紹棧的以下基本操作: 棧的建立(順序棧和鏈式棧) 棧的初始化 棧遍歷 棧清空/銷燬 判斷棧是否為空 求棧的長度 返回並刪除棧頂元素 1. 棧建立 - 順序棧和鏈式棧 //順序棧的

    演算法資料結構複習——遞迴實現計算漢諾塔遊戲步驟

     漢諾塔遊戲  移動盤子將一定數量的盤子從第一個地方放到第三個地方,且大盤子不能放在小盤子上面,一次只能移動一次盤子   /** * */ package ch07; /** * @author lixin * @date 2018年7月23日 * @Des

    程式設計師程式碼面試指南:IT名企演算法資料結構題目最優解-字串問題:C/C++語言實現

    程式設計師程式碼面試指南-字串問題:C/C++語言實現 以下程式執行環境:VC6++ 看到左老師出的書:程式設計師程式碼面試指南:IT名企演算法與資料結構題目最優解,都是Java實現,為了刷題,但是職位是C/C++,以下是我用C/C++實現的程式碼 題目介紹略簡單,以後補上

    STL容器中list迭代器iterator的模擬實現

    list在容器中結構是有一個頭結點_head,頭結點指向第一個結點,尾結點指向頭結點,它為雙向迴圈連結串列,在其中它有自己的迭代器可以類似於智慧指標,用於資料的訪問和演算法的配合。 程式碼實現: #include <iostream> #include <

    演算法資料結構--實現線性表的合併操作(合併後按非遞減排列)

    /*檔名稱:實現線性表的合併操作(合併後按非遞減排列)*/#include <bits/stdc++.h> using namespace std; #define LIST_INIT_SIZE 100 //線性表儲存空間的初始分配量

    演算法資料結構基礎8:C++實現有向圖——鄰接表儲存

    前面實現了連結串列和樹,現在看看圖。 連結串列是一對一的對應關係; 樹是一對多的對應關係; 圖是多對多的對應關係。 圖一般有兩種儲存方式,鄰接表和鄰接矩陣。 先看鄰接表。 鄰接表就是將圖中所有的點用一個數組儲存起來,並將此作為一個連結串列的頭, 連結串列中儲存跟這個點相鄰的

    演算法資料結構基礎9:C++實現有向圖——鄰接矩陣儲存

    鄰接矩陣的儲存比鄰接表實現起來更加方便,也更加容易理解。 鄰接矩陣就是用一個二維陣列matrix來儲存每兩個點的關係。如果兩個點m,n之間有邊,將陣列matrix[]m[m]設為1,否則設為0。 如果有權,則將matrix[m]n[]設為權值,定義一個很大或者很小的數(只要

    演算法資料結構-佇列的基本操作C語言實現

    序言 佇列有順序佇列和鏈式佇列,順序佇列通過陣列方式來實現,鏈式佇列通過連結串列方式來實現。 陣列方式實現便於資料訪問(大小和空間確定),連結串列方式實現便於資料操作(插入和刪除靈活)。 這裡介紹

    python演算法資料結構-二叉樹的程式碼實現(46)

    一、二叉樹回憶   上一篇我們對資料結構中常用的樹做了介紹,本篇部落格主要以二叉樹為例,講解一下樹的資料結構和程式碼實現。回顧二叉樹:二叉樹是每個節點最多有兩個子樹的樹結構。通常子樹被稱作“左子樹”(left subtree)和“右子樹”(right subt