查找——散列表查找(哈希表)

来源:互联网 发布:python pdf2txt 编辑:程序博客网 时间:2024/05/20 03:38

散列算法概念

散列是通过一定的方法,直接将某个数据的key转化成访问地址。使得存储位置=f(关键字),通过f(key)我们能直接访问这个数据。我们把这种对应关系f成为散列函数,又称哈希函数。采用散列技术将记录存储在一块连续的空间中,这块连续的空间称为散列表或哈希表。关键字对应的存储位置我们称为散列地址。

散列冲突

两个关键字,通过函数计算出来的地址如果是一样的。对于这种情况,我们称之为冲突,并把key1和key2称为散列函数的同义词

散列函数构造方法

散列函数应该要计算简单,而且能够让散列地址均匀分布。这样在查找的时候,既能节约运算时间,又能够节约空间。

常用的散列函数有以下几种:

  1. 直接定址法:我们通过取关键字的线性函数值为散列地址。f(key) = axkey+b(a、b为常数),虽然这样的散列函数简单,均匀而且不会冲突,但我们要事先知道关键词的分布情况。它适合查找表较小且连续的情况,由于这种限制,我们通常情况下不用这种方法。
  2. 数字分析法:在关键字比较大,而且部分相同的情况下(比如我们学校的电话号码,虽然有11位,但前面7位都是相同的),我们可以抽取关键词的部分值,用来作为散列地址。这种方法适合处理关键数比较大的情况,如果事先知道关键字的分布且关键字的若干分布位比较均匀,就可以用这个方法。
  3. 平方取中法:假设有一个关键字1234,我们先得到他的平方1522756,然后再抽取它的中间位227用作散列地址。平方取中法用于不知道关键字的分布,而且位数又不是很大的情况。
  4. 折叠法:将关键字从左到右分割成数位相等的几部分,然后将其相加。比如我们有一个数字12345678,我们先分为123|456|78,然后再123+456+78求和,得出散列地址。折叠法适合不需要知道关键字的分布,位数比较多的情况。
  5. 除留取余法:f(key) = key mod p(p<

散列的冲突解决方法

不管我们使用哪种散列函数,最终还是会得到相同的值,所以要通过一些方法去处理散列冲突。常见的散列冲突解决办法有以下几种:

  1. 开放地址法:如果散列产生了冲突,我们就寻找当前散列地址的下一个地址,并将其作为该key的散列地址。在查找的时候,如果当前的值跟查找的值不相同,说明这个地址冲突了,我们需要后移一个位置。如果后移还是冲突,那么再后移N个,直到找到为止。我们把这种方法称为线性探测法。然而很尴尬的是,如果在后面的散列地址中一直没有空位,而前面刚好有空位,那么我们还是会不断的找后面的。所以我们可以通过双向探测法来解决这个问题(也就是我们不仅可以后移,还可以前移)。当然,我们还可以通过伪随机随机一个位置让其作为冲突的时候的散列地址(伪随机在下次随机的时候,是固定的值)。
  2. 再散列函数法:我们事先准备多个散列函数,如果我们使用某个散列函数发生了冲突,那么我们就换一个散列函数,直到不冲突为止。这种方法会使得关键字不产生聚集,当然他增加了计算的时间。
  3. 链接地址法:如果某个关键字产生了冲突,那么我们使用链表结构,用这个链表记录所有的冲突key,在查找的时候,我们先根据key作为关键字查找,然后再遍历这个链表查找我们想要的值。
  4. 共同溢出方法:我们把所有有冲突的值放到一个数组中,如果在通过key查找的时候,找到的不是我们想要的值,那么我们就去公共溢出表里面顺序查找相应的值。

散列查找性能分析

散列的效率由以下几点决定:

  1. 散列函数是否均匀:散列函数的好坏直接影响着出现冲突的频繁程度,不过由于不同的散列函数对同一组随机的关键字,产生冲突的可能性是相同的。
  2. 冲突处理的方法:冲突处理的方法不同,会使得平均查找长度不同,比如线性探测处理冲突可能会产生堆积,显然没有二次探测法好,而连接地址处理冲突不会产生任何冲突,因而会有更佳的平均查找性能
  3. 散列函数的装填因子。装填因子=填入表中的个数/散列长度,装填因子越大,冲突的可能性就越大,但装填因子越小,浪费的空间就越大。

散列查找实现

#include <stdio.h>#include <iostream>#include <time.h>#include <malloc.h>#include <memory>  #define MAXSIZE 12#define SUCC 1#define  ERROR -1#define NULLKEY -32768typedef  int STATUS;using namespace std;typedef struct {    int *elem;//元素    int count;//长度}HashTable;int m = 0;//初始化一个hashtableSTATUS initHashTable(HashTable *H) {    m = MAXSIZE;    H->elem = (int*)malloc(m * sizeof(int));//初始化数组    H->count = m;    for (int i = 0;i<m;i++)    {        H->elem[i] = NULLKEY;    }    return SUCC;}//初始化一个hashint Hash(int key) {    return key%m;//这里使用除留取余法}//插入一个hashSTATUS insertHash(HashTable *H,int key) {    int addr = Hash(key);    while (H->elem[addr] !=NULLKEY)//如果有冲突,我们就循环数组去找空位    {        addr = (++addr) % m;    }    H->elem[addr] = key;    return SUCC;}STATUS searchKey(HashTable *H,int key,int *addr) {    *addr = Hash(key);    while (H->elem[*addr] != key) {        *addr = (*addr + 1) % m;        if (H->elem[*addr] == NULLKEY || *addr == Hash(key))//如果遍历的时候找到一个值为空或者循环了一整圈,就表示没有找到这个值        {            return ERROR;        }    }    return SUCC;}int main(void) {    int a[] = { 35,37,47,51,58,62,73,88,93,99 };    HashTable *H = (HashTable *)malloc(sizeof(HashTable));    initHashTable(H);    for (int i = 0;i<10;i++)    {        insertHash(H, a[i]);    }    for (int i = 0;i < m;i++)    {        cout << H->elem[i] << endl;;    }    int *addr = (int *)malloc(sizeof(int));    for (int i = 0;i<10;i++)    {        STATUS s = searchKey(H, a[i], addr);        if (s == SUCC)        {            cout << "找到了" << a[i] << "这个值,他在散列的" << *addr << "位置" << H->elem[*addr] << endl;        }        else        {            cout << "未找到" << a[i] << "这个值" << endl;        }    }    delete addr;    delete H;    system("pause");}