当前位置:   article > 正文

数据结构与算法-散列表_用二次探测法插入键值为

用二次探测法插入键值为

无论是顺序表还是树表,查找数据元素时要进行一系列的键值比较的过程,为了减少比较次数,就需要使数据元素的存储位置和键值之间建立某种联系,为此我们就需要使用散列技术动态查找表。首先我们需要熟悉几个基本一概念:

1. 散列函数-数据元素的键值和存储位置之间建立的对应关系;

2. 散列表-用键值通过散列函数获取存储位置的这种存储方式构造的存储结构;

3. 散列地址-由散列函数决定数据元素的存储位置,该位置 称为散列地址;

4. 散列查找-给定关键字,由散列函数进行转换在表中的地址,查看该位置上有无欲查的元素,有则输入该元素,没有则将它插入到该位置上;

理想的情况下,使用的散列函数使每个键值与散列地址是分别对应的,但在实际应用中,这种情况很少出现。若两个元素的键值不相等,但是通过散列函数转换后的散列地址却是一样的,这就形成了冲突,因为散列函数是从键值集合到地址集合的映像,所以一般情况下,冲突只能尽可能的减少,而不能完全避免。因此,采用散列技术时需要考虑两个问题:

第一,如何选择"均匀的"散列函数?

一个好的散列函数应该满足计算简便,运算速度快,随机性好,地址尽可能均匀分布,冲突小。

第二,用什么方法有效解决冲突?

解决冲突的办法将在下面散列表实现中讲解。

1. 常用的散列法

1.1. 数字分析法;

数字分析法又称数字选择法,其方法是收集所有可能出现的键值,排列在一起,对键值的每一位进行分析,选择分布较均匀若干位组成散列地址。所取的位数取决于散列表的表长,若表长为100,取2位即可。

假定已知可能出现的键值如下:

对所有的键值分析不难看出,左起的前三位都是“7 1 2”,第5位只能取1、4,第7位只能取6、7、8,所以这5位都不可取。剩下的4、6、8、9位都是分布较均匀的,可考虑将它们或它们中的几位组织起来作为散列地址。

1. 2. 除留余数法;

除留余数法是一种简单有效却最常用的构造方法,其方法是选择一个不大于散列表长n的正整数p,以键值除以p所得的余数作为散列地址,值得注意的是,这一方法的关键在于p的选择,若p选择的不合适,容易发生冲突,通常应遵循以下规则:

1. p不取偶数。

2. p不取关键字字符集的n倍。

3. 一般p选为最接近表长的质数。

1.3. 平方取中法;

平方取中法以键值平方的中间几位作为散列地址。这一方法计算简单,是一种较常用的构造散列函数的方法,通常在选定散列函数时不一定能知道键值的分布情况,取其中哪几位也不一定合适,而一个数的平方中间几位与这个数的每一位都有关,所得散列地址比较均匀。

1.4. 基数转换法;

将键值看成另一种进制的数再转换成原来进制的数,然后选其中几位作为散列地址。

例如:对于一个十进制键值443730,先把它看成十三进制的数并转换为十进制的数,然后根据散列表的长度从中选取几位作散列地址。

2. 散列表的实现

由于冲突不可避免,所以采用散列技术需要考虑的第二个问题是如何解决冲突。

在遇到冲突时,会按照一定的规则选择该地址的下一个址,如果仍然冲突,则继续按规则选择下一个地址,以此类推直到不发生冲突为止。

通常用来解决冲突的办法有以下几种:

2.1. 线性探测法;

对任何键值 key,设H(key) = d,设散列表的容量为m,则线性探测法生成的后继散列地址为:

d+1,d+2,...,m-1,0,1,...,d-1

例如:如下图所示,长度为13的散列表,其散列函数为H(key) = key mod 13,在表中已填入键值分别为16,30,54的元素。现要插入其键值 为29的元素,散列函数求出散列地址为3,在地址3上已有元素16,发生冲突。应用线性探测法,得到下一个地址为d+1=4,仍冲突,则再求下一个地址d+2 = 5,这个位置上没有元素,将元素填入散列表中序号为5的单元。

从上面的例子可以看出,用线性探测法生成后继散列地址计算简单,但由于探测的是一个连续的地址续列,这样容易导致非同义词之间对同一个散列地址出现争夺现象,俗称"堆积",为了减小堆积的机会,应设法使后继散列地址尽量均匀的分布在整个散列表中。

2.2. 二次探测法;

二次探测法的基本思想:生成的后继散列地址不是连续的,而是跳跃式的,以便为后续数据元素留下空间而减少堆积。按照二次探测法,键值key的散列地址序列为:

其中m为散列表的表长,i = 1^2,-1^2,2^2,-2^2,...,k^2,-k^2,其中k<=m/2

例如:仍然使用线性探测法中的散列表和散列函数,插入键值为29的元素,当发生冲突时,使用二次探测法,得到下一个地址d1 = (3+1^2) mod 13 = 4,仍然冲突,则再求一下地址d2  = (3-1^2) mod 13 = 2,仍然冲突,直到散列地址为d3 = (3+2^2) mod 13 = 7时其位置没有元素,所以将元素填入散列表中序号为7的位置。

二次探测法的缺点是不易探测到整个散列表的空间,也就是说,上述后继散列地址可能难以包括散列表的所有存储位置。

2.3. 链地址法;

链地址法是对每一个同义词都建一个单链表来解决冲突。

设选定的散列函数H,H的值域(即散列地址的范围)为0~(n-1)。设置一个指针向量Pointer HP[n],其中每个指针HP[i]指向一个单链表,该单链表用于存储散列地址为i的数据元素,每一个这样的单链表称为一个同义词子表。

例如:若选定的散列函数为 H(key) = key mod 13,已存入的键值为26、41、25、05、07、15、12、49、51、31、62的散列表。

2.4. 多重散列法;

此法要求设立多个散列函数Hi,i=1,...,k,当给定值key与散列表中的某个值是相对于某个散列函数 Hi 的同义词而发生冲突时,继续计算这个给定值key在下一个散列函数H(i+1)下的散列地址,直到不再产生冲突为止。这种方法的优点是不易产生"堆积",缺点是计算量较大。

2.5. 公从溢出区法;

这种方法的散列表由两个一维数组组成。一个称为基本表,它实际上就是上面所说的散列表,另一个称为溢出表。插入首先在基本表上进行,假如发生冲突,则将同义词存入溢出表。这样,基本表不可能发生冲突。

3. 散列表基本操作算法

3.1. 链地址法散列表;

类型定义

  1. // 定义表长
  2. const int n=20;
  3. typedef struct TagNode{
  4. KeyType key;
  5. struct TagNode *next;
  6. }*Pointer ,Node;
  7. typedef Pointer LinkHash[n];

查找算法

  1. Pointer SeahchLinkHash(KeyType key,LinkHash HP){
  2. // 计算key的散列地址
  3. int i = H(key);
  4. // 将同义词子表表头指针传给p
  5. p = HP[i];
  6. if(p==NULL){
  7. return NULL;
  8. };
  9. // 循环扫描
  10. while((p!=NULL) && (p->key!=key)){
  11. p = p->next;
  12. };
  13. return p
  14. }

插入算法

  1. void InsertLinkHash(KeyType key, LinkHash HP){
  2. if (SearchLinkHash(key, HP) == NULL){
  3. int i = H(key);
  4. // 生成新结点
  5. q = Pointer malloc(size(Node));
  6. q->key = key;
  7. // 将新结点插到同义子表的表头结点之后,原表首结点之前
  8. q->next = HP[i];
  9. HP[i] = q;
  10. }
  11. }

删除算法

  1. void DeleteLinkHash(KeyType key, LinkHash HP){
  2. int i=H(key);
  3. if(HP[i]==NULL){
  4. // 同义词子表为空则返回
  5. return;
  6. }else{
  7. p = HP[i];
  8. // 待删结点位于子表表首
  9. if(p->key == key){
  10. HP[i] = p->next;
  11. free(p);
  12. return;
  13. }else{
  14. // 循环子表
  15. while(p->next!=NULL){
  16. q = p;
  17. p = p->next;
  18. if(p->key == key){
  19. q->next = p->next;
  20. free(p);
  21. return;
  22. }
  23. }
  24. }
  25. }
  26. }

3.2. 线性探测法散列表;

类型定义

  1. const int MaxSize = 20;
  2. typedef struct{
  3. KeyType key;
  4. }Element;
  5. typedef Element OpenHash[MaxSize];

查找算法

  1. int SearchOpenHash(KeyType key,OpenHash HL){
  2. // 计算散列地址
  3. int d = H(key);
  4. int i = d;
  5. while((HL[i].key!=NULL) && (HL[i].key!=key)){
  6. i = (i+1)%m;
  7. };
  8. if(HL[i].key ==key){
  9. // 查找成功
  10. return i;
  11. }else{
  12. // 查找失败
  13. return 0;
  14. }
  15. }

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/AllinToyou/article/detail/561246
推荐阅读
相关标签
  

闽ICP备14008679号