相信学计算机的童鞋对于“哈希”这个词会很熟悉,但是能明明白白的说清楚,并且用程序来描述的人还是比较少的。这里,我们就全面学习这个重要的数据结构,以及它的思想和应用。
首先,我们来学习一下几个基本概念。
哈希(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