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库的内部结构

posted @ 2012-08-25 20:37  Mr.Rico  阅读(5555)  评论(0编辑  收藏  举报