hash函数应用(整理)

评估hash函数优劣的基准主要有以下两个指标:

(1) 散列分布性

即桶的使用率backet_usage = (已使用桶数) / (总的桶数),这个比例越高,说明分布性良好,是好的hash设计。

(2) 平均桶长

即avg_backet_len,所有已使用桶的平均长度。理想状态下这个值应该=1,越小说明冲突发生地越少,是好的hash设计。

hash函数计算一般都非常简洁,因此在耗费计算时间复杂性方面判别甚微,这里不作对比。

 f(x) x的变化引起雪崩反应     分布均匀  桶利用率高  

 

择。当然,最好实际测试一下,毕竟应用特点不大相同。其他几组测试结果也类似,这里不再给出。

Hash函数    桶数    Hash调用总数    最大桶长    平均桶长    桶使用率%
simple_hash    10240    47198    16    4.63    99.00%
RS_hash    10240    47198    16    4.63    98.91%
JS_hash    10240    47198    15    4.64    98.87%
PJW_hash    10240    47198    16    4.63    99.00%
ELF_hash    10240    47198    16    4.63    99.00%
BKDR_hash    10240    47198    16    4.63    99.00%
SDBM_hash    10240    47198    16    4.63    98.90%
DJB_hash    10240    47198    15    4.64    98.85%
AP_hash    10240    47198    16    4.63    98.96%
CRC_hash    10240    47198    16    4.64    98.77%
 

字符串求hash:

/* A Simple Hash Function */  
unsigned int simple_hash(char *str)  
{  
    register unsigned int hash;  
    register unsigned char *p;  
  
    for(hash = 0, p = (unsigned char *)str; *p ; p++)  
        hash = 31 * hash + *p;  
  
    return (hash & 0x7FFFFFFF);  
}  
 

//平时写小程序的时候&0xFFFFFFF就行了,这个可以控制在3位数,用来平时写小程序

  如:"fsfdsfdfdfdqqqqqqqqqqqqqqqqqsssssssssssssssssssssssssssssssssssssssssssssssssssssssqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqd",

"fsdfdsf",

"sssss",

"sseeeeeeeeeeee",

"sssee"

 

对应的哈希值是 111  218  104 248 104

数字求哈希:

static inline u32 hash_32(u32 val, unsigned int bits){

/* On some cpus multiply is faster, on others gcc will do shifts */

 u32 hash = val * GOLDEN_RATIO_PRIME_32; //   ox9e370001UL

/* High bits are more random, so use them. */

return hash >> (32 - bits);

}

 

 //  BKDR Hash Function
 unsigned  int  BKDRHash( char   * str)
 {
        unsigned  int  seed  =   131 ;  //  31 131 1313 13131 131313 etc..
         unsigned  int  hash  =   0 ;

         while  ( * str)
         {
                hash  =  hash  *  seed  +  ( * str ++ );
        }

         return  (hash  &   0x7FFFFFFF );
}

 

 

 

其中hash_long在<linux/hash.h>中定义如下:

/* 2^31 + 2^29 - 2^25 + 2^22 - 2^19 - 2^16 + 1 */
#define GOLDEN_RATIO_PRIME_32 0x9e370001UL
/*  2^63 + 2^61 - 2^57 + 2^54 - 2^51 - 2^18 + 1 */
#define GOLDEN_RATIO_PRIME_64 0x9e37fffffffc0001UL
 
#if BITS_PER_LONG == 32
#define GOLDEN_RATIO_PRIME GOLDEN_RATIO_PRIME_32
#define hash_long(val, bits) hash_32(val, bits)
#elif BITS_PER_LONG == 64
#define hash_long(val, bits) hash_64(val, bits)
#define GOLDEN_RATIO_PRIME GOLDEN_RATIO_PRIME_64
#else
#error Wordsize not 32 or 64
#endif
 
static inline u64 hash_64(u64 val, unsigned int bits)
{
    u64 hash = val;
 
    /*  Sigh, gcc can't optimise this alone like it does for 32 bits. */
    u64 n = hash;
    n <<= 18;
    hash -= n;
    n <<= 33;
    hash -= n;
    n <<= 3;
    hash += n;
    n <<= 3;
    hash -= n;
    n <<= 4;
    hash += n;
    n <<= 2;
    hash += n;
 
    /* High bits are more random, so use them. */
    return hash >> (64 - bits);
}
 
static inline u32 hash_32(u32 val, unsigned int bits)
{
    /* On some cpus multiply is faster, on others gcc will do shifts */
    u32 hash = val * GOLDEN_RATIO_PRIME_32;
 
    /* High bits are more random, so use them. */
    return hash >> (32 - bits);
}
 
static inline unsigned long hash_ptr(const void *ptr, unsigned int bits)
{
    return hash_long((unsigned long)ptr, bits);
}
#endif /* _LINUX_HASH_H */

上面的函数很有趣,我们来仔细看一下。

首先,hash的方式是,让key乘以一个大数,于是结果溢出,就把留在32/64位变量中的值作为hash值,又由于散列表的索引长度有限,我们就取这hash值的高几为作为索引值,之所以取高几位,是因为高位的数更具有随机性,能够减少所谓“冲突”。什么是冲突呢?从上面的算法来看,key和hash值并不是一一对应的。有可能两个key算出来得到同一个hash值,这就称为“冲突”。

那么,乘以的这个大数应该是多少呢?从上面的代码来看,32位系统中这个数是0x9e370001UL,64位系统中这个数是0x9e37fffffffc0001UL。这个数是怎么得到的呢?

“Knuth建议,要得到满意的结果,对于32位机器,2^32做黄金分割,这个大树是最接近黄金分割点的素数,0x9e370001UL就是接近 2^32*(sqrt(5)-1)/2 的一个素数,且这个数可以很方便地通过加运算和位移运算得到,因为它等于2^31 + 2^29 - 2^25 + 2^22 - 2^19 - 2^16 + 1。对于64位系统,这个数是0x9e37fffffffc0001UL,同样有2^63 + 2^61 - 2^57 + 2^54 - 2^51 - 2^18 + 1。”

从程序中可以看到,对于32位系统计算hash值是直接用的乘法,因为gcc在编译时会自动优化算法。而对于64位系统,gcc似乎没有类似的优化,所以用的是位移运算和加运算来计算。首先n=hash, 然后n左移18位,hash-=n,这样hash = hash * (1 - 2^18),下一项是-2^51,而n之前已经左移过18位了,所以只需要再左移33位,于是有n <<= 33,依次类推,最终算出了hash值。
 

 


处理冲突:
  开放地址法:hi=(h(key)+i)%m  i<=m-1    di=i;
  用线性探测法处理冲突,思路清晰,算法简单,但存在下列缺点:
① 处理溢出需另编程序。一般可另外设立一个溢出表,专门用来存放上述哈希表中放不下的记录。此溢出表最简单的结构是顺序表,查找方法可用顺序查找。
② 按上述算法建立起来的哈希表,删除工作非常困难。假如要从哈希表 HT 中删除一个记录,按理应将这个记录所在位置置为空,但我们不能这样做,而只能标上已被删除的标记,否则,将会影响以后的查找。
③ 线性探测法很容易产生堆聚现象。所谓堆聚现象,就是存入哈希表的记录在表中连成一片。按照线性探测法处理冲突,如果生成哈希地址的连续序列愈长 ( 即不同关键字值的哈希地址相邻在一起愈长 ) ,则当新的记录加入该表时,与这个序列发生冲突的可能性愈大。因此,哈希地址的较长连续序列比较短连续序列生长得快,这就意味着,一旦出现堆聚 ( 伴随着冲突 ) ,就将引起进一步的堆聚。


(2)拉链法的优点
与开放定址法相比,拉链法有如下几个优点:
①拉链法处理冲突简单,且无堆积现象,即非同义词决不会发生冲突,因此平均查找长度较短;
②由于拉链法中各链表上的结点空间是动态申请的,故它更适合于造表前无法确定表长的情况;
③开放定址法为减少冲突,要求装填因子α较小,故当结点规模较大时会浪费很多空间。而拉链法中可取α≥1,且结点较大时,拉链法中增加的指针域可忽略不计,因此节省空间;
④在用拉链法构造的散列表中,删除结点的操作易于实现。只要简单地删去链表上相应的结点即可。而对开放地址法构造的散列表,删除结点不能简单地将被删结 点的空间置为空,否则将截断在它之后填人散列表的同义词结点的查找路径。这是因为各种开放地址法中,空地址单元(即开放地址)都是查找失败的条件。因此在 用开放地址法处理冲突的散列表上执行删除操作,只能在被删结点上做删除标记,而不能真正删除结点。

(3)拉链法的缺点
     拉链法的缺点是:指针需要额外的空间,故当结点规模较小时,开放定址法较为节省空间,而若将节省的指针空间用来扩大散列表的规模,可使装填因子变小,这又减少了开放定址法中的冲突,从而提高平均查找速度。

 

C++中的hashMap


[转]http://biancheng.dnbcw.info/c/170128.html

标准std中只有map,是使用平衡二叉树实现的,查找和添加的复杂度都为O(log(n)), 
没有提供hash map,gnu c++提供了hash_map,是一个hash map的实现,查找和添加复杂 
度均为O(1)。 利用空间来换时间     可以参考:http://www.cnblogs.com/luxiaoxun/archive/2012/09/02/2667782.html
#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;
    cout << "march -> " << months["march"] << endl;
}

不过gnu hash_map和c++ stl的api不兼容,c++ tr1(C++ Technical Report 
1)作为标准的扩展,实现了hash map,提供了和stl兼容一致的api,称为unorder_map.在头文件 
<tr1/unordered_map>中。另外c++ tr1还提供了正则表达式、智能指针、hash table、
随机数生成器的功能。 
#include <iostream>
#include <string>
#include <tr1/unordered_map>
using namespace std;

int main(){
    typedef std::tr1::unordered_map<int,string> hash_map;
    hash_map hm;
    hm.insert(std::pair<int,std::string>(0,"Hello"));
    hm[1] = "World";
    for(hash_map::const_iterator it = hm.begin(); it != hm.end(); ++it){
        cout << it->first << "-> " << it->second << endl;
    }
    return 0;
}
 

 与C++primer(4版)中的map用法相同!!!不过这个速度快一点!C:\MinGW\lib\gcc\mingw32\4.6.2\include\c++\tr1

#include <iostream>
#include <string>
#include <tr1/unordered_map>
using namespace std;

int main(){
 typedef std::tr1::unordered_map<string,int> hash_map;
 hash_map hm;
 hm.insert(make_pair("Hello",1));
 hm.insert(std::pair<std::string,int>("Hello2",1));
 hm.insert(hash_map::value_type("Hello2",1));//已经存在了就不会代替
 pair<hash_map::iterator,bool> ret=hm.insert(hash_map::value_type("Hello2",1));//要改就用这种方式改!!!
 if(!ret.second)  ++ret.first->second;
 hm["world"] =1;
 ++hm["world"];
 for(hash_map::const_iterator it = hm.begin(); it != hm.end(); ++it){
  cout << it->first << "-> " << it->second << endl;
 }
 return 0;
}


--------------------- 
作者:tanglanting12 
来源:CSDN 
原文:https://blog.csdn.net/szu_tanglanting/article/details/12406605 
版权声明:本文为博主原创文章,转载请附上博文链接!

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值