1. 程式人生 > >C++STL中的hash_map 雜湊表

C++STL中的hash_map 雜湊表

map與hash_map

map與hash_map都是在C++STL中常用的資料結構。
map:儲存資料結構是採用紅黑樹實現,提供了key-value的儲存和查詢功能,查詢速度可達log(n)。
hash_map:基於hash_table(雜湊表)儲存,相對map來說,他的查詢速度大大的降低,幾乎可以看成是常熟時間;但是代價就是消耗更多的記憶體(但是在現在記憶體越來越大的情況下,用記憶體換時間的選擇十分值得)。

他們之間的區別:

  • 建構函式:hash_map需要hash函式,比較函式(等於函式);map只需要比較函式(小於函式)。
  • 儲存結構:hash_map採用hash表儲存,map一般採用紅黑樹(RB Tree)實現。

什麼時候選擇hash_map和map(使用場景):

  • hash_map 查詢速度會比map快,而且查詢速度基本為資料資料量大小,屬於常數級別。
  • map的查詢速度是log(n)級別。

但這並不意味著一定常數就比log(n)小:因為在hash_map中,每一個元素的儲存都需要經過hash函式轉換為對應的hash值,這些都是需要時間的。
如果考慮查詢效率並對記憶體大小沒有要求,當元素達到一定數量級時,考慮hash_map。
如果要求儘可能少消耗記憶體,那麼hash_map可能會不那麼適合,特別是當hash_map物件特別多時,記憶體消耗就更大了,而且hash_map的構造速度也比較慢。

總的來說,權衡他們因素有三個:查詢速度, 資料量, 記憶體使用

hash_map

hash_map需要一個hash函式和比較函式(如果不提供,在符合的情況下,STL會為我們提供預設的函式):使用一個下標範圍比較大的陣列來儲存元素其中的每個元素的關鍵字都與一個hash值(即陣列下標)相對應,於是用這個陣列單元來儲存這個元素,這個陣列單元稱之為“桶。
那麼如何獲得hash值呢:在儲存資料時,hash函式會根據資料的key值得到該資料的hash值。

但也有例外的情況:hash值和元素關鍵字並不一定是一一對應的,根據hash函式很可能會對不同的元素的key生成相同的hash值,從而把不同的元素放在了相同的“桶”,這種情況稱之為“衝突”。因此雜湊表有“直接定址

”與“解決衝突”兩個特點。

hash_map會在一開始分配一大片記憶體,形成許多“桶”。在之後插入元素時,根據以下步驟:

  1. 獲取元素的key值
  2. 根據key值通過hash函式得到hash值
  3. 得到該hash值的“桶”號
  4. 將元素(key和value)存放在“桶”中

而查詢元素的步驟為:

  1. 獲得key值
  2. 根據key值通過hash函式得到hash值
  3. 得到相應的“桶”號
  4. 比較桶內元素是否與key值相同
  5. (若存在該key對應的資料)取出該key的value

使用hash_map

#include<hash_map>
//細節省略......
hash_map<string, int> hashmap;
//STL會為我們預設的提供hash函式與比較函式
//該宣告等同於hash_map<string, int, hash<string>, equal_to<int> > mymap;

C++支援的提供預設hash函式對應的資料型別有:char*,const char*,char,unsigned char,signed char,short,unsigned short,int ,unsigned int,long ,unsigned long
也就是說,以上的資料型別,STL會為我們提供預設的hash函式;而對於其他的資料型別,我們需要自己提供(例如string):

自定義hash函式

struct MyHash{
        size_t operator()(const string& str) const
        {
                unsigned long hash_value = 0;
                for (size_t i = 0 ; i < str.size() ; i ++)
                hash_value= 5*hash_value+ str[i];
                return size_t(hash_value);
        }
};

宣告自己的hash函式需要遵循以下幾點:

  1. 使用strut ,並重載operator()
  2. 返回 size_t
  3. 形參需要為hash的key的資料型別
  4. const函式

寫出了自己的hash函式,上面的hash_map宣告需要改為:

hash_map<string, int , MyHash> hashmap;

接下來寫比較函式:

自定義比較函式

在hash_map中,要比較桶內的資料和key是否相等 。
假如我們儲存的是一個自定義資料型別:
第一種設計比較函式的方法,就是過載==操作符,使用equal_to比較:

struct stu{
	int ID;
	int data;
	//過載==
	bool operator == (const stu & Temp){
		return ((ID==Temp.ID)&&(data==Temp.data));
	}
};

過載了stu資料結構的==之後,使用equal_to

hash_map<string, stu, MyHash,equal_to<stu>> hashmap;

equal_to的定義:
template <class _Tp>
struct equal_to : public binary_function<_Tp,_Tp,bool>
{
bool operator()(const _Tp& __x, const _Tp& __y) const { return __x == __y; }
};

另一種方法,就是寫一個函式物件

struct compare_func{
        bool operator()(const char* s1,const char* s2) const{
                return strcmp(s1,s2)==0;        
        }
};

在有了conpare_func之後,就可以使用hash_map了:

hash_map<const char*a,string , hash<const char*>, compare_func> StrMap;

使用typedef定義hash_map

typedef hash_map<const char*, string, hash<const char*>, compare_str> StrMap;
//..........
StrMap mymap;
mymap["ABC"]="aaaaaaa";
mymap["EFG"]="bbbbbbb";
//..........

因此如果我們想在hash_map中加入自己的資料型別,我們只需要做兩件事:你只要做兩件事,定義hash函式,定義比較(等於)函式

hash_map 函式

hash_map的函式和map的函式差別不大,以下為常用的函式

  1. hash_map(size_type n):為了程式執行效率,這個引數十分重要,n為hash桶的個數,個數越多,hash_map發生衝突的概率就越小,效率也越高;但相應的記憶體消耗會變大。
  2. const_iterator find(const key_type& k) const:用於查詢,引數為key值,返回一個該引數位置的迭代器。
  3. data_type&operator[](const key_type& k ):類似map的[key]下標查詢,但注意的是與map一樣,若無對應key的資料,會增加一個key的元素。
  4. const_iterator find(const key& key) const:find()函式可與上一條[key]下標查詢相對應,若無該key元素存在,他不會增加一個該key的元素。
  5. insert 函式:在容器中不包含key值時,insert函式和[]操作符的功能差不多,但注意的是,當元素越來越多,為了效率,程式會申請更多的記憶體以存放更多的桶及其元素,這時在insert後,iterator不一定依然有效。
  6. erase 函式:但在SGI STL中,erase並不會自動回收記憶體,因此呼叫erase後,其他元素的iterator還是可用的。

相關的hash容器

容器有set, multimap, multiset
hash 容器除了hash_map之外,還有hash_set, hash_multimap, has_multiset
這些容器使用起來和(set, multimap, multiset的區別)與(hash_map和map的區別)一樣。