哈希表

来源:互联网 发布:导出新浪微博数据 编辑:程序博客网 时间:2024/05/17 07:49

原文地址:http://www.cnblogs.com/super-d2/archive/2012/08/04/2620800.html

哈希表是最基础的数据结构之一,利用键值对存储并检索数据的一种非线性结构。

  在其它各种结构线性表、树等数据结构中,记录在结构中的位置是随机的,和记录关键字之间不存在确定的关系,因此,在结构中查找记录时需进行一系列和关键字的“比较”的基础上。在顺序查找时,比较的结果为“==”与“!=”两种可能;在折半查找、二叉排序树查找和B-树查找时,比较的结果为“<”、"="和“>”3种可能。查找的效率依赖于查找过程中所进行的比较次数。

  理想的情况是希望不经过比较,一次存取便能得到所查记录,那就必须在记录的存储位置和它的关键字之间建立一个确定的对应关系f,使每个关键字和结构中一个唯一的存储位置相对应。因而在查找时,只要根据这个对应关系f找到给定值K的像f(k)。若结构中存在关键字和K相等的记录,则必定在f(k)的存储位置上,由此,不需要进行比较便可直接取得所查记录。在此,我们称这个对应的关系f为哈希(Hash)函数,按这个思想建立的表为哈希表。

  我们可以举一个哈希表的最简单的例子。假设要建立一张全国34个地区的各民族人口统计表,每个地区为一个记录,记录的各数据项为:

编号

地区名

总人口

汉族

回族

...

显然,可以用一个一维数组C(1...30)来存放这张表,其中C[i]是编号为i的地区的人口情况。编号i便为记录的关键字,由它唯一确定记录的存储位置c[i]。例如:假设北京市的编号为1,则若要查看北京市的各族人口,只要取出c[1]的记录即可。假如把这个数组看成是哈希表,则哈希函数f(key) = key。然而,很多情况下的哈希函数并不如此简单。可仍以此为例,为了查看方便以地区名作为关键字。假设,很多情况下的哈希函数并不如此简单。可仍以此为例,为了查看方便应以地区名作为关键字。假设地区名以汉语拼音的字符表示,则不能简单地取哈希函数f(key)=key,而是首先要将它们转化为数字,有时候还要作些简单的处理。例如我们可以有这样的哈希函数:(1)取关键字中第一个字母在字母表中的序号作为哈希函数。例如:BEIJING的哈希函数值为字母“B”在字母表中的序号,等于02;或者(2)先求关键字的第一个和最后一个字母在字母表中的序号之和,然后判断这个和值,若比30(表长)大,则减去30.例如:TIANJIN的两个字母“T”和“N”的序号之和为34,故取04为哈希函数值;或(3)先求每个汉字的第一个拼音字母的ASCxx码(英文字母相同)之和的八进制形式,然后将这个八进制数看成是十进制数再除以30取余数,若余数为零则加上30而为哈希函数值。例如:HENAN的头两个拼音字母为“H”和“N”,它们的ASCxx码之和为(226)8,以(226)8除以(30)10得余数为16,则16为HENAN的哈希函数值,即记录在数组中的下标值。上述人口统计表中部分关键字在这3种不同的哈希函数情况下的哈希函数值如表下标所列:

简单的哈希函数示例

 

Key

BEIJING(北京)

TIANJING(天津)

HEBEI(河北)

SHANXI(山西)

SHANHAI(上海)

SHANGDONG(山东)

HENAN(河南)

SICHUAN(四川)

f1(key)

02

20

08

19

19

19

08

19

f2(key)

09

04

17

28

28

26

22

03

f3(key)

04

26

02

13

23

17

16

16

从这个例子可见:

  (1)哈希函数是一个映像,因此哈希函数的设定很灵活,只要使得任何关键字由此所得的哈希函数值都落在表长允许范围之内即可;

  (2)对不同的关键字可能得到同一哈希地址,即key1!=key2,而f(key1)=f(key2),这种现象称为冲突(collision)。具有相同函数值的关键字对该哈希函数来说称做同义词(synonym)。例如:关键字HEBEI和HENAN不等,但f1(HEBEI)=f1(HENAN),又如:f2(SHANXI)=f2(SHANGHAI);f3(HENAN)=f3(SICHUAN)。这种现象给建表造成困难,如在第一种哈希函数情况下,因为山西、上海、山东和四川这4个记录的哈希地址造成困难,如在第一种哈希函数的情况下,因为山西、上海、山东和四川这4个记录的哈希地址均为19,而C[19]只能存放一个记录,那么其他3个记录存放在表中什么位置呢?并且,从上表3个不同的哈希函数的情况可以看出,哈希函数选的合适可以减少这种冲突现象。特别是在这个例子中。只可能有30个记录,可以仔细分析者30个关键字的特性,选择一个切当的哈希函数来避免冲突的发生。

  然而,在一般情况下,冲突只能尽可能地少,而不能完全避免。因为,哈希函数是从关键字集合到地址集合的映像。通常,关键字集合集合比较大,它的元素包括所有可能的关键字。而地址集合元素仅为哈希表中的地址值。假设表长为n,则地址为0到n-1。例如,在C语言的编译程序中可对源程序中的标识符建立一张哈希表。在设定哈希函数时考虑的关键字集合应包含所有可能产生的关键字;假设标识符定义为以字母为首的8位字母或数字,则关键字(标识符)的集合大小(-----PS:数字大,打印不了····) ,而在一个源程序中出现的标识符合是有限的,设表长为1000足矣。地址集合中的元素为0~999。因此,在一般情况下,哈希函数是一个压缩映像,这就不可避免产生冲突。因此,在建造哈希表时不仅要设定一个”好“的哈希函数,而且要设定一种处理冲突的方法。

  综上所述,可如下描述哈希表:根据设定的哈希函数H(key)和处理冲突的方法将一组关键字映像到一个有限的连续地址集上,并以关键字在地址集中的”像“作为记录在表中存储位置,这种表便称为哈希表,这一映像过程称为哈希造表或散列,所得存储位置称哈希地址或者散列地址。

 -------------------------------------------------------------------

哈希函数的构造方法:

  若对于关键字集合中的任一个关键字,哈希函数映像到地址集合中任何一个地址的概率是相等的,则称此类哈希函数为均匀的哈希函数。换句话说,就是使关键字经过哈希函数得到一个“随机的地址“,以便使一组关键字的哈希地址均匀分布在整个地址区间中,从而减少冲突。

1.直接定址法

  取关键字或关键字的某个线性函数值为哈希地址。即:

H(key)=key或H(key)=a*key+b;

其中a和b为常数(这种哈希函数叫做自身函数)。

由于直接定址所得地址集合和关键字集合的大小相同。因此,对于不同的关键字不会发生冲突。但实际中使用这种哈希函数的情况很少。

 2.数字分析法

  假设关键字是以r为基的数(如:以10为基的十进制数),并且哈希表中可能出现的关键字都是事先知道的,则可取关键字的若干数位组成哈希地址。

3.平方取中法

  取关键字平方后的中间几位为哈希地址。

4.折叠法

  将关键字分割成位数相同的几部分(最后一部分的位数可以不同),然后取这几部分的叠加和(舍去进位)作为哈希地址,这方法称为折叠法。

5.除留余数法

  取关键字被某个不大于哈希表表长m的数p除后所得余数为哈希地址。即H(key)=key MOD p,(p<=m),这是一种最简单,也是最常用的构造哈希函数的方法。它不仅可以对关键字直接取模(MOD),也可在折叠、平方取中等运算之后取模。

6.随机数法

  选择一个随机函数,取关键字的随机函数值为它的哈希地址,即H(key)=random(key),其中random为随机函数。通常,当关键字长度不等时采用此法构造哈希函数较切当。

总结:

  实际工作中需视不同情况采用不同的哈希函数,通常,考虑的因素有:

  (1)计算哈希函数所需时间(包括硬件指令的因素);

  (2)关键字的长度;

  (3)哈希表的大小;

  (4)关键字的分布情况;

  (5)记录的查找频率。 

 处理冲突的方法(冲突只能减少,不能避免)

  1.开放定址法

   2.再哈希法

  3.链地址法

  4.建立一个公共溢出区

哈希表的查找及其分析:

  在哈希表上进行查找的过程和哈希造表的过程基本一致。给定的K值,根据造表时设定的哈希函数求得哈希地址,若表中此位置上没有记录,则查找不成功;否则必将关键字,若和给定的值相等,则查找成功;否则根据造表时设定的处理冲突的方法找”下一地址“,直至哈希表中的某个位置为”空“或者表中所填记录的关键字等于给定值时为止。 

   哈希表的装填因子定义为:

    α=(表中填入的记录数)/(哈希表的长度)

 -----------------------------------------------------------------------------------------------------------------------------

哈希表的查找的c语言实现:

复制代码
/* * 题目:给定一个全部由字符串组成的字典,字符串全部由大写字母构成。其中为每个字符串编写密码,编写的 *       方式是对于 n 位字符串,给定一个 n 位数,大写字母与数字的对应方式按照电话键盘的方式: *         2: A,B,C     5: J,K,L    8: T,U,V *         3: D,E,F     6: M,N,O    9: W,X,Y,Z *         4: G,H,I     7: P,Q,R,S *    题目给出一个1--12位的数,找出在字典中出现且密码是这个数的所有字符串。字典中字符串的个数不超过5000。 *         * 思路:1.回溯法找出所有可能的字符串 *       2.在字典中查找此字符串是否存在。(字典存储采用哈希表存储)  * */#include<stdio.h>#include<stdlib.h>#include<string.h>#define HASHTABLE_LENGTH 5001  //哈希表长度#define STRING_LENGTH   13     //单词最大长度//字符串typedef struct{    char str[STRING_LENGTH];    int length;}HString; HString string={'\0',0};             //暂存可能的字符串HString hashTable[HASHTABLE_LENGTH]; //哈希表//hash函数,构造哈希表void createHashTable(char *str){    int i,key,step=1;    i=key=0;    while(str[i]){        key+=str[i++]-'A';    }    key%=HASHTABLE_LENGTH;    while(1){        if(hashTable[key].length==0){            hashTable[key].length=strlen(str);            strcpy(hashTable[key].str,str);            break;        }        key=(key+step+HASHTABLE_LENGTH)%HASHTABLE_LENGTH;        //处理冲突,线性探测再散列        if(step>0)            step=-step;        else{            step=-step;            step++;        }    }}//从文件中读字典void readString(){    int i;    char str[STRING_LENGTH];    char ch;    FILE *fp;    if((fp=fopen("document/dictionary.txt","r"))==NULL){          printf("can not open file!\n");          exit(0);       }          i=0;    while((ch=getc(fp))!=EOF){           if(ch=='\n'){//读完一个字符串            str[i]='\0';            createHashTable(str);            i=0;            continue;        }        str[i++]=ch;    }    if(fclose(fp)){           printf("can not close file!\n");           exit(0);       }   }//在哈希表中查找是否存在该字符串,存在返回1,不存在返回0int search(char *str){    int i,key,step=1;    i=key=0;    while(str[i]){        key+=str[i++]-'A';    }    key%=HASHTABLE_LENGTH;    while(1){        if(hashTable[key].length==0)            return 0;        if(strcmp(hashTable[key].str,str)==0){            return 1;        }        key=(key+step+HASHTABLE_LENGTH)%HASHTABLE_LENGTH;        //处理冲突,线性探测再散列        if(step>0)            step=-step;        else{            step=-step;            step++;        }    }    return 0;}//求所有可能的字符串void getString(char* num){    int i,digit,max;    if(*num==0){//递归出口,字符串已到末尾        string.str[string.length]='\0';        if(search(string.str))//这个字符串存在于字典中,输出            puts(string.str);        return;    }    digit=*num-'0';//取第一位字符,转成数字    if(digit>=2&&digit<=6){        i=(digit-2)*3+'A';        max=(digit-2)*3+'A'+3;    }    else if(digit==7){        i='P';        max='P'+4;    }    else if(digit==8){        i='T';        max='T'+3;    }    else if(digit==9){        i='W';        max='W'+4;    }    for(i;i<max;i++){        string.str[string.length++]=i;        getString(num+1); //递归        string.length--;    }}void main(){    char num[STRING_LENGTH];   //由于输入的数字超出了unsigned long的范围,所以用字符串来存储    readString();              //把字典从文件中读入内存    printf("please inputer an number(1--12位,不能有0或1)\n");    scanf("%s",num);    getString(num);}
0 0
原创粉丝点击