C++中的Hash容器总结
散列Hash函數是一種特殊的映射函數, 散列表Hash Table由散列函數所產生的一種數據結構. 這是一種非常重要的數據結構.
首先, 先了解散列表在數據結構方面的基礎:
散列表是用于存儲動態集的一種非常有效的數據結構。通過散列函數h(key)的計算結果,直接得到key關鍵字表示的元素的存儲地址。散列技術中,可能會有兩個不同的key1和key2,通過h(key)計算得到的地址是一樣的,這就發生了沖突。散列技術中散列函數h(key)和解決沖突的技術是最關鍵的問題。
散列函數要求(1)能快速的計算;(2) 計算結果分布要均勻。
散列函數有如下幾種常用的:(1) 除留余數法;(2) 平方取中法;(3) 折疊法;(4) 數字分析法。
1,除法散列法(除留余數法)
最直觀的一種,上圖使用的就是這種散列法,公式:
index = value % 16
學過匯編的都知道,求模數其實是通過一個除法運算得到的,所以叫"除法散列法"。
2,平方散列法(平方取中法)
求index是非常頻繁的操作,而乘法的運算要比除法來得省時(對現在的CPU來說,估計我們感覺不出來),所以我們考慮把除法換成乘法和一個位移操作。公式:
index = (value * value) >> 28
如果數值分配比較均勻的話這種方法能得到不錯的結果,但還有個問題,value如果很大,value * value會溢出的,但我們這個乘法不關心溢出,因為我們根本不是為了獲取相乘結果,而是為了獲取index。
3,斐波那契(Fibonacci)散列法
平方散列法的缺點是顯而易見的,所以我們能不能找出一個理想的乘數,而不是拿value本身當作乘數呢?答案是肯定的。
(1),對于16位整數而言,這個乘數是40503
(2),對于32位整數而言,這個乘數是2654435769
(3),對于64位整數而言,這個乘數是11400714819323198485
這幾個"理想乘數"是如何得出來的呢?這跟一個法則有關,叫黃金分割法則,而描述黃金分割法則的最經典表達式無疑就是著名的斐波那契數列.
對我們常見的32位整數而言,公式:
index?=?(value?*?2654435769)?>>?28解決沖突的方法有:(1)拉鏈法(開散列法);(2) 開地址法(閉散列法)。
拉鏈法是通過在各地址構建一個鏈表來解決多元素同地址的問題。開地址法根據探查序列的不同,又分為(1)線性探查法;(2) 二次探查法;(3) 雙散列法。等,其中線性探查法會發生基本聚集,為解決基本聚集采用二次探查法,不過其也會發生二次聚集,雙散列法可消除二次聚集。
C++對這種數據結構提供了很好的支持.
現代C++最主要的一個特點就是使用STL. STL是C++標準化時所產生的一個非常好的標準模板庫. 模板編程帶來了C++編程的一個變革. 但是STL的制定還是花費了很長的時間的. 在制定STL的過程中, 曾經有C++標準委員會的委員提出將Hash數據結構加入到STL中,但是,該提案提出的時間比較晚, C++標準委員會不想再將標準完成的時間拖延下去, 所以沒有將Hash加入到最初的STL中去. 所以在最初的STL中, 我們只能看到vector, list, set, map 等容器. 但是之后的hash相關的容器加入到了C++的TR1庫中去了. TR1 庫是準備在C++0x標準時轉化為新的C++標準. 現在新的C++標準已經制定完畢, 是C++11. Hash相關的數據結構也已經從TR1中加入.
其實在hash相關的容器進入TR1之前, C++編譯器的其他廠商就已經實現了自己的hash容器了. 一般這些容器的命名都是hash_set 或hash_map, 這其中最著名的就是SGI 的STL中所實現的hash容器. 其實這也導致了以后C++標準中對hash容器命名的變化. 為了與之前的名稱區別開來, C++標準委員會將標準中的容器命名為unordered_set和unordered_map.
C++還有一個比較著名庫是boost庫, 其實C++標準中的hash容器都是從boost中轉化來的.
下面是摘自<<Boost程序庫完全開放指南>>
下面一段摘自<<C++標準庫擴展權威指南>>
?
SGI STL中提供提供的與hash相關的數據結構
SGI STL中提供了4個容器: hash_set, hash_map, hash_multiset, hash_multimap
還有一個hash函數:hash
在linux和Windows平臺上, 對SGISTL的使用有些差別:
在Linux下g++的形式:
頭文件:: #include <ext/hash_map>
命名空間:: using namespace __gnu_cxx;
使用方法上和map沒有什么大的區別,
#include?<ext/hash_map>using?namespace?__gnu_cxx;
hash_map<key_type,value_type>?obj;
hash_map<key_type,value_type>::iterator?iter?=?obj.begin();
在Windows下VC++的形式:
和map的使用方法一樣,沒有命名空間,直接#include <hash_map>就可以使用了,就像直接#include <map>一樣。
(1) hash_set 容器
容器聲明: hash_set<Key, HashFcn, EqualKey, Alloc>
簡單的示例程序如下:
#include?<iostream>#include?<ext/hash_set>
#include?<cstring>
using?namespace?std;
using?namespace?__gnu_cxx;
struct?eqstr
{
??bool?operator()(const?char*?s1,?const?char*?s2)?const
??{
????return?strcmp(s1,?s2)?==?0;
??}
};
void?lookup(const?hash_set<const?char*,?hash<const?char*>,?eqstr>&?Set,??????const?char*?word)
{
??hash_set<const?char*,?hash<const?char*>,?eqstr>::const_iterator?it
????=?Set.find(word);
??cout?<<?word?<<?":?"
???????<<?(it?!=?Set.end()???"present"?:?"not?present")
???????<<?endl;
}
int?main()
{
??hash_set<const?char*,?hash<const?char*>,?eqstr>?Set;
??Set.insert("kiwi");
??Set.insert("plum");
??Set.insert("apple");
??Set.insert("mango");
??Set.insert("apricot");
??Set.insert("banana");
??lookup(Set,?"mango");
??lookup(Set,?"apple");
??lookup(Set,?"durian");
}
?
?
(2) hash_map容器
容器聲明: hash_map<Key, Data, HashFcn, EqualKey, Alloc>
簡單的示例程序:
#include?<ext/hash_map>???????????????????????????????????????????#include?<iostream>
?#include?<cstring>
?using?namespace?std;
?using?namespace?__gnu_cxx;
?struct?eqstr
?{
?????bool?operator()(const?char*?s1,?const?char*?s2)?const
?????{
?????????return?strcmp(s1,?s2)?==?0;
?????}
?};
??int?main()
?{
?????hash_map<const?char*,?int,?hash<const?char*>,?eqstr>?months;
?
?????months["january"]?=?31;
?????months["february"]?=?28;
?????months["march"]?=?31;
?????months["april"]?=?30;
?????months["may"]?=?31;
?????months["june"]?=?30;
?????months["july"]?=?31;
?????months["august"]?=?31;
?????months["september"]?=?30;
?????months["october"]?=?31;
?????months["november"]?=?30;
?????months["december"]?=?31;
?
?????cout?<<?"september?->?"?<<?months["september"]?<<?endl;
?????cout?<<?"april?????->?"?<<?months["april"]?<<?endl;
?????cout?<<?"june??????->?"?<<?months["june"]?<<?endl;
?????cout?<<?"november??->?"?<<?months["november"]?<<?endl;
?}
?
(3) hash_multiset與hash_multimap
其他的hash_multiset和hash_multimap與上面的兩個類似.
(4) hash函數
這里所提供的hash函數是用函數對象的方式提供的: hash<T>
注意,這里的實例化類型是有限制的,即T只能采用下面的類型:
char*,const char*, char,signed char,unsigned char,short,unsigned short,int,unsigned int,long,unsigned long
下面的一個例子是對char*型字符串進行hash:
#include?<iostream>#include?<ext/hash_set>???????????????????????????????
#include?<cstdlib>
#include?<string>
using?namespace?std;
using?namespace?__gnu_cxx;
int?main(){
????const?string?alpha="abcdefghijklmnopqrstuvwxyz";
????const?int?N=10;
????string?s(N,'?');
????hash<const?char*>?H;
????for(int?i=0;i<30;i++){
????????for(int?j=0;j<N;j++){
????????????s[j]=alpha[rand()%26];
????????}
????????cout<<s<<"?->?"<<H(s.c_str())<<endl;
????}
}
TR1(C++11)中提供的與hash相關的數據結構
因為新的C++標注還沒有普及開來, 所以我們現在是采用TR1庫.
1. 散列集合簡介:
成員類型如下:
成員函數如下:
?
2. 散列集合用法:
unordered_set的一個示例程序如下:
#include?<iostream>?#include?<tr1/unordered_set>
?#include?<string>
?using?namespace?std;
?using?namespace?std::tr1;
?
?int?main?()
?{
????const?char*?arr[]?=?{"Mercury","Venus","Earth","Mars","Jupiter","?Saturn","Uranus","Neptune"};
????int?sz=sizeof(arr)/sizeof(char*);
?unordered_set<string>?myset(arr,arr+sz);
?????unsigned?n?=?myset.bucket_count();
?
?????cout?<<?"myset?has?"?<<?n?<<?"?buckets.\n";
?????unordered_set<string>::const_iterator?it;
?????for(it=myset.begin();it!=myset.end();++it)
?????????cout<<*it<<'?';
?????cout<<endl;
?
?????for?(unsigned?i=0;?i<n;?++i)?{
?????????cout?<<?"bucket?#"?<<?i?<<?"?contains:";
?????????unordered_set<string>::const_local_iterator?it;
?????????for?(it=myset.begin(i);?it!=myset.end(i);?++it)
?????????????std::cout?<<?"?"?<<?*it;????????????????????????????????
?????????cout<<endl;
?????}
????
?????return?0;
?}
?
程序輸出:
從這兒可以知道, unordered_set中可以對其內部結構bucket進行操作. 并且begin(i)的返回值是一個const_local_iterator
?
3. 散列映射簡介
成員類型如下:
成員函數如下:
4. 散列映射的用法
下面是一個簡單的例子:
#include?<iostream>#include?<string>
#include?<tr1/unordered_map>
using?namespace?std;
using?namespace?std::tr1;
int?main?()
{
????const?char*?mm[][2]={
????????????{"house","maison"},
????????????{"apple","pomme"},
????????????{"tree","arbre"},
????????????{"book","livre"},
????????????{"door","porte"},
????????????{"grapefruit","pamplemousse"}
????};
????int?sz=6;
??unordered_map<string,string>?mymap;
??for(int?i=0;i<sz;i++)
??????mymap.insert(unordered_map<string,string>::value_type(mm[i][0],mm[i][1]));
??unsigned?n?=?mymap.bucket_count();
??cout?<<?"mymap?has?"?<<?n?<<?"?buckets.\n";
??unordered_map<string,string>::const_iterator?it;
??for(it=mymap.begin();it!=mymap.end();++it)
??????cout?<<?"["?<<?it->first?<<?":"?<<?it->second?<<?"]?";
??cout<<endl;
??for?(unsigned?i=0;?i<n;?++i)?{
????cout?<<?"bucket?#"?<<?i?<<?"?contains:?";
??unordered_map<string,string>::const_local_iterator?it;
????for?(it?=?mymap.begin(i);?it!=mymap.end(i);?++it)
??????cout?<<?"["?<<?it->first?<<?":"?<<?it->second?<<?"]?";
????cout?<<?"\n";
??}
??return?0;
}
?
輸出如下:
這兒需要注意是如何進行元素的插入的.
5. TR1中提供了一個函數對象hash, 可以進行映射.
其定義如下:
template<typename?_Tp>????struct?hash?:?public?std::unary_function<_Tp,?size_t>
????{
??????size_t
??????operator()(_Tp?__val)?const;
????};
??///?Partial?specializations?for?pointer?types.
??template<typename?_Tp>
????struct?hash<_Tp*>?:?public?std::unary_function<_Tp*,?size_t>
????{
??????size_t
??????operator()(_Tp*?__p)?const
??????{?return?reinterpret_cast<size_t>(__p);?}
????};
??///?Explicit?specializations?for?integer?types.
#define?_TR1_hashtable_define_trivial_hash(_Tp)?????\
??template<>????????????????????????\
????inline?size_t????????????????????\
????hash<_Tp>::operator()(_Tp?__val)?const????????\
????{?return?static_cast<size_t>(__val);?}
??_TR1_hashtable_define_trivial_hash(bool);
??_TR1_hashtable_define_trivial_hash(char);
??_TR1_hashtable_define_trivial_hash(signed?char);
??_TR1_hashtable_define_trivial_hash(unsigned?char);
??_TR1_hashtable_define_trivial_hash(wchar_t);
??_TR1_hashtable_define_trivial_hash(short);
??_TR1_hashtable_define_trivial_hash(int);
??_TR1_hashtable_define_trivial_hash(long);
??_TR1_hashtable_define_trivial_hash(long?long);
??_TR1_hashtable_define_trivial_hash(unsigned?short);
??_TR1_hashtable_define_trivial_hash(unsigned?int);
??_TR1_hashtable_define_trivial_hash(unsigned?long);
??_TR1_hashtable_define_trivial_hash(unsigned?long?long);
#undef?_TR1_hashtable_define_trivial_hash
??//?Fowler?/?Noll?/?Vo?(FNV)?Hash?(type?FNV-1a)
??//?(Used?by?the?next?specializations?of?std::tr1::hash.)
??///?Dummy?generic?implementation?(for?sizeof(size_t)?!=?4,?8).
??template<size_t>
????struct?_Fnv_hash_base
????{
??????template<typename?_Tp>
????????static?size_t
????????hash(const?_Tp*?__ptr,?size_t?__clength)
????????{
??????size_t?__result?=?0;
??????const?char*?__cptr?=?reinterpret_cast<const?char*>(__ptr);
??????for?(;?__clength;?--__clength)
????????__result?=?(__result?*?131)?+?*__cptr++;
??????return?__result;
????}
????};
??template<>
????struct?_Fnv_hash_base<4>
????{
??????template<typename?_Tp>
????????static?size_t
????????hash(const?_Tp*?__ptr,?size_t?__clength)
????????{
??????size_t?__result?=?static_cast<size_t>(2166136261UL);
??????const?char*?__cptr?=?reinterpret_cast<const?char*>(__ptr);
??????for?(;?__clength;?--__clength)
????????{
??????????__result?^=?static_cast<size_t>(*__cptr++);
??????????__result?*=?static_cast<size_t>(16777619UL);
????????}
??????return?__result;
????}
????};
??
??template<>
????struct?_Fnv_hash_base<8>
????{
??????template<typename?_Tp>
????????static?size_t
????????hash(const?_Tp*?__ptr,?size_t?__clength)
????????{
??????size_t?__result
????????=?static_cast<size_t>(14695981039346656037ULL);
??????const?char*?__cptr?=?reinterpret_cast<const?char*>(__ptr);
??????for?(;?__clength;?--__clength)
????????{
??????????__result?^=?static_cast<size_t>(*__cptr++);
??????????__result?*=?static_cast<size_t>(1099511628211ULL);
????????}
??????return?__result;
????}
????};
??struct?_Fnv_hash
??:?public?_Fnv_hash_base<sizeof(size_t)>
??{
????using?_Fnv_hash_base<sizeof(size_t)>::hash;
????template<typename?_Tp>
??????static?size_t
??????hash(const?_Tp&?__val)
??????{?return?hash(&__val,?sizeof(__val));?}
??};
??///?Explicit?specializations?for?float.
??template<>
????inline?size_t
????hash<float>::operator()(float?__val)?const
????{
??????//?0?and?-0?both?hash?to?zero.
??????return?__val?!=?0.0f???std::tr1::_Fnv_hash::hash(__val)?:?0;
????}
??///?Explicit?specializations?for?double.
??template<>
????inline?size_t
????hash<double>::operator()(double?__val)?const
????{
??????//?0?and?-0?both?hash?to?zero.
??????return?__val?!=?0.0???std::tr1::_Fnv_hash::hash(__val)?:?0;
????}
?
從上面的類可以看到類hash是一個模板函數,并且進行了模板特化.
對于整型(包括char, short, int和long等)是直接使用其自己的值(使用了static_cast強制轉換), 對于浮點型(float, double等)是進行了一些變化得到映射值.
?
6. 如何支持自定義類型
一個自定義的例子:
#include?<iostream>#include?<tr1/unordered_set>
#include?<string>
#include?<cstdlib>
using?namespace?std;
using?namespace?std::tr1;
struct?demo{
????int?a;
????demo(int?i):a(i){}
};
bool?operator==(const?demo&?lhs,const?demo&?rhs){
????return?lhs.a==rhs.a;
}
ostream&?operator<<(ostream&?os,const?demo&?s){
????os<<"<"<<s.a<<">";
}
size_t?hash_value(demo&?s){
????return?hash<int>()(s.a);
}
namespace?std{
????namespace?tr1{
????????template<>
????????????struct?hash<demo>{
????????????????size_t?operator()(const?demo&s)const{
????????????????????return?hash<int>()(s.a);
????????????????}
????????????};
????}
}
int?main(){
????int?a[]={3,7,5,-3,-4};
????int?sz=sizeof(a)/sizeof(int);
????cout<<sz<<endl;
????unordered_set<demo,hash<demo>?>?us(a,a+sz);
????unordered_set<demo,hash<demo>?>::const_iterator?it;
????for(it=us.begin();it!=us.end();++it)
????????cout<<*it<<'?';
????cout<<endl;
????unsigned?n?=?us.bucket_count();
????for?(unsigned?i=0;?i<n;?++i)?{
????????cout?<<?"bucket?#"?<<?i?<<?"?contains:";
????????unordered_set<demo,hash<demo>?>::const_local_iterator?it;
????????for?(it=us.begin(i);?it!=us.end(i);?++it)
????????????cout?<<?"?"?<<?*it;
????????cout<<endl;
????}
????cout<<"======================================"<<endl;
????us.clear();
????int?N=20;
????for(int?i=0;i<N;i++){
????????int?val=rand()%1000;
????????us.insert(val);
????}
????for(it=us.begin();it!=us.end();++it)
????????cout<<*it<<'?';
????cout<<endl;
????n?=?us.bucket_count();
????for?(unsigned?i=0;?i<n;?++i)?{
????????cout?<<?"bucket?#"?<<?i?<<?"?contains:";
????????unordered_set<demo,hash<demo>?>::const_local_iterator?it;
????????for?(it=us.begin(i);?it!=us.end(i);?++it)
????????????cout?<<?"?"?<<?*it;
????????cout<<endl;
????}
????
}
?
輸出結果如下:
6. unordered庫的內部結構
轉載于:https://www.cnblogs.com/xkfz007/archive/2012/08/25/2656898.html
總結
以上是生活随笔為你收集整理的C++中的Hash容器总结的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 常见动态内存的管理程序错误
- 下一篇: discuz论坛整合ucenter免激活