赞
踩
目录
(3) hash_multiset与hash_multimap
3.2 TR1(C++11)中提供的与hash相关的数据结构
(5) TR1中提供了一个函数对象hash, 可以进行映射.
散列表 Hash Table由散列函数所产生的一种数据结构,是一种非常重要的数据结构. 散列函数(或称 Hash函数)是一种特殊的映射函数。
散列表(哈希表、hash表)是用于存储动态集的一种非常有效的数据结构。通过散列函数h(key)的计算结果,直接得到key关键字表示的元素的存储位置。散列技术中,可能会有两个不同的key1和key2,通过h(key)计算得到的地址如果是一样的,就发生了冲突。散列技术中 哈希函数h(key)的设计 和 哈希冲突如何解决是hash中最关键的两个问题。
散列函数要求(1)能快速的计算;(2) 计算结果分布要均匀。(hash function 设计的一条主要原则是:尽量让不同的key算出的结果不同。)
散列函数有如下几种常用的:(1) 除留余数法;(2) 平方取中法;(3) 折叠法;(4) 数字分析法。
最直观的一种,上图使用的就是这种散列法,公式:
index = value % 16
学过汇编的都知道,求模数其实是通过一个除法运算得到的,所以叫"除法散列法"。
求index是非常频繁的操作,而乘法的运算要比除法来得省时(对现在的CPU来说,估计我们感觉不出来),所以我们考虑把除法换成乘法和一个位移操作。公式:
index = (value * value) >> 28
如果数值分配比较均匀的话这种方法能得到不错的结果,但还有个问题,value如果很大,value * value会溢出的,但我们这个乘法不关心溢出,因为我们根本不是为了获取相乘结果,而是为了获取index。
平方散列法的缺点是显而易见的,所以我们能不能找出一个理想的乘数,而不是拿value本身当作乘数呢?答案是肯定的。
(1),对于16位整数而言,这个乘数是40503
(2),对于32位整数而言,这个乘数是2654435769
(3),对于64位整数而言,这个乘数是11400714819323198485
这几个"理想乘数"是如何得出来的呢?这跟一个法则有关,叫黄金分割法则,而描述黄金分割法则的最经典表达式无疑就是著名的斐波那契数列.
对我们常见的32位整数而言,公式:
index = (value * 2654435769) >> 28
解决冲突的方法有:
拉链法是通过在各地址构建一个链表来解决多元素同地址的问题。
开地址法根据探查序列的不同,又分为(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中加入。
(C++ TR1: 全称Technical Report 1,是针对C++标准库的第一次扩展。即将到来的下一个版本的C++标准c++0x会包括它,以及一些语言本身的扩充。tr1包括大家期待已久的smart pointer,正则表达式以及其他一些支持范型编程的内容。草案阶段,新增的类和模板的名字空间是std::tr1)
其实在hash相关的容器进入TR1之前, C++编译器的其他厂商就已经实现了自己的hash容器了. 一般这些容器的命名都是hash_set 或hash_map, 这其中最著名的就是SGI 的STL中所实现的hash容器. 其实这也导致了以后C++标准中对hash容器命名的变化. 为了与之前的名称区别开来, C++标准委员会将标准中的容器命名为unordered_set和unordered_map.
C++还有一个比较著名库是boost库, 其实C++标准中的hash容器都是从boost中转化来的.
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>一样。
容器声明: 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");
- }
容器声明: 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;
- }
其他的hash_multiset和hash_multimap与上面的两个类似.
这里所提供的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;
- }
- }
因为新的C++标注还没有普及开来, 所以我们现在是采用TR1库.
成员类型如下:
成员函数如下:
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
成员类型如下:
成员函数如下:
下面是一个简单的例子:
-
- #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;
- }
-
-
-
-
输出如下:
这儿需要注意是如何进行元素的插入的.
其定义如下:
- 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等)是进行了一些变化得到映射值.
一个自定义的例子:
- #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;
- }
-
- }
输出结果如下:
参考 : C++中的Hash容器总结
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。