hash函数应用(整理)

2023-10-30

评估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:

  1. /* A Simple Hash Function */  
  2. unsigned int simple_hash(char *str)  
  3. {  
  4.     register unsigned int hash;  
  5.     register unsigned char *p;  
  6.   
  7.     for(hash = 0, p = (unsigned char *)str; *p ; p++)  
  8.         hash = 31 * hash + *p;  
  9.   
  10.     return (hash & 0x7FFFFFFF);  
  11. }  

 

//平时写小程序的时候&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;
}

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

hash函数应用(整理) 的相关文章

随机推荐

  • Web开发必备的 9 个开源项目

    1 AdminLTE Github Star 数 24969 Github 地址 https github com almasaeed2010 AdminLTE 2 vue Element Admin Github Star 数 19546
  • LinearAlgebraMIT_9_LinearIndependence/SpanningASpace/Basis/Dimension

    这节课我们主要学习一下 Linear Independence 线性无关 spanning a space 生成空间 basis 基和 dimension 维度 同时我们要注意这四个很重要的基本概念的描述对象 我们会说向量组线性无关 由一个
  • WIN10操作系统下如何修改MySQL5.7数据库的ROOT用户密码(亲测有效)

    由于工程项目需要 需要修改MySQL5 7数据库的ROOT用户密码 但是发现网上很多方法都是针对MySQL5 6数据库系统的 并不适合MySQL5 7数据库 一 查看MySQL5 7数据库的服务名 可以观察到 MySQL5 7数据库的服务名
  • 黑马程序员jvm笔记(四)--字节码部分心得

    10 字节码部分 这部分 主要介绍了类的字节码文件 以及从更深层的角度去理解类是怎么被加载的 jvm的内存结构 10 1 字节码文件的分析 1 获得字节码文件 得到如下的字节码文件 0000000 ca fe ba be 00 00 00
  • MySQL(十)—线上MySQL锁超时了怎么办?update操作怎么上了个表锁啊?

    文章目录 一 异常错误 二 尽量还原这个错误 1 准备数据 2 阐述业务 3 分析原因 三 线上如何解决这个异常呢 1 设置锁超时时间 2 使用online ddl方式建立唯一索引 3 动态增加服务节点 一 异常错误 先上一个出现异常的截图
  • STC89C51——定时器/计数器介绍及程序配置

    前言 本文介绍基于常见的51单片机 即如下图的芯片 AT89C51具备2个定时器 计数器 即定时器 计数器 0 定时器 计数器 1 简称 T0 T1 T0 有 4 种工作方式 T1 有 3 种工作方式 2个定时器前3种工作方式一样 但是在T
  • IS-IS协议 HCIP

    我需要 最狂的风 和最静的海 HCIP IS IS协议基本原理 场景应用 历史起源 路由计算过程 地址结构 路由器分类 邻居HELLO报文 邻居关系建立 DIS及DIS与DR的类比 链路状态信息的载体 链路状态信息的交互 路由算法 网络分层
  • IO和NIO的区别

    在这里不再过多描述IO的具体API用法 总的来说reader writer是处理字符的 而inputsream 和 outputstream是处理字节的 eg 图片什么的 其实现在大多Web应用上传图片时候也不会使用字节流而是上传一个图片存
  • elasticsearch中节点都启动但是无法形成集群问题

    近日 单台机器 8个节点的es集群 8个节点都正常started了 但是就是无法形成集群 后来看日志 日志中出现一堆的MasterNotDiscoveredException这种异常 完整日志如下 2016 04 27 15 08 22 4
  • 关于STM32软件IIC与PCF8563通信 逻辑分析仪0xA2 Missing Ack /NAK排查与解决

    最近在使用PCF8563时 准备用STM32 软件IIC通信时 改了软件IIC后 将所有函数都做了适配 但是 发现PCF正常初始化 程序无法运行 链接上逻辑分析仪后发现是一直收不到ACK 发送的A2 地址和0x08都正常 程序正常时先设置时
  • 暴力解决八皇后问题

    如题 翻到了一个以前的代码 发现是刚学c时帮同学解决八皇后问题的代码文件 足足两百行 放出来纪念一下当初傻傻的自己吧哈哈 话不多说 放代码 include
  • arm linux kernel编译问题总结

    1 make menuconfig报错 guang guang kylin Develop linux stable make menuconfig HOSTCC scripts basic fixdep Unable to find th
  • 【记录】服务器搬家记录

    服务器搬家记录 前言 零 备份数据 程序 一 备份mysql 1 先删掉无用的表和库 减小数据包大小 2 备份到本地 二 备份docker 1 提交 2 标签 3 push 4 保存挂载宿主机上的文件 三 备份定时脚本 四 开发环境 服务器
  • 完美解决:由于找不到mfc140u.dll,无法继续执行代码。

    什么是msvcp140u dll msvcp140 dll是Microsoft Visual C Redistributable的一个组件 它包含了许多用于C 编程的函数和类 如果你的系统缺少了这个文件 那么你可能会遇到 找不到msvcp1
  • js 用变量做对象key值的写法

    变量为name 对象为obj var name name var obj obj key var name jack var obj name 1 name 2 name aaa 3 console log obj name 1 jack
  • Dart中List的常用方法概述及使用案例

    在Dart中 List是一种有序的集合 它提供了许多有用的方法来操作列表数据 Flutter使用Dart语言开发 所以在Flutter中依然适用 下面是List常用的方法概述及使用案例 length属性 List的length属性返回Lis
  • pandas的时间对象

    pandas时间处理对象 pandas中有个时间库 datautil 可以使用其中的方法把多种字符串时间格式转化为时间对象 import dateutil import pandas as pd a dateutil parser pars
  • git还原到某个版本

    1 tortoisegit还原 v2还原到v1 1 1 强制还原 git reset 如果使用这种方式还原到v1 将丢失还原到v1到v2之间的所有提交及日志 1 1 1 显示日志 有save1 save2两条提交记录 1 1 2 重置版本
  • Git的理解与使用

    文章目录 一 初识Git 1 1 分布式管理系统 1 2 Git的安装与配置 二 Git理论 2 1 四个工作区域 2 2 提交代码的简易流程 2 3 Git所管理文件的四种状态 三 Git命令 3 1 基础命令 git init git
  • hash函数应用(整理)

    评估hash函数优劣的基准主要有以下两个指标 1 散列分布性 即桶的使用率backet usage 已使用桶数 总的桶数 这个比例越高 说明分布性良好 是好的hash设计 2 平均桶长 即avg backet len 所有已使用桶的平均长度