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的區別)一樣。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章