散列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容器总结相关推荐

  1. 【小白学习C++ 教程】二十二、C++ 中的STL容器stack、queue和map

    @Author:Runsen STL 中的栈容器是一种容器适配器.在栈容器中,元素在一端插入并在同一端删除. stack 为了实现堆栈容器,我们需要在我们的程序中包含头文件<stack>. ...

  2. 利用NVIDIA-NGC中的MATLAB容器加速语义分割

    利用NVIDIA-NGC中的MATLAB容器加速语义分割 Speeding Up Semantic Segmentation Using MATLAB Container from NVIDIA NG ...

  3. 全网把Map中的hash()分析的最透彻的文章,别无二家。

    你知道HashMap中hash方法的具体实现吗?你知道HashTable.ConcurrentHashMap中hash方法的实现以及原因吗?你知道为什么要这么实现吗?你知道为什么JDK 7和JDK 8 ...

  4. 聊聊 SpringCloud 中的父子容器

    点击上方"方志朋",选择"置顶或者星标" 你的关注意义重大! 来源公号:吉姆餐厅ak 概述 在引入 SpringCloud 的项目中会多次创建 Spring 容 ...

  5. Pytorch中的序列化容器-度消失和梯度爆炸-nn.Sequential-nn.BatchNorm1d-nn.Dropout

    Pytorch中的序列化容器-度消失和梯度爆炸-nn.Sequential-nn.BatchNorm1d-nn.Dropout 1. 梯度消失和梯度爆炸 在使用pytorch中的序列化 容器之前,我们 ...

  6. HashMap中的hash函数

    在写一个HashSet时候有个需求,是判断HashSet中是否已经存在对象,存在则取出,不存在则add添加.HashSet也是通过HashMap实现,只用了HashMap的key,value都存储一个 ...

  7. libevent中的hash表

    libevent中的hash表的代码在ht-internal文件中,在添加io事件,signal事件时,底层是在操作  event_io_map和event_signal_map 1. hash的 ...

  8. STL中的set容器的一点总结

    1.关于set C++ STL 之所以得到广泛的赞誉,也被很多人使用,不只是提供了像vector, string, list等方便的容器,更重要的是STL封装了许多复杂的数据结构算法和大量常用数据结构 ...

  9. 不要在递归中使用静态容器

    不要在递归中使用静态容器 这个很容易出BUG,且很难查.由于递归层次很深,容器可能反复的增加删除元素,不同层级之间的清空与增加极易造成难以察觉的冲突. 这个问题从10年前读书时写算法到现在已经遇到过不 ...

最新文章

  1. 国内外财务软件科目结构的比较
  2. JS魔法堂:IE5~9的DragDrop API
  3. linux下防火墙加白名单
  4. seaJS简介和完整实例
  5. python __init__ 构造函数
  6. FreeSql (三十二)Aop
  7. kali root默认密码_Kali Linux root 默认密码是:toor
  8. [转载] Python图结构(复杂网络)可视化模块——networkx
  9. hdu 1969 Pie(二分查找)
  10. oracle 登录不了的解决
  11. 百度网盘客户端使用的一些小窍门
  12. 阿里云虚拟主机内存占用大问题解决
  13. 《大数据原理与应用》林子雨:一. 大数据基础
  14. php获取肖前时间,马克思主义哲学原理(上下册)
  15. 可由线性表示且表达式唯一_一个向量能由另一个向量组线性表示,且表示式唯一的等价条件是什么?...
  16. Autoware.Auto
  17. 懒人福利!传沃尔玛计划将超市购物车变为智能机器人
  18. mysql 设置双1_2020-10-15:mysql的双1设置是什么?
  19. python初中数学建模培训_初中生数学建模培养
  20. 全球机场三字代码查询工具

热门文章

  1. MySQL 8.0 新特性之统计直方图
  2. rsync的配置应用
  3. C++如何实现DNS域名解析转
  4. [svn] linux 下svn服务器的搭建
  5. pongo - 字符串消除
  6. E431 笔记本电池问题 0190 Critical low-battery error 解决办法
  7. 修改表格字体颜色_CAD表格文字样式失灵?不,你错了
  8. Android中ActivityManagerService与应用程序(客户端)通信模型分析
  9. hadoop MapReduce实例解析
  10. hadoop hive hbase 集群搭建