数据结构和算法 -- 散列技术

559 阅读4分钟

散列技术

散列技术是记录的存储位置和它的关键字之间建立一个确定的对应关系t,使得每个关键字key对应一个存储位置f(key)。当查找时根据这个对应关系找到给定值key的映射f(key),若查找集合中存在这个记录,则必定在f(key)的位置上。

而关键字key和存储位置f直接的对应关系就是通过散列函数还实现

设计散列函数时,有两个原则:

  • 存储简单,分布均匀,
  • 不适合大范围的查找

散列技术常用方法

直接定址法

我们可以直接通过函数

来计算index

  • 优点:简单,均匀,不易产生冲突
  • 缺点:需要事先知道数据关键字的分布情况,适合查找表小且连续的情况。

例如对0-100岁人口的统计

就可以直接通过定址法确定每个值的地址

数字分析法

如果关键字是一个位数特别多的数字,比如手机号,找出数字的规律,尽可能利用这些数据来构造冲突几率较低的散列地址。

比如一个地区手机号,前面很多位都相同,不同可能是最后4位数,将这部分作为散列算法的依据。

平方取中法

取关键字平方后的中间几位作为散列地址。

先通过求关键字的平方值扩大相近数的差别,然后根据表长度取中间的几位数作为散列函数值。又因为一个乘积的中间几位数和乘数的每一位都相关,所以由此产生的散列地址较为均匀。

并且平方取中法并不仅仅局限于平方,立方、立方根、log等都可以作为运算方式

折叠法

折叠法就是将关键字从左到右分割成位数相等的几部分(注意最后一部分位数不够可以稍微短些)。然后将几部分叠加求和,并按散列表长,取后几位作为散列地址

例如:对数字9876543210进行分割,三位一组,共四组,分别为987、654、321和0。然后对四个数字求和得到1962,最终取和的后三位962作为散列地址。

除留余数法

取关键字被某个不大于散列表表长m的数p除后所得的余数为散列地址。

随机数法

直接通过随机数来得到一个地址

散列冲突

因为地址的范围是有限的,所以涉及的散列函数很容易导致两个不同的值对应同一个地址,这时候就造成散列冲突了,下面列几个解决散列冲突的方法

开放定址法

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

解决冲突的开放定址法称为 线性探测法

再散列函数法

事先准备多个散列函数,当一个散列函数冲突时,用另一个散列函数。

RHi指的是不同的散列函数

链地址法

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

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

则如下存储:

散列表实现

#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
#define MAXSIZE 100 //存储空间初始分配量
#define SUCCESS 1
#define UNSUCCESS 0

//定义散列表长为数组的长度
#define HASHSIZE 12
#define NULLKEY -32768

typedef struct
{
    //数据元素存储基址,动态分配数组
    int *elem;
    //当前数据元素个数
    int count;
}HashTable;
int m=0; /* 散列表表长,全局变量 */

//1.初始化散列表
Status InitHashTable(HashTable *H)
{
    int i;
    
    //1 设置H.count初始值; 并且开辟m个空间
    m = HASHSIZE;
    H->count = m;
    H->elem = (int *)malloc(m*sizeof(int));
    
    //2 为H.elem[i] 动态数组中的数据置空(-32768)
    for(i = 0; i < m; i++)
        H->elem[i] = NULLKEY;
    
    return OK;
}

//2. 散列函数
int Hash(int key)
{
    //除留余数法
    return key % m;
}

//3. 插入关键字进散列表
void InsertHash(HashTable *H,int key)
{
    
    
    //1. 求散列地址
    int addr = Hash(key);
    
    //2. 如果不为空,则冲突
    while (H->elem[addr] != NULLKEY)
    {
        //开放定址法的线性探测
        addr = (addr+1) % m;
    }
    
    //3. 直到有空位后插入关键字
    H->elem[addr] = key;
}

//4. 散列表查找关键字
Status SearchHash(HashTable H,int key,int *addr)
{
    //1. 求散列地址
    *addr = Hash(key);
    
    //2. 如果不为空,则冲突
    while(H.elem[*addr] != key)
    {
        //3. 开放定址法的线性探测
        *addr = (*addr+1) % m;
        
        //4. H.elem[*addr] 等于初始值或者循环有回到了原点.则表示关键字不存在;
        if (H.elem[*addr] == NULLKEY || *addr == Hash(key))
            //则说明关键字不存在
            return UNSUCCESS;
    }
    
    return SUCCESS;
}