系统学习hash算法(哈希算法)

转载请说明出处。

前言:

关于本文《系统学习hash算法》的由来。在看到了《十一、从头到尾彻底解析Hash 表算法》这篇文章之后,原文中没有暴雪hash快的原因分析以及和别的hash方法比能快多少,结合自己以前研究MonetDB的数据库时也涉及到了hash

join的相关内容,于是决定实现一个简单的hash和暴雪hash比较下,但在搜索资料时发现了《

一,基础知识的梳理总结。

在准备实现一个hash算法时需要思考一下三个问题:

第一:hash函数的选择。

第二:hash冲突的解决办法。

第三:装填因子大小的选择。装填因子  a=n / m。其中m为hash表的bucket个数;n为关键字的个数。装填因子越大,产生hash冲突就严重。

对于第一条,一般情况下基本不需要自己考虑,找到别人设计好的,同时也很适合自己的hash函数用用就行了。所以暂不考虑hash函数的设计问题(目前的认识是这样)

对于第二条,书上写了一堆的方法,在实践中见到的最多的就是“拉链法”,暴雪hash中使用的应该叫线性探测再散列法。还有就是再哈希法,布谷鸟hash中使用的应该就是此种方法,孰优孰劣不好说,各有千秋吧,以后用到时会再对比,再分析。

对于第三条,记忆中看那本关于Java的教材时,上边说最佳的取值范围是0.75--0.8。不太确定,但是《数据结构》教材上有关于hash算法性能与装填因子a取值的数学推导,后续详细研究。

二,字符串hash函数评估(注:参照刘爱贵的专栏中《字符串函数的评估》这篇文章。但有我的思考,测试结果也和原文有向左之处)

Hash查找因为其O(1)的查找性能而著称,被对查找性能要求高的应用所广泛采用。它的基本思想是:

(1) 创建一个定长的线性Hash表,一般可以初始化时指定length;

(2) 设计Hash函数,将关键字key散射到Hash表中。其中hash函数设计是最为关键的,均匀分布、冲突概率小全在它;

(3) 通常采用拉链方法来解决hash冲突问题,即散射到同一个hash表项的关键字,以链表形式来表示(也称为桶bucket);

(4) 给定关键字key,就可以在O(1) + O(m)的时间复杂度内定位到目标。其中,m为拉链长度,即桶深。

Hash应用中,字符串是最为常见的关键字,应用非常普通,现在的程序设计语言中基本上都提供了字符串hash表的支持。字符串hash函数非常多,常见的主要有Simple_hash, RS_hash, JS_hash, PJW_hash, ELF_hash, BKDR_hash, SDBM_hash, DJB_hash, AP_hash, CRC_hash等。它们的C语言实现见后面附录代码: hashFunction.c, hashTests.c。那么这么些字符串hash函数,谁好熟非呢?评估hash函数优劣的基准主要有以下两个指标:

(1) 散列分布性

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

(2) 平均桶长

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

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

评估方案的设计:

第一步:随机生成1000个字符串,每个字符串的长度均为10。将这1000个 字符串写入一个文件test.txt。做为下一步建立hash表的输入。(生成的字符串的个数由你自己决定)

第二步:分别应用上面提到的各种字符串hash函数,进行hash散列模拟。(注:CRC_hash还没改好)

第三步:统计输出结果,用散列分布性和平均桶长两个指标进行评估分析。(是否能用方差和均方差来评估???,暂存的疑问)

实验的结果如下:

表格中字符串的解释,参见如下注释:

printf("bucket_len = %d\n", pHashTable->mBucketLen);   ///哈希表的桶的个数

printf("hit_count = %d\n", hit_count);     ///建立hash表的不重复的元素的个数

printf("buket conflict count = %d\n", conflict_count);  ///冲突的桶的个数

printf("longest hash entry = %d\n", max_link);   ///最长的链的长度

printf("average hash entry length = %.2f\n", avg_link);  ///链表的平均长度

printf("backet usage = %.2f%\n", backet_usage);   ///hash table的桶的使用率

hash_function_name

bucketcount

bucket_len

hit_count

bucket conflict count

longest hash entry

averge hash entry length

bucket usage

string count

simple_hash

1000

1000

1000

264

5

1.59

62.80%

1000

RS_hash

1000

1000

1000

259

5

1.58

63.20%

1000

JS_hash

1000

1000

1000

267

5

1.59

62.90%

1000

PJW_hash

1000

1000

1000

124

18

8

12.5%

1000

ELF_hash

1000

1000

1000

124

18

8

12.5%

1000

BKDR_hash

1000

1000

1000

267

5

1.56

63.90%

1000

SDBM_hash

1000

1000

1000

274

5

1.59

62.70%

1000

DJB_hash

1000

1000

1000

270

6

1.57

63.50%

1000

AP_hash

1000

1000

1000

271

6

1.60

62.50%

1000

以上实验结果使用的装填因子是1,装填因子更小些,更能评估不同hash函数散列结果的好坏。

实验结果中,PJW_hash和ELF_hash函数的实验结果很差。

以上hash函数的由来很感兴趣,有待挖掘下!!!,看到的朋友如果了解这些函数的来源请告知,谢谢!

另:以上结果如有异议,请留言多多指教,谢谢。

以下是实验的源代码:

第一部分:字符串随机生成代码(注:此部分也是改编网络上某个哥们儿的代码,一时找不到出处了,谁看到了请告知,我添加上引用,感谢尊重他人劳动成果):

#include

#include

#include

#include

#include

#define STRINGSIZE 10

#define STRINGCOUNT 1000

//如果是在一个程序的循环中不断调用这个函数,那么是没有效果的虽然也是使用的系统的时间函数来初始化随机数发生器,但程序的

//执行速度太快了,可能执行1000次循环返回的秒数都是一样的time返回时间戳

/*

void get_rand_str(char s[],int num)

{

//定义随机生成字符串表

char *str = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";

int i,lstr;

lstr = strlen(str);//计算字符串长度

srand((unsigned int)time((time_t *)NULL));//使用系统时间来初始化随机数发生器

for(i = 0; i

{

s[i]=str[(rand()%lstr)];

}

s[i++]='\n';

s[i]='\0';

printf("%s",s);

}

*/

int main()

{

FILE *fp1;          //定义文件流指针,用于打开读取的文件

char text[10];      //定义一个字符串数组,用于存储读取的字符

int i=0,j=0,lstr;

char *str = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";

lstr = strlen(str);//计算字符串长度

fp1 = fopen("d:\\test.txt","r+");//只读写方式打开文件a.txt

//while(fgets(text,1024,fp1)!=NULL)//逐行读取fp1所指向文件中的内容到text中

srand((unsigned int)time((time_t *)NULL));//使用系统时间来初始化随机数发生器

for(j=0;j

{

for(i = 0; i

{

text[i]=str[(rand()%lstr)];

}

text[i++]='\n';

text[i]='\0';

fputs(text,fp1);//将内容写到fp1所指向文件中

}

fclose(fp1);//关闭文件a.txt,有打开就要有关闭

}

第二部分:字符串hash函数评估代码:

hashFunction.c

#include

#include

#include "hashTest.h"

/* 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);

}

/* RS Hash Function */

unsigned int RS_hash(char *str)

{

unsigned int b = 378551;

unsigned int a = 63689;

unsigned int hash = 0;

while (*str)

{

hash = hash * a + (*str++);

a *= b;

}

return (hash & 0x7FFFFFFF);

}

/* JS Hash Function */

unsigned int JS_hash(char *str)

{

unsigned int hash = 1315423911;

while (*str)

{

hash ^= ((hash <> 2));

}

return (hash & 0x7FFFFFFF);

}

/* P. J. Weinberger Hash Function */

unsigned int PJW_hash(char *str)

{

unsigned int BitsInUnignedInt = (unsigned int)(sizeof(unsigned int) * 8);

unsigned int ThreeQuarters     = (unsigned int)((BitsInUnignedInt   * 3) / 4);

unsigned int OneEighth         = (unsigned int)(BitsInUnignedInt / 8);

unsigned int HighBits          = (unsigned int)(0xFFFFFFFF) <

unsigned int hash              = 0;

unsigned int test              = 0;

while (*str)

{

hash = (hash <

if ((test = hash & HighBits) != 0)

{

hash = ((hash ^ (test >> ThreeQuarters)) & (~HighBits));

}

}

return (hash & 0x7FFFFFFF);

}

/* ELF Hash Function */

unsigned int ELF_hash(char *str)

{

unsigned int hash = 0;

unsigned int x     = 0;

while (*str)

{

hash = (hash <

if ((x = hash & 0xF0000000L) != 0)

{

hash ^= (x >> 24);

hash &= ~x;

}

}

return (hash & 0x7FFFFFFF);

}

/* BKDR Hash Function */

unsigned int BKDR_hash(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);

}

/* SDBM Hash Function */

unsigned int SDBM_hash(char *str)

{

unsigned int hash = 0;

while (*str)

{

hash = (*str++) + (hash <

}

return (hash & 0x7FFFFFFF);

}

/* DJB Hash Function */

unsigned int DJB_hash(char *str)

{

unsigned int hash = 5381;

while (*str)

{

hash += (hash <

}

return (hash & 0x7FFFFFFF);

}

/* AP Hash Function */

unsigned int AP_hash(char *str)

{

unsigned int hash = 0;

int i;

for (i=0; *str; i++)

{

if ((i & 1) == 0)

{

hash ^= ((hash <> 3));

}

else

{

hash ^= (~((hash <> 5)));

}

}

return (hash & 0x7FFFFFFF);

}

/* CRC Hash Function */

/*

unsigned int CRC_hash(char *str)

{

unsigned int        nleft   = strlen(str);

unsigned long   long    sum     = 0;

unsigned short int *w       = (unsigned short int *)str;

unsigned short int  answer  = 0;

// Our algorithm is simple, using a 32 bit accumulator (sum), we add

// sequential 16 bit words to it, and at the end, fold back all the

// carry bits from the top 16 bits into the lower 16 bits.

while ( nleft > 1 ) {

sum += *w++;

nleft -= 2;

}

//mop up an odd byte, if necessary

if ( 1 == nleft ) {

*( unsigned char * )( &answer ) = *( unsigned char * )w ;

sum += answer;

}

// add back carry outs from top 16 bits to low 16 bits

// add hi 16 to low 16

sum = ( sum >> 16 ) + ( sum & 0xFFFF );

// add carry

sum += ( sum >> 16 );

// truncate to 16 bits

answer = ~sum;

return (answer & 0xFFFFFFFF);

}

*/

hashTests.c

#include

#include

///do not know where use those head file

///#include

///#include

///#include

///#include

#include

#include "hashTest.h"

//#include "md5.h"

#define STRING_LEN 255

///one atom of the chain when build the hash table

struct AtomOfBucketChain {

unsigned char *pKey;

struct AtomOfBucketChain *pNext;

};

struct ChainOfHashTable {

unsigned int mHitCount;

unsigned int mEntryCount;

struct AtomOfBucketChain *pKeys;

};

struct HashTable {

unsigned int mBucketLen;

struct ChainOfHashTable *pTable;

};

unsigned int (*pHashFunc)(char *str);

///choose which hash function to be used

void chooseHashFunc(char *pHashFuncName)

{

if (0 == strcmp(pHashFuncName, "simple_hash"))

pHashFunc = simple_hash;

else if (0 == strcmp(pHashFuncName, "RS_hash"))

pHashFunc = RS_hash;

else if (0 == strcmp(pHashFuncName, "JS_hash"))

pHashFunc = JS_hash;

else if (0 == strcmp(pHashFuncName, "PJW_hash"))

pHashFunc = PJW_hash;

else if (0 == strcmp(pHashFuncName, "ELF_hash"))

pHashFunc = ELF_hash;

else if (0 == strcmp(pHashFuncName, "BKDR_hash"))

pHashFunc = BKDR_hash;

else if (0 == strcmp(pHashFuncName, "SDBM_hash"))

pHashFunc = SDBM_hash;

else if (0 == strcmp(pHashFuncName, "DJB_hash"))

pHashFunc = DJB_hash;

else if (0 == strcmp(pHashFuncName, "AP_hash"))

pHashFunc = AP_hash;

// else if (0 == strcmp(pHashFuncName, "CRC_hash"))

//     pHashFunc = CRC_hash;

else

pHashFunc = NULL;

}

///build the hash table

void buildHashTable(unsigned char *pKey, struct HashTable *pHashTable)

{

unsigned int mHashValue = pHashFunc(pKey) % pHashTable->mBucketLen;

struct AtomOfBucketChain *p=NULL;

p = pHashTable->pTable[mHashValue].pKeys;

while(p)

{

if (0 == strcmp(pKey, p->pKey))

{

break;

}

p = p->pNext;

}

if (p == NULL)

{

p = (struct AtomOfBucketChain *)malloc(sizeof(struct AtomOfBucketChain));

if (p == NULL)

{

printf("malloc in buildHashTable filled");

return ;///must have 'return',否则失败也不会停止。

}

p->pKey = strdup(pKey);

p->pNext = pHashTable->pTable[mHashValue].pKeys;

pHashTable->pTable[mHashValue].pKeys = p;

pHashTable->pTable[mHashValue].mEntryCount++;

}

pHashTable->pTable[mHashValue].mHitCount++;

}

///initial hash table

void hashTableInit(struct HashTable *pHashTable)

{

unsigned int i;

if ((NULL == pHashTable) || (NULL==pHashTable->pTable))

{

printf("hashTableInit: malloc pHashTable or pTable failed");

return;

}

for (i = 0; i mBucketLen; i++)

{

pHashTable->pTable[i].mHitCount=0;

pHashTable->pTable[i].mEntryCount=0;

pHashTable->pTable[i].pKeys=NULL;

}

}

///free space hash table used

void freeHashTable(struct HashTable *pHashTable)

{

unsigned int i;

struct AtomOfBucketChain *pFront, *pBack;

if ((NULL == pHashTable) || (NULL==pHashTable->pTable))

{

printf("hash table has been free");

return;

}

for (i = 0; i mBucketLen; i++)

{

pFront = pHashTable->pTable[i].pKeys;

while(pFront)

{

pBack = pFront->pNext;

if (pFront->pKey) free(pFront->pKey);

free(pFront);

pFront = pBack;

}

}

free(pHashTable->pTable);

}

///显示统计结果

void showTestsResult(struct HashTable *pHashTable)

{

int backet = 0, sum = 0;

unsigned i=0, max_link=0;

int conflict_count = 0, hit_count = 0;

double avg_link, backet_usage;

for(i = 0; i mBucketLen; i++)

{

if (pHashTable->pTable[i].mHitCount > 0)

{

backet++;

sum += pHashTable->pTable[i].mEntryCount;

if (pHashTable->pTable[i].mEntryCount > max_link)

{

max_link = pHashTable->pTable[i].mEntryCount;

}

if (pHashTable->pTable[i].mEntryCount > 1)

{

conflict_count++;

}

hit_count += pHashTable->pTable[i].mHitCount;

}

}

backet_usage = backet/1.0/pHashTable->mBucketLen * 100;

avg_link = sum/1.0/backet;

printf("bucket_len = %d\n", pHashTable->mBucketLen);   ///哈希表的桶的个数

/// printf("hash_call_count = %d/n", hash_call_count);   ///建立hash表的字符串的个数

printf("hit_count = %d\n", hit_count);                  ///建立hash表的不重复的元素的个数

printf("buket conflict count = %d\n", conflict_count);      ///冲突的桶的个数

printf("longest hash entry = %d\n", max_link);          ///最长的链的长度

printf("average hash entry length = %.2f\n", avg_link);  ///链表的平均长度

printf("backet usage = %.2f%\n", backet_usage);         ///hash table的桶的使用率

}

//

void usage()

{

printf("Usage:  hash_func_name [backet_len]\n");

printf("hash_func_name:\n");

printf("/tsimple_hash\n");

printf("/tRS_hash\n");

printf("/tJS_hash\n");

printf("/tPJW_hash\n");

printf("/tELF_hash\n");

printf("/tBKDR_hash\n");

printf("/tSDBM_hash\n");

printf("/tDJB_hash\n");

printf("/tAP_hash\n");

// printf("/tCRC_hash\n");

}

int main(int argc, char *argv[])

{

FILE *fp;

int mStringCount=0;

unsigned char  pKey[10];

struct HashTable *pHashTable=NULL;

///参数输入

char hashfunctionname[10],bucketcount[10];

printf("input hashfunctionname\n");

gets(hashfunctionname);

printf("input bucketcount\n");

gets(bucketcount);

pHashTable=(struct HashTable*)malloc(sizeof(struct HashTable));

if(NULL==pHashTable)

{

printf("malloc hash table filled");

return -1;

}

/*

if (argc<=1)

{

usage();

return -1;

}

if (2==argc)

{

usage();

}

*/

//  pHashTable->mBucketLen = atoi(argv[1]);

pHashTable->mBucketLen = atoi(bucketcount);

pHashTable->pTable=(struct ChainOfHashTable*)malloc(sizeof(struct ChainOfHashTable) * pHashTable->mBucketLen);

if (!(fp = fopen("d:\\test.txt", "r")))  ///假设文件已经生成,需要补充自动生成字符串的函数。将生成的字符串保存在一个文件中。

{

printf("open source file filled");

return -1;

}

hashTableInit(pHashTable);

//chooseHashFunc(argv[0]);

chooseHashFunc(hashfunctionname);

while(fgets(pKey,10,fp)!=NULL)//逐行读取fp1所指向文件中的内容到text中

{

mStringCount++;

buildHashTable(pKey,pHashTable);

}

fclose(fp);

showTestsResult(pHashTable);

printf("String Count: %d",mStringCount);    ///建立hash表的字符串的个数

freeHashTable(pHashTable);

return 0;

}

三,暴雪hash的实现及和上述hash函数的对比及分析

四,装填因子和hash算法性能优劣的数学推导。

五,布谷鸟hash算法的实现及讨论。

六,第二部分中的字符串hash函数的由来。

2014年7月6日

未完待续……。

布谷鸟哈希函数的参数_系统学习hash算法(哈希算法)相关推荐

  1. 布谷鸟哈希函数的参数_用于并发读密集型的乐观Cuckoo(布谷鸟) Hashing

    用于并发读密集型的乐观Cuckoo(布谷鸟) Hashing:Optimistic Cuckoo Hashing for concurrent, read-intensive applications ...

  2. 布谷鸟哈希函数的参数_Cuckoo Hash 布谷鸟哈希

    布谷鸟哈希最早于2001 年由Rasmus Pagh 和Flemming Friche Rodler 提出.该哈希方法是为了解决哈希冲突的问题而提出,利用较少计算换取了较大空间.名称源于该哈希方法行为 ...

  3. 布谷鸟哈希函数的参数_Cuckoo Hash 布谷鸟哈希

    查看原文:http://www.dullgull.com/2012/05/cuckoo-hash-%e5%b8%83%e8%b0%b7%e9%b8%9f%e5%93%88%e5%b8%8c/ 布谷鸟哈 ...

  4. js有默认参数的函数加参数_函数参数:默认,关键字和任意

    js有默认参数的函数加参数 PYTHON开发人员的提示 (TIPS FOR PYTHON DEVELOPERS) Think that you are writing a function that ...

  5. 哈希函数的特征_哈希函数及其特征

    哈希函数的特征 Prerequisite: Hashing data structure 先决条件: 哈希数据结构 The hash function is the component of hash ...

  6. python怎么理解函数的参数_理解Python中函数的参数

    定义函数的时候,我们把参数的名字和位置确定下来,函数的接口定义就完成了.对于函数的调用者来说,只需要知道如何传递正确的参数,以及函数将返回什么样的值就够了,函数内部的复杂逻辑被封装起来,调用者无需了解 ...

  7. 函数传参数_算法笔记(7)第二章C、C++快速入门函数,main函数,

    #includevoid change(int x){ x=x+1;}int main(){ int x=10; change(x); prinf("%d\n",x); retur ...

  8. mysql函数输出参数_函数--返回值、参数和作用域

    一.函数的返回值--return的作用 1.return将return后面的值作为函数的输出返回值,当函数被调用时,返回值可以被其他变量接收.使用. 而print只是打印在控制台,一个函数如果没有re ...

  9. 迁移学习 迁移参数_迁移学习简介

    迁移学习 迁移参数 介绍 (Introduction) We as humans have the ability to transfer the knowledge gained in one ta ...

最新文章

  1. JS正则表达式使用方法及示例
  2. 关于EventSource的精华
  3. APT 信息收集——shodan.io ,fofa.so、 MX 及 邮件。mx记录查询。censys.io查询子域名。...
  4. 漫谈:机器学习中距离和相似性度量方法
  5. Spring 源码阅读 之 Spring框架加载
  6. boost::log::attribute_value_set用法的测试程序
  7. mysql表连接_mysql表连接
  8. nc65右键生成菜单_DbSchema生成表单和报表,原来如此简单
  9. Checking battery state… ubuntu
  10. 中内连和外联的去区别_喜欢和爱的区别是什么?“爱”的繁体字会告诉你答案...
  11. CSS 魔法:学海无涯,而吾生有涯
  12. Silverlight 中的 CoreCLR
  13. (5)数据分析-T检验
  14. android机顶盒root,[Android]机顶盒root脚本:SupersuSU获取完美Root权限万能方法,解决二进制更新问题 | 樱花庄...
  15. PageRank算法在社交网络上的应用
  16. POI操作Microsoft Office 之 操作PPT简单示例(附源码)
  17. linux 多核cpu监控,Linux 下多核CPU知识
  18. 云计算有哪些?云计算排排坐,拿去不谢
  19. 【不忘初心】Windows11_22000.593_X64_无更新[深度精简版][1.37G](2022.4.2)
  20. JAVA之简单的随机点名

热门文章

  1. Python 之父 Guido van Rossum 退休失败,正式加入微软搞开源!
  2. 腾讯云区块链产品负责人邵兵:产业区块链刚刚起步,做好基础设施才有可能进入2.0阶段
  3. 停滞数年后,ElasticJob 携首个 Apache 版本 3.0.0-alpha 回归!
  4. 2020 年 AI 产业报告:100 个岗位抢 1 个人,计算机视觉成最大缺口
  5. 不要再被Python洗脑了,来看看这个吧......
  6. Nutanix超融合基础架构和桌面虚拟化解决方案助力新松机器人加速数字化转型
  7. 无代码开发究竟是不是伪需求?
  8. 改写画质、突破性能, Unity 全面升级!
  9. 阿里 20 年,逍遥子宣告「全面迈入数字经济时代」
  10. 大龄开发者究竟该何去何从?2019年Python全栈工程师,都是开发人员改怎么转向高收入?