散列冲突处理:链地址法与开放地址法

试想一下,当你观望很久很久,终于看上一套房打算要买了,正准备下订金,人家告诉你,这房子已经被人买走了,你怎么办?对呀,再找别的房子呗!这其实就是一种处理冲突的方法开放定址法。

开放定址法

所谓的开放定址法就是一旦发生了冲突,就去寻找下一个空的散列地址,只要散列表足够大,空的散列地址总能找到,并将记录存入。

线性探测法

公式为:

fi(key) = (f(key)+di) MOD m (di=1,2,3,......,m-1)

用开放定址法解决冲突的做法是:当冲突发生时,使用某种探测技术在散列表中形成一个探测序列。沿此序列逐个单元地查找,直到找到给定的关键字,或者碰到一个开放的地址(即该地址单元为空)为止(若要插入,在探查到开放的地址,则可将待插入的新结点存人该地址单元)。查找时探测到开放的地址则表明表中无待查的关键字,即查找失败。
探测方法

比如说,我们的关键字集合为{12,67,56,16,25,37,22,29,15,47,48,34},表长为12。 我们用散列函数f(key) = key mod l2。

当计算前S个数{12,67,56,16,25}时,都是没有冲突的散列地址,直接存入:
开放地址法
计算key = 37时,发现f(37) = 1,此时就与25所在的位置冲突。

于是我们应用上面的公式f(37) = (f(37)+1) mod 12 = 2。于是将37存入下标为2的位置。这其实就是房子被人买了于是买下一间的作法:。开放地址法
接下来22,29,15,47都没有冲突,正常的存入:在这里插入图片描述
到了 key=48,我们计算得到f(48) = 0,与12所在的0位置冲突了,不要紧,我们f(48) = (f(48)+1) mod 12 = 1,此时又与25所在的位置冲突。于是f(48) = (f(48)+2) mod 12=2,还是冲突……一直到 f(48) = (f(48)+6) mod 12 = 6时,才有空位,机不可失,赶快存入:在这里插入图片描述
我们把这种解决冲突的开放定址法称为线性探测法

从这个例子我们也看到,我们在解决冲突的时候,还会碰到如48和37这种本来都不是同义词却需要争夺一个地址的情况,我们称这种现象为堆积。很显然,堆积的出现,使得我们需要不断处理冲突,无论是存入还是査找效率都会大大降低。

二次探测法

考虑深一步,如果发生这样的情况,当最后一个key=34,f(key)=10,与22所在的位置冲突,可是22后面没有空位置了,反而它的前面有一个空位置,尽管可以不断地求余数后得到结果,但效率很差。

因此我们可以改进di = 12, -12, 22, -22,……, q2, -q2 (q <= m/2),这样就等于是可以双向寻找到可能的空位置。

对于34来说,我们取di即可找到空位置了。另外增加平方运算的目的是为了不让关键字都聚集在 某一块区域。我们称这种方法为二次探测法。

公式为:

fi(key) = (f(key)+di) MOD m (di = 12, -12, 22, -22,……, q2, -q2, q <= m/2)

随机探测法

还有一种方法是,在冲突时,对于位移量 di 采用随机函数计算得到,我们称之为随机探测法。

此时一定会有人问,既然是随机,那么查找的时候不也随机生成办吗?如何可以获得相同的地址呢?这是个问题。这里的随机其实是伪随机数。

伪随机数是说,如果我们设置随机种子相同,则不断调用随机函数可以生成不会重复的数列,我们在査找时,用同样的随机种子,它每次得到的数列是相同的,相同的 di 当然可以得到相同的散列地址。

公式为:

fi(key) = (f(key)+di) MOD m (di是一个随机数列)

总之,开放定址法只要在散列表未填满时,总是能找到不发生冲突的地址,是我们常用的解决冲突的办法。

链地址法(拉链法)

前面我们谈到了散列冲突处理的开放定址法,它的思路就是一旦发生了冲突,就去寻找下一个空的散列地址。那么,有冲突就非要换地方呢,我们直接就在原地处理行不行呢?

可以的,于是我们就有了链地址法

将所有关键字为同义词的记录存储在一个单链表中,我们称这种表为同义词子表,在散列表中只存储所有同义词子表的头指针

对于关键字集合{12,67,56,16,25,37, 22,29,15,47,48,34},我们用前面同样的12为除数,进行除留余数法:
在这里插入图片描述

此时,已经不存在什么冲突换址的问题,无论有多少个冲突,都只是在当前位置给单链表增加结点的问题。很不错的解决思路吧?

拉链法解决冲突的做法是:将所有关键字为同义词的结点链接在同一个单链表中。若选定的散列表长度为m,则可将散列表定义为一个由m个头指针组成的指针数组T[0…m-1]。凡是散列地址为i的结点,均插入到以T[i]为头指针的单链表中。T中各分量的初值均应为空指针。在拉链法中,装填因子α可以大于 1,但一般均取α≤1。

拉链法的优势与缺点

与开放定址法相比,拉链法有如下几个优点:

  • 1、拉链法处理冲突简单,且无堆积现象,即非同义词决不会发生冲突,因此平均查找长度较短;
  • 2、由于拉链法中各链表上的结点空间是动态申请的,故它更适合于造表前无法确定表长的情况;
  • 3、开放定址法为减少冲突,要求装填因子α较小,故当结点规模较大时会浪费很多空间。而拉链法中可取α≥1,且结点较大时,拉链法中增加的指针域可忽略不计,因此节省空间;
  • 4、在用拉链法构造的散列表中,删除结点的操作易于实现。只要简单地删去链表上相应的结点即可。而对开放地址法构造的散列表,删除结点不能简单地将被删结点的空间置为空,否则将截断在它之后填人散列表的同义词结点的查找路径。这是因为各种开放地址法中,空地址单元(即开放地址)都是查找失败的条件。因此在用开放地址法处理冲突的散列表上执行删除操作,只能在被删结点上做删除标记,而不能真正删除结点。
  • 5、拉链法的缺点:指针需要额外的空间,故当结点规模较小时,开放定址法较为节省空间,而若将节省的指针空间用来扩大散列表的规模,可使装填因子变小,这又减少了开放定址法中的冲突,从而提高平均查找速度。

链地址法的优势是对于可能会造成很多冲突的散列函数来说,提供了绝不会出现找不到地址的保障。当然,这也就带来了査找时需要遍历单链表的性能损耗,不过性能损耗在很多场合下也不是什么大问题。

参考

  • 8
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
哈希表是一种数据结构,用于实现字典或映射。哈希表使用哈希函数将键映射到存储桶中,以便快速查找值。地址是哈希表的一种实现方式,它使用表来解决哈希冲突的问题。下面是使用地址实现哈希表的C语言代码示例: 首先,定义一个哈希表节点的结构体: ``` typedef struct HashNode{ int key; int value; struct HashNode* next; }HashNode; ``` 哈希表节点包含键值对和一个指向下一个节点的指针。 接着,定义一个哈希表的结构体: ``` typedef struct HashTable{ int size; HashNode** table; }HashTable; ``` 哈希表包含一个大小和一个指向指针数组的指针。 接下来,实现哈希函数,将键映射到存储桶中: ``` int hash(int key, int size){ return key % size; } ``` 这里使用取模运算将键映射到存储桶中。 然后,实现哈希表的初始化函数: ``` HashTable* createHashTable(int size){ HashTable* ht = (HashTable*)malloc(sizeof(HashTable)); ht->size = size; ht->table = (HashNode**)malloc(sizeof(HashNode*) * size); for(int i = 0; i < size; i++){ ht->table[i] = NULL; } return ht; } ``` 这里使用malloc动态分配哈希表和指针数组的内存空间,并将指针数组中的元素初始化为NULL。 接下来,实现插入操作: ``` void insert(HashTable* ht, int key, int value){ int index = hash(key, ht->size); HashNode* node = (HashNode*)malloc(sizeof(HashNode)); node->key = key; node->value = value; node->next = ht->table[index]; ht->table[index] = node; } ``` 这里首先使用哈希函数计算键的索引,然后创建一个新的哈希节点并将其插入到表的头部。 最后,实现查找操作: ``` int search(HashTable* ht, int key){ int index = hash(key, ht->size); HashNode* node = ht->table[index]; while(node != NULL){ if(node->key == key){ return node->value; } node = node->next; } return -1; } ``` 这里首先使用哈希函数计算键的索引,然后遍历该索引处的表,查找对应的键值对。如果找到,则返回其值,否则返回-1。 以上就是使用地址实现哈希表的C语言代码示例。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值