相信学计算机的童鞋对于“哈希”这个词会很熟悉,但是能明明白白的说清楚,并且用程序来描述的人还是比较少的。这里,我们就全面学习这个重要的数据结构,以及它的思想和应用。
首先,我们来学习一下几个基本概念。
哈希(hash)
是一种数据编码方式。将大尺寸的数据(如一句话,一张图片,一段音乐、一个视频等)浓缩到一个数字中,从而方便地实现数据匹配和查找的功能。
哈希表(hash table)
一种数据表结构。hash表,有时候也被称为散列表。个人认为,hash表是介于链表和二叉树之间的一种中间结构。链表使用十分方便,但是数据查找十分麻烦;二叉树中的数据严格有序,但是这是以多一个指针作为代价的结果。hash表既满足了数据的查找方便,同时不占用太多的内容空间,使用也十分方便。
打个比方来说,所有的数据就好像许许多多的书本。如果这些书本是一本一本堆起来的,就好像链表或者线性表一样,整个数据会显得非常的无序和凌乱,在你找到自己需要的书之前,你要经历许多的查询过程;而如果你对所有的书本进行编号,并且把这些书本按次序进行排列的话,那么如果你要寻找的书本编号是n,那么经过二分查找,你很快就会找到自己需要的书本;但是如果你每一个种类的书本都不是很多,那么你就可以对这些书本进行归类,哪些是文学类,哪些是艺术类,哪些是工科的,哪些是理科的,你只要对这些书本进行简单的归类,那么寻找一本书也会变得非常简单,比如说如果你要找的书是计算机方面的书,那么你就会到工科一类当中去寻找,这样查找起来也会显得麻烦。
映像
由哈希函数得到的哈希表是一个映像。
冲突
如果两个关键字的哈希函数值相等,这种现象称为冲突。
哈希算法
并不是一个特定的算法而是一类算法的统称。 哈希算法也叫散列算法,一般来说满足这样的关系:f(data)=key,输入任意长度的data数据,经过哈希算法处理后输出一个定长的数据key。同时这个过程是不可逆的,无法由key逆推出data。
处理冲突的几个方法(后面的程序会详解)
稍微想一下就可以发现,既然输入数据不定长,而输出的哈希值却是固定长度的,这意味着哈希值是一个有限集合,而输入数据则可以是无穷多个。那么建立一对一关系明显是不现实的。所以"碰撞"(不同的输入数据对应了相同的哈希值)是必然会发生的,所以一个成熟的哈希算法会有较好的抗冲突性。同时在实现哈希表的结构时也要考虑到哈希冲突的问题。
密码上常用的MD5,SHA都是哈希算法,因为key的长度(相对大家的密码来说)较大所以碰撞空间较大,有比较好的抗碰撞性,所以常常用作密码校验。
下面,我们通过一个例子,来自己实现一个完整的哈希表
问题描述:针对某个集体(比如你所在的班级)中的“人名”设计一个哈希表,使得平均查找长度不超过R,完成相应的建表和查表程序。
思路:假设人名为中国人姓名的汉语拼音形式。待填入哈希表的人名共有30个,取平均查找长度的上限为2。哈希函数用除留余数法构造,用伪随机探测再散列法处理冲突。
代码如下 :
#include <stdio.h> #include <stdlib.h> #include <string> #include <iostream> #ifndef _HashTest_H_ #define _HashTest_H_ #define NAME_NO 30 #define HASH_LENGTH 50 #define M 50; using namespace std; typedef struct { char *py; //名字的拼音 int k; //拼音所对应的整数 }NAME; typedef struct //哈希表 { char *py; //名字的拼音 int k; //拼音所对应的整数 int si; //查找长度 }HASH; #endif HASH HashList[HASH_LENGTH],NameList[HASH_LENGTH]; void InitNameList() { char *f; int r,s0,i; NameList[0].py="zhoujielun"; NameList[1].py="naying"; NameList[2].py="halin"; NameList[3].py="wangfeng"; NameList[4].py="fenghanzao"; NameList[5].py="fuzongkai"; NameList[6].py="hujingbin"; NameList[7].py="huangjianwu"; NameList[8].py="lailaifa"; NameList[9].py="lijiahao"; NameList[10].py="liangxiaocong"; NameList[11].py="linchunhua"; NameList[12].py="liujianhui"; NameList[13].py="luzhijian"; NameList[14].py="luonan"; NameList[15].py="quegaoxiang"; NameList[16].py="sugan"; NameList[17].py="suzhiqiang"; NameList[18].py="taojiayang"; NameList[19].py="wujiawen"; NameList[20].py="xiaozhuomin"; NameList[21].py="xujinfeng"; NameList[22].py="yanghaichun"; NameList[23].py="yeweixiong"; NameList[24].py="zengwei"; NameList[25].py="zhengyongbin"; NameList[26].py="zhongminghua"; NameList[27].py="chenliyan"; NameList[28].py="liuxiaohui"; NameList[29].py="panjinmei"; for(i=0;i<NAME_NO;i++) { s0=0; f=NameList[i].py; for(r=0;*(f+r)!='\0';r++)/*将字符串的各个字符所对应的ASCII码相加,所得的整数做为哈希表的关键字*/ s0=*(f+r)+s0; NameList[i].k=s0; } } void CreateHashList() { int i; for(i=0; i<HASH_LENGTH;i++) { HashList[i].py=""; HashList[i].k=0; HashList[i].si=0; } for(i=0;i<NAME_NO;i++) { int sum=0; int adr=(NameList[i].k)%M;//哈希函数 int d=adr; if(HashList[adr].si==0)//如果不冲突 { HashList[adr].k=NameList[i].k; HashList[adr].py=NameList[i].py; HashList[adr].si=1; } else//冲突 { do { d=(d+NameList[i].k%10+1)%M;//伪随机探测再散列法处理冲突 sum=sum+1; //查找次数加1 }while (HashList[d].k!=0); //找到一个空位置 HashList[d].k=NameList[i].k; HashList[d].py=NameList[i].py; HashList[d].si=sum+1; } } } void FindList() //查找 { char name[20]={0}; int s0=0,r,sum=1,adr,d; printf("input name:"); scanf("%s",name); for(r=0;r<20;r++) //求出姓名的拼音所对应的整数(关键字) s0+=name[r]; adr=s0%M; //使用哈希函数 d=adr; if(HashList[adr].k==s0) //分3种情况进行判断 printf("\nname:%s key:%d find times: 1",HashList[d].py,s0); else if (HashList[adr].k==0) printf("no records!"); else { int g=0; do { d=(d+s0%10+1)%M; //伪随机探测再散列法处理冲突 sum=sum+1; if(HashList[d].k==0) { printf("no records! "); g=1; } if(HashList[d].k==s0) { printf("\nname:%s key:%d find times:%d",HashList[d].py,s0,sum); g=1; } }while(g==0); } } void Display() { int i; float average=0; printf("\naddress\tkey\t\ttimes\tH(key)\t name\n"); //显示的格式 for(i=0; i<50; i++) { printf("%d ",i); printf("\t%d ",HashList[i].k); printf("\t\t%d ",HashList[i].si); printf("\t\t%d ",HashList[i].k%30); printf("\t %s ",HashList[i].py); printf("\n"); } for(i=0;i<HASH_LENGTH;i++) average+=HashList[i].si; average/=NAME_NO; } int main() { char ch1; InitNameList(); CreateHashList (); do { printf("D. show hashTable\nF. find\nQ. quit\nplease choose"); cin>>&ch1; switch(ch1) { case 'D':Display(); cout<<endl;break; case 'F':FindList(); cout<<endl;break; case 'Q':exit(0); } cout<<"come on !(y/n):"; cin>>&ch1; }while(ch1!='n'); return 0; }
我们执行这段程序,会出现如下结果:
根据输出我们可以看到四个值:
关键字就是拼音的ascll码之和。然后通过哈希函数形成了一个值,我们叫做value这段程序是用50取余数之后得到的,所以key值在0~50之间。这个value值决定了数据存放的位置,所以能看到value值跟地址很多是一样的。这里是如何解决“冲突”的呢。如果有两个元素的经过哈希函数之后的结果是一样的呢?这里定义了一个si的值,意思为搜索次数,如果没有冲突意思是一次性找到了位置。
我们来看这一段函数
else //冲突,d的初始值为adr,adr意思是address,也是value值赋予的 { do { d=(d+NameList[i].k%10+1)%M; sum=sum+1; }while(HashList[d].k!=0); //找到空位 HashList[d].k=NameList[i].k; //放入元素 HashList[d].py=NameList[i].py; HashList[d].si=sum+1; }
其中
d=(d+NameList[i].k%10+1)%M
叫做伪随机探测再散列法处理冲突,就是将散列值(value),d,重新散列一次重新获得一次d的值找到一个空的位置,把元素塞进去。
再看一看key是如何得到的,也就是姓名的ascll码之和是如何得到的。
for(i=0;i<NAME_NO;i++) { S0=0; f=NameList[i].py; for(r=0;*(f+r)!='\0';r++) s0=*(f+r)+s0; NameList[i].k=s0; }
这里注意一下,f是字符类型,r是int型,有一个类型转换在这里。
版权声明:本文为博主原创文章,未经博主允许不得转载。
原文地址:http://blog.csdn.net/baidu_28312631/article/details/47749737