散列(2)线性探测法和双重散列法

接上篇 散列的简要描述和链地址法

解决散列冲突的方法:

1. 线性探测法

如果我们能够预测将要存入表中元素的数目,而且我们有足够的内存空间可以容纳带有空闲空间的所有关键字,那么使用链地址法是不值得的。我们依靠空的存储空间解决冲突:设计表长M大于元素数目N,开放地址法,最简单的开放地址法是线性探测法:

初始化

该符号表的实现将元素保存到大小是元素个数两倍的散列表中。

void HashTableInit(int max)
{
    N = 0;
    M = 2*max;
    hash_table = new Item[M];
    for(int i = 0; i < M; i++)
        hash_table[i] = NULLItem;
}

插入

(1)当冲突发生时,即准备插入的位置已被占用,我们检查表中的下一个位置,

(2)如果下一个位置也被占用,继续检查下一个,知道遇到一个空位,然后将其插入

在搜索时:

void HashTableInsert(Item item)
{
    int hash_key = Hash(GetKey(item), M);
    while (hash_table[hash_key] != NULLItem) {
        hash_key = (hash_key+1) % M;
    }
    hash_table[hash_key] = item;
    N++;
}

搜索

我们将检查表中是否存在与搜索关键字匹配的元素成为探测。线性探测法的特点是每次探测有3种可能结果:

(1)搜索命中(当前位置上的元素关键字与搜索关键字匹配,停止搜索),

(2)搜索失败(搜索位置为空,停止搜索),

(3)不匹配(搜索位置非空,但是不匹配,继续搜索下一位置)。

Item HashTabelSearch(KeyItem v)
{
    int hash_key = Hash(v, M);
    //遇到空位置,搜索失败
    while (hash_table[hash_key] != NULLItem) {
        if(eq(GetKey(hash_table[hash_key]), v)) //搜索命中
            break;
        hash_key = (hash_key+1) % M; //不匹配
    }
    return hash_table[hash_key];
}

删除

线性探测表中的删除,仅仅移走删除关键字对应的元素时不够的。因为当移走后形成的空位会导致其后面元素的搜索失败(空位终止向后搜索)。因此,应该将删除位置与其右边的下一个空位之间所有元素重新散列插入到表中。

void HashTabelDelete(Item item)
{
    int hash_key = Hash(GetKey(item), M);
    //寻找删除位置
    while (hash_table[hash_key] != NULLItem) {
        if (eq(GetKey(item), GetKey(hash_table[hash_key])))
            break;
        else
            hash_key = (hash_key+1) % M;
    }
    if (hash_table[hash_key] == NULLItem)
        return;
    hash_table[hash_key] = NULLItem;
    N--;
    //将删除位置到其右边下一个空位之间所有的元素重新散列
    for (int j = hash_key+1; hash_table[j] != NULLItem; j = (j+1)%M) {
        HashTableInsert(hash_table[j]);
        hash_table[j] = NULLItem;
    }
}

性能分析

开放地址法的性能依赖于α=N/M,表示表中被位置被占据的百分比,成为装填因子。

对于稀疏表(α较小),预期几次探测就能找到表的空位,但对于接近满的表(α较大),一次搜索要经历相当多次的探测。因此我们为了避免过长时间搜索,不允许表达到接近满。

在线性探测中,多个元素聚合到连续一段空间成为聚焦,这将导致搜索时间的变慢。时间平均开销依赖于插入时的聚焦情况。即:要经历很多次的探测才能确定是否搜索成功(匹配)或失败(空位)。

2. 双重散列表

对于线性探测法,当聚焦问题严重或者表接近满时,要搜索一个关键字,往往要逐个检查很多个无关项(先于和搜索关键字匹配的元素插入)。为了解决聚焦问题,提出了双重散列算法,其基本策略和线性探测法一项,唯一不同是:它不是检查冲突位置后的每一个位置,而是采用另一个散列函数产生一个固定的增量。(跳跃式检查和插入,减小聚焦大小)

假设第二个散列函数值为T

- 线性探测法:逐个检查冲突位置的下一个位置

- 双重散列表:每隔T个位置检查一次

注:第二个散列函数要仔细选择,需满足条件

(1)排除散列值是0的情况

(2)产生的散列值必须与表长M互素

常用`#define Hash2(v) ((v % 97) + 1)

2.1 搜索和插入

void HashTableInsert(Item item)
{
    int hash_key = Hash(GetKey(item), M);
    int hash_key2 = Hash2(GetKey(item), M);

    while (hash_table[hash_key] != NULLItem) {
        // hash_key = (hash_key+1) % M; 线性探测时 +1
        hash_key = (hash_key+hash_key2) % M;
    }
    hash_table[hash_key] = item;
    N++;
}

Item HashTabelSearch(KeyItem v)
{
    int hash_key = Hash(v, M);
    int hash_key2 = Hash2(v, M);
    while (hash_table[hash_key] != NULLItem) {
        if(eq(GetKey(hash_table[hash_key]), v))
            break;
        hash_key = (hash_key+hash_key2) % M;
    }
    return hash_table[hash_key];
}

2.2 删除

如果双重散列表的删除操作继承线性探测算法:那么删除算法会降低双重散列表的性能,因为待删除关键字有可能影响整个表中的关键字,解决办法是:用一个观察哨代替已删除元素,表示该位置被占用,不与任何关键字匹配。

2.3 性能分析

如果要保证所有搜索的平均开销小于t次探测,那么线性探测法和双重散列法的装填因子分别要小于1?1/t√和1?1/t

3. 动态散列表

因为随着表中关键字的增多,表的性能会下降,一种解决办法是:当表快要满时,表的大小加倍,然后被所有元素重新插入。(非经常性操作,可以接受)

如果表支持删除的ADT操作,随着表元素减少,值得对表大小减半。但需要注意的是表长加倍和减半的阈值时不同的。如在半满时加倍,在1/8满时减半。

表长动态变化能够合理处理各种元素个数的变化,缺点是表的扩张和缩减时的重新散列和内存分配的开销。

4. 散列的综述

  • 线性探测是三者中最快的(前提是内存足够大保证表稀疏)
  • 双重散列法使用内存最高效(需要额外开销计算第二个散列值)
  • 链地址法易于实现(假设已经存在好的内存分配),特别对于删除操作;对于固定表长通常选择链地址法。

如何选择:

- 是选择线性探测还是双重散列主要取决于:计算散列函数的开销和装填因子。α较小,两者均可;长关键字计算散列函数开销大;装填因子α接近于1,双重散列性能大于线性探测。

- 链地址法需要额外的内存空间存储链接,但是有一些符号表中已经事先分配好了链接字段的元素。虽然不如开放地址法快,但性能仍然比顺序搜索快的多。

- 当以搜索为主且关键字数目不能精确预测时,动态散列表是可选的。

5. 所有源码

http://download.csdn.net/detail/quzhongxin/8620465

参考资料 《算法:C语言实现》p388-401

时间: 2024-11-08 21:14:44

散列(2)线性探测法和双重散列法的相关文章

哈希冲突的处理【闭散列方法-线性探测和二次探测】

散列表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构.也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度.这个映射函数叫做散列函数,存放记录的数组叫做散列表. 给定表M,存在函数Hash(key),对任意给定的关键字值key,代入函数后若能得到包含该关键字的记录在表中的地址,则称表M为哈希(Hash)表,函数Hash(key)为哈希(Hash) 函数. 构造哈希表的两种方法 1.直接定址法--取关键字的某个线性函数为散列地

处理哈希冲突的闭散列方法-线性探测

说到哈希冲突,就必须谈到哈希函数了. 什么时候哈希函数 哈希冲突函数hv(i),用于在元素i发生哈希冲突时,将其映射至另一个内存位置. 什么是哈希冲突 哈希冲突即关键字不同的元素被映射到了同一个内存位置,包括由同义词冲突和非同义词冲突. 处理哈希冲突的方法很多,这里浅谈一下处理哈希冲突的闭散列方法: 线性探测 如下图所示 在上图中,这里key取8. 实现线性探测代码: #pragma once #include<string> enum Status { EXIST, EMPTY, DELET

Hash算法:双重散列

双重散列是线性开型寻址散列(开放寻址法)中的冲突解决技术.双重散列使用在发生冲突时将第二个散列函数应用于键的想法. 此算法使用: (hash1(key) + i * hash2(key)) % TABLE_SIZE  来进行双哈希处理.hash1() 和 hash2() 是哈希函数,而 TABLE_SIZE 是哈希表的大小.当发生碰撞时,我们通过重复增加 步长i 来寻找键. 第一个Hash函数:hash1(key) = key % TABLE_SIZE. 1 /** 2 * 计算首次的Hash值

哈希表---线性探测再散列(hash)

//哈希表---线性探测再散列 #include <iostream> #include <string> #include <stdio.h> #include <string.h> #define m 10000 #define NULLkey -1 using namespace std; int HashTable[m]; int Hash_search( int k) { int p0, pi; p0=hash(k); //函数运算值 if(Has

线性探测再散列 建立HASH表

根据数据元素的关键字和哈希函数建立哈希表并初始化哈希表,用开放定址法处理冲突,按屏幕输出的功能表选择所需的功能实现用哈希表对数据元素的插入,显示,查找,删除. 初始化哈希表时把elem[MAXSIZE].elemflag[MAXSIZE]和count分别置0.创建哈希表时按哈希函数创建哈希表,输入数据元素的关键字时,以“0”结束输入且要求关键字为正整数,数据元素个数不允许超过表长MAXSIZE. 输出的形式:根据所选择的哈希表的功能输出相应提示语句和正确结果. 程序的功能:将一组个数不超过哈希表

--算法分析与设计--课程作业--【顺序统计】--【采用链表法散列表】--【开放地址法(双重散列)】

本次作业大力感谢以下量 参考信息 经典算法总结之线性时间做选择 http://www.cnblogs.com/javaspring/archive/2012/08/17/2656208.html 11.4 双重散列法 : http://blog.csdn.net/zixiawzm/article/details/6746946 [未完待续]

Java学习笔记(2)----散列集/线性表/队列/集合/图(Set,List,Queue,Collection,Map)

1. Java集合框架中的所有实例类都实现了Cloneable和Seriablizable接口.所以,它们的实例都是可复制和可序列化的. 2. 规则集存储的是不重复的元素.若要在集合中存储重复的元素,就需要使用线性表.线性表不仅可以存储重复的元素,而且允许用户指定存储的位置.用户可以通过下标来访问线性表中的元素. 3. Java集合支持三种类型的规则集:散列集HashSet.链式散列集LinkedHashSet和树形集TreeSet.HashSet以一个不可预知的顺序存储元素:LinkedHas

java 散列与散列码探讨 ,简单HashMap实现散列映射表执行各种操作示列

package org.rui.collection2.maps; /** * 散列与散列码 * 将土拔鼠对象与预报对象联系起来, * @author lenovo * */ //土拨鼠 public class Groundhog { protected int number; public Groundhog(int n) { number=n; } @Override public String toString() { return "Groundhog #" + number

【数据结构】哈希表的线性探测算法

构造哈希表常用的方法是: 除留余数法--取关键值被某个不大于散列表长m的数p除后的所得的余数为散列地址.HashKey= Key % P. 直接定址法--取关键字的某个线性函数为散列地址HashKey= Key 或 HashKey= A*Key + BA.B为常数. 我在这里主要使用一下除留余数法Hash(key) =Key%P,(P这里是哈希表的长度)p最好是素数考虑降低哈希冲突的原因,我并没有在这上面过于追究此处哈希表长度10,见线性探测图. 哈希表经常遇到的一个问题就是哈希冲突. 哈希冲突