Hash表

来源:互联网 发布:童装淘宝店铺名字 编辑:程序博客网 时间:2024/04/28 18:14

Hash表也称散列表,也有直接译作哈希表,Hash表是一种特殊的数据结构,它同数组、链表以及二叉排序树等相比较有很明显的区别,它能够快速定位到想要查找的记录,而不是与表中存在的记录的关键字进行比较来进行查找。这个源于Hash表设计的特殊性,它采用了函数映射的思想将记录的存储位置与记录的关键字关联起来,从而能够很快速地进行查找

Hash表采用一个映射函数 f : key —> address 将关键字映射到该记录在表中的存储位置,从而在想要查找该记录时,可以直接根据关键字和映射关系计算出该记录在表中的存储位置,通常情况下,这种映射关系称作为Hash函数,而通过Hash函数和关键字计算出来的存储位置(注意这里的存储位置只是表中的存储位置,并不是实际的物理地址)称作为Hash地址。

1. Hash函数的设计

Hash函数设计的好坏直接影响到对Hash表的操作效率。所以在构造Hash函数时应尽量考虑关键字的分布特点来设计函数使得Hash地址随机均匀地分布在整个地址空间当中。通常有以下几种构造Hash函数的方法:

  1)直接定址法

  取关键字或者关键字的某个线性函数为Hash地址,即address(key)=a*key+b;如知道学生的学号从2000开始,最大为4000,则可以将address(key)=key-2000作为Hash地址。

  2)平方取中法

  对关键字进行平方运算,然后取结果的中间几位作为Hash地址。假如有以下关键字序列{421,423,436},平方之后的结果为{177241,178929,190096},那么可以取{72,89,00}作为Hash地址。

  3)折叠法

  将关键字拆分成几部分,然后将这几部分组合在一起,以特定的方式进行转化形成Hash地址。假如知道图书的ISBN号为8903-241-23,可以将address(key)=89+03+24+12+3作为Hash地址。

  4)除留取余法

  如果知道Hash表的最大长度为m,可以取不大于m的最大质数p,然后对关键字进行取余运算,address(key)=key%p。

  在这里p的选取非常关键,p选择的好的话,能够最大程度地减少冲突,p一般取不大于m的最大质数。

2.Hash表大小的确定

Hash表大小的确定也非常关键,如果Hash表的空间远远大于最后实际存储的记录个数,则造成了很大的空间浪费,如果选取小了的话,则容易造成冲突。在实际情况中,一般需要根据最终记录存储个数和关键字的分布特点来确定Hash表的大小。还有一种情况时可能事先不知道最终需要存储的记录个数,则需要动态维护Hash表的容量,此时可能需要重新计算Hash地址。

3.冲突的解决

在上述例子中,发生了冲突现象,因此需要办法来解决,否则记录无法进行正确的存储。通常情况下有2种解决办法:

1)开放定址法

   即当一个关键字和另一个关键字发生冲突时,使用某种探测技术在Hash表中形成一个探测序列,然后沿着这个探测序列依次查找下去,当碰到一个空的单元时,则插入其中。比较常用的探测方法有线性探测法,比如有一组关键字{12,13,25,23,38,34,6,84,91},Hash表长为14,Hash函数为address(key)=key%11,当插入12,13,25时可以直接插入,而当插入23时,地址1被占用了,因此沿着地址1依次往下探测(探测步长可以根据情况而定),直到探测到地址4,发现为空,则将23插入其中。

2)链地址法

   采用数组和链表相结合的办法,将Hash地址相同的记录存储在一张线性表中,而每张表的表头的序号即为计算得到的Hash地址。如上述例子中,采用链地址法形成的Hash表存储表示为: 

地址: 表T:

即:h(12)=h(23)=h(34)=1

一般情况:

1.简单均匀希哈(simple uniform hashing):每个属于S的键值K都有相同的几率被希哈函数映射到表T的任意一个槽里面,每个键与其他键或者其他被希哈的记录独立。

with n keys and m slots, a=n/m,a表示链表的平均长度

则1.搜索失败即没有找到所花费的搜索时间为O(1+a),1表示键值映射到槽的时间,a表示搜索槽对应的链表的时间

2.如果希哈表键的数量不会超过槽的数量的整数倍,则搜索花费的时间就是一个常数


最差情况下的访问时间即为访问链表的时间,为O(n),如果整个集合的大小S等于n.

4.Hash表的平均查找长度

Hash表的平均查找长度包括查找成功时的平均查找长度和查找失败时的平均查找长度。 查找成功时的平均查找长度=表中每个元素查找成功时的比较次数之和/表中元素个数; 查找不成功时的平均查找长度相当于在表中查找元素不成功时的平均比较次数,可以理解为向表中插入某个元素,该元素在每个位置都有可能,然后计算出在每个位置能够插入时需要比较的次数,再除以表长即为查找不成功时的平均查找长度

下面举个例子:

  有一组关键字{23,12,14,2,3,5},表长为14,Hash函数为key%11,则关键字在表中的存储如下:

  地址     0     1     2     3      4     5    6   7   8    9  10   11   12    13

  关键字        23    12   14     2     3    5

 比较次数         1      2    1     3     3     2

  因此查找成功时的平均查找长度为(1+2+1+3+3+2)/6=11/6;

  查找失败时的平均查找长度为(1+7+6+5+4+3+2+1+1+1+1+1+1+1)/14=38/14;

  这里有一个概念装填因子=表中的记录数/哈希表的长度,如果装填因子越小,表明表中还有很多的空单元,则发生冲突的可能性越小;而装填因子越大,则发生冲突的可能性就越大,在查找时所耗费的时间就越多。因此,Hash表的平均查找长度和装填因子有关。有相关文献证明当装填因子在0.5左右的时候,Hash的性能能够达到最优。因此,一般情况下,装填因子取经验值0.5。

5.Hash表的优缺点

  Hash表存在的优点显而易见,能够在常数级的时间复杂度上进行查找,并且插入数据和删除数据比较容易。但是它也有某些缺点,比如不支持排序,一般比用线性表存储需要更多的空间,并且记录的关键字不能重复。

下面为简单的hash表操作

1.hash表基本数据结构

//hash表的基本数据结点,链表的结点typedef struct _NODE{int data;struct _NODE* next;}NODE;//希哈表,这里包含十个结构体指针typedef struct _HASH_TABLE{NODE* value[10];}HASH_TABLE;
2.创建hash表
HASH_TABLE* create_hash_table(){HASH_TABLE* pHashTbl = (HASH_TABLE*)malloc(sizeof(HASH_TABLE));//指向希哈表的指针memset(pHashTbl, 0, sizeof(HASH_TABLE));//此函数的作用为将指针指向的大小为HASH_TABLE的区域赋值为0,即初始化return pHashTbl;}
3.在hash表中查找数据

NODE* find_data_in_hash(HASH_TABLE* pHashTbl, int data){NODE* pNode;if(NULL ==  pHashTbl)//判断hash表是否存在return NULL;if(NULL == (pNode = pHashTbl->value[data % 10]))//找到所在hash表的地址return NULL;while(pNode){//在以找到的地址作为链表的首地址来查找if(data == pNode->data)return pNode;pNode = pNode->next;}return NULL;}
4.在hash表中插入数据

STATUS insert_data_into_hash(HASH_TABLE* pHashTbl, int data){NODE* pNode;if(NULL == pHashTbl)return FALSE;if(NULL == pHashTbl->value[data % 10]){//插入数据的地址根据希哈函数没有在hash表中pNode = (NODE*)malloc(sizeof(NODE));//新地址memset(pNode, 0, sizeof(NODE));pNode->data = data;pHashTbl->value[data % 10] = pNode;//此时hash表的值即为地址return TRUE;}if(NULL != find_data_in_hash(pHashTbl, data))//数据如果存在,返回return FALSE;pNode = pHashTbl->value[data % 10];//数据存在且数据的地址找到while(NULL != pNode->next)pNode = pNode->next;pNode->next = (NODE*)malloc(sizeof(NODE));//向链表的最后插入数据memset(pNode->next, 0, sizeof(NODE));pNode->next->data = data;return TRUE;}
5.在hash表中删除数据

STATUS delete_data_from_hash(HASH_TABLE* pHashTbl, int data){NODE* pHead;NODE* pNode;if(NULL == pHashTbl || NULL == pHashTbl->value[data % 10])//hash表或者hash表中的地址都不存在,返回return FALSE;if(NULL == (pNode = find_data_in_hash(pHashTbl, data)))//链表里的数据也不存在,返回return FALSE;if(pNode == pHashTbl->value[data % 10]){//若pNode的值与hash表的地址相同,则数据在此地址的第一个节点上pHashTbl->value[data % 10] = pNode->next;//让此地址指向链表的下一位地址goto final;}pHead = pHashTbl->value[data % 10];//若数据不在找到的地址的第一个节点上,则按删除链表的步骤while(pNode != pHead ->next)pHead = pHead->next;pHead->next = pNode->next;final:free(pNode);return TRUE;}







0 0