数据结构之散列表(哈希表)

来源:互联网 发布:游戏编程需要怎么学 编辑:程序博客网 时间:2024/06/05 16:47

今天学的是数据结构的散列查找篇,其他的查找可参见以前的传送门

以前的查找都是基于比较关键字的基础上,所以查找的效率依赖于查找过程中所进行的比较次数。
理想的情况是不经过任何比较,通过计算就能直接得到记录所在的存储地址,散列查找(Hashed Search)是基于上述思想的一种查找方式。
散列法又称为哈希法、杂凑法或关键字地址计算法,是一种重要的存储方式,又是一种查找方式。

按散列法存储方式构造的动态查找表称为散列表(Hash Table)。散列法查找的核心是散列函数,又称为哈希函数。

散列法查找的基本思想

以记录中关键字的值K为自变量,通过确定的散列函数H进行计算,求出对应的函数值H(k),并吧这个函数值作为关键字值为K的记录的存储地址,将该记录(或记录的关键字)存放在这个位置上,查找时仍按这个确定的散列函数H进行计算,获得的将是待查找关键字所在记录的存储地址。这样,每个记录的关键字通过散列函数H计算都将对应得到一个记录的存储地址:
Addr( i )=H (第 i 个记录的关键字Ki) 其中H为确定的散列函数:Addr( i )为计算得到的第i 个记录的存储地址。

散列表查找需要解决好以下两个问题:

1、如何设计较好的散列函数。一个好的散列函数应该计算简单(加快转换速度);并且冲突较少,使散列函数结果值均匀分布在散列表的地址空间中。
2、如何处理冲突

散列函数的构造方法

1、直接定址法
此法的散列函数是线性的,取关键字或关键字的某个线性函数值为散列地址

2、除留余数法( 最常用)
取关键字被某个不大于散列表表长m的质数p除后所得余数为散列地址,即对关键字进行取余运算:H( k )=k%p (p<=m)

3、数字分析法
设关键字集合中,每个关键字均由m位组成,每位上可能有r种不同的符号。数字分析法根据r种不同的符号在各位上的分布情况,选取某几位,组合成散列地址。所选的位应使各种符号在该位上出现的频率大致相同。(适用于关键字集中的集合,且关键字是事先知道的)

4、平方取中法
若已知关键字为数字,但预先不一定能够知道关键字的全部情况,用数字分析法难以确定哪几位分布比较均匀,可先求出关键字的平方,然后取其中若干位作为散列地址。(关键字平方后的结果与关键字中每一位都相关,故不同关键字产生不同散列地址的概率较高。

发生冲突是指由关键字得到的散列地址的位置上已经存有记录。而处理冲突是为该关键字的记录找到一个空的散列地址。在找空的散列地址时,可能还会产生冲突,此时需再找下一个空的散列地址,直到不产生冲突为止。
处理冲突法

1、开放定址法(再散列法)
分为:线性探测法、二次探测法
说白了就是本来的位置已经满了,此时继续以当前位置为起点往后寻找到空位,填入。

2、拉链法(链地址法)
即将同一地址的关键字组成链表,放到该地址处

3、建立一个公共溢出区
将散列表分为基本表和溢出表两部分,凡是与基本表发生冲突的元素一律填入溢出表

算法实现(完整版)

以除留余数法分配空间,以线性探测法处理冲突。

#include<stdio.h>#include<malloc.h>typedef struct{    int key;    char data;}record;int prime(int m){    int i,p,flag;    for(p=m;p>=2;p--)    {        for(i=2,flag=1;i<=p/2&&flag;i++)        if(p%i==0)        flag=0;        if(flag==1)        break;    }    return p;}int hi(int key,int p){    return key%p;}void creat(record**r,int n){    int i;    (*r)=(record*)malloc(n*sizeof(record));    printf("input %d number:\n",n);    for(i=0;i<n;i++)    scanf("%d",&((*r)[i].key));}void hashed(record**ht,record*r,int n,int m,int p){    int i,j;    (*ht)=(record*)malloc(m*sizeof(record));    for(i=0;i<m;i++)    (*ht)[i].key=0;    for(i=0;i<n;i++)    {        j=hi(r[i].key,p);        while((*ht)[j].key!=0)        j=(j+1)%m;        (*ht)[j].key=r[i].key;    }}int search(record*ht,int key,int p,int*k){    int i;    *k=1;    i=hi(key,p);    while(ht[i].key!=0&&ht[i].key!=key)    {        i++;        ++*k;    }    if(ht[i].key==0)    return -1;    else return i;}int main(){    record*r,*ht;    int key,i,n,m,p,k;    char ch;    printf("input n and m.(n<=m):");    scanf("%d,%d",&n,&m);    creat(&r,n);    p=prime(m);    printf("the prime is:%d",p);    hashed(&ht,r,n,m,p);    printf("\nthe hashed is:");    printf("\n location:");    for(i=0;i<m;i++)    printf("%3d",i);    printf("\nthe value:");    for(i=0;i<m;i++)    printf("%3d",ht[i].key);    do{        printf("\ninput search value:");        scanf("%d",&key);        i=search(ht,key,p,&k);        if(i!=-1)        {            printf("search good,location:%d",i);            printf("\n compare time:%d\n",k);        }        else {            printf("search bad");            printf("\n compare time:%d\n",k);        }        fflush(stdin);        ch=getchar();    }while(ch=='y'||ch=='Y');    return 0;}

这里写图片描述

性能分析

虽然散列表时基于计算式的查找方式但由于冲突的存在,散列法仍需进行关键字比较,故仍需要用平均查找长度来评价散列法的查找性能。影响散列法中关键字的比较次数的因素有三个:散列函数、处理冲突的方法、散列表的装填因子。
散列表的装填因子α=表中已记录数/表长。α可描述散列表的装填状态程度。α越大,冲突越大;α越小,冲突越小。

总结

数据结构是一门神奇的学科,它的思想很丰富,应用也是相当之广泛,有时间我将会多多更新其中的实际应用,数据结构就先告一段落了,开始伟大的自学奋斗模式了。