散列表

散列表又可以称之为哈希表,是实现字典操作的一种有效数据结构

最坏情况下查找元素时间为O(n),但是平均时间为O(1)

适用于静态关键字集合,如程序设计语言中的保留字集合,CD-ROM上的文件名集合

直接寻址法

当全域U比较小时,直接寻址是一种简单有效的技术,用数组表示动态集合,记为T[0..m],其中每个位置称为槽。

直接寻址法就是将元素直接放在槽中,如果不放,则需要用某种方法表示槽为空。

操作:

typedef struct node {
    int key;
} *NODE;

NODE direct_address_search(NODE T[], int k) { //查找
    return T[k];
}


void direct_address_insert(NODE T[], int x) { //插入
    T[x->key] = x;
}

void direct_address_delete(NODE T[], int x) { //删除
    T[x->key] = NULL;
}

直接寻址的缺点:当要储存的关键字比可能要储存的关键字小的多时,那么被分配的大部分空间会被浪费,那么需要散列函数列重新计算关键字的值T(h(k)),再放到槽里,h(k)可以称之为散列值。

存在的问题:当两个散列值映射到同一槽中时,形成了冲突。

为了解决冲突,有两种方法,一:通过精心设计的散列函数来尽量减少冲突的次数,二:需要有解决冲突的办法。



解决冲突的办法

链接法

在链接法中,把散列到同一槽中的所有元素都放在一个链表中,如图

装载因子 等于n/m,n为存放的元素个数,m位槽的个数

一次不成功或成功的查找的平均时间都为O(1+n/m)

开放寻址法

在开放寻址法中,散列表可能被填满,以至于不能插入新的元素。该方法导致结果是装填因子不会超过1

优点:不需要用指针,而是计算要存取的槽序列,于是不需要存储指针而节省的空间,是的可以用同样的空间来提供更多的槽,潜在减少冲突,提高了检索速度

为了使用开放寻址法插入一个元素,需要连续的检查散列表,称为探查

int hash_insert(NODE T[], int k) { //插入
    int i = 0, j;
    while(i != m) {
        j = h(k, i); // h为探查序列
        if(T[j] != NULL) {
            T[j] = k;
            return j;
        }else{
            i = i + 1;
        }
    }
    print("hash table overflow\n");
}

int hash_search(NODE T[], int k) { //查找
    i = 0;
    while(T[i] != NULL || i != m) {
        j = h(k, i); //探查序列
        if(T[j] == k) {
            return j;
        }
        i = i + 1;
    }
    return NULL;
}

从开放寻址法散列表中删除一个元素是比较困难的。解决办法是删除时候用delete代替NULL标记槽。

为此在必须删除关键字的应用中,更常见的做法是使用链接法解决冲突。

有三种技术来计算开放寻址法中的探查序列:

1.线性探查

散列函数 h(k,i) = (h’(k) + i) mod m, i = 0,…,m-1 。h’为辅助散列函数

该方法存在一次群集的现象,当一个空槽前有i个满的槽时,该空槽下一次会有很大概率被占用,以至于被占用的槽越来越长,平均查找时间也会越来越大。

2.二次探查

散列函数 h(k,i) = (h’(k) + bi + ci*i) mod m, i = 0,…,m-1。 b,c为正的辅助常数。

该方法存在二次群集的现象

3.双重散列

二重散列是用于开放寻址法的最好方法之一,因为他所产生的排列具有随机选择排列的许多特性

散列函数 h(k,i) = (h1’(k) + i*h2(k)) mod m, i = 0,…,m-1。 h1和h2为辅助散列函数

初始探测位置为T[h1(k)],然后每次移动i*h2(k) mod m 。

完全散列

该方法最坏情况下访问时间为O(1)

该散列结构如图:

采用一级散列表和二级散列表,在同一槽位的元素进行二级散列


精心设计散列函数

除法散列法

函数表示 h(k) = k mod m;

在进行除法散列法时,m不应为2的幂,如何m=2的p次幂,h(k)就是k的p个最低位数字,所以一个不太接近
2的整数幂的素数,常常是m的一个较好选择

乘法散列法

函数表示 h(k) = int(m(kA mod 1))

kA mod 1 代表取kA的小数部分,即kA-int(kA)

进行乘法散列法时,对于m的取值不是很关键,一般选择2的某个幂,因为在大多数计算机上比较容易实现

全域散列法

随机地选择散列函数,使之独立于要存储的关键字,不管别人选什么关键字,其平均性能都很好,这就是全域散列