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位整数而言,公式:
解决冲突的方法有:(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没有什么大的区别,
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 <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 <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 <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 <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 <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, 可以进行映射.
其定义如下:
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 <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库的内部结构