走进数据结构和算法(c++版)(17)——散列表查找(哈希表)

散列表查找(哈希表)

  前面我们介绍的几种查找都是通过按顺序比对内存存储位置中的关键字与需查找的关键字是否一致来判断是否查找到。对于有序表的查找还可以通过比较大小来折半查找。是否可以通过需查找的关键字直接得到其的内存存储位置?答案就是一种新的存储技术一一散列技术。

散列技术是在记录的存储位置和它的关键字之间建立一个确定的对应关系 f ,使得每个关键字 k e y 对应一个存储位置 f ( k e y ) 。这种对应关系 f 称为散列函数, 又称为哈希( Hash) 函数。采用散列技术将记录存储在一块连续的存储空间中,这块连续存储空间称为散列表或哈希表(Hash table)

  散列技术既是一种存储方法,也是一种查找方法。两个关键字 k e y 1 k e y 2 ,但是却有 f ( k e y 1 ) = f ( k e y 2 ) ,这种现象我们称为冲突(collision) ,并把 k e y 1 k e y 2 称为这个散列函数的同义词(synonym) 。

1.构造方法

直接定址法

  取关键字的某个线性函数值为散列地址:

f ( k e y ) = k e y × a + b ( a b )

  适合查找表较小且连续的情况。

数字分析法

  使用关键字的一部分来计算散列存储位置的方法。适合处理关键字位数比较大的情况,如果事先知道关键字的分布且关键字的若干位分布较均匀,就可以考虑用这个方法。

平方取中法

  对关键字的平方后取中间部分的值。比较适合子不知道关键字的分布,而位数又不是很大的情况。

折叠法

  将关键字从左到右分割成位数相等的几部分(注意最后一部分位数不够时可以短些) ,然后将这几部分叠加求和,并按散列表表长,取后几位作为散列地址。适合关键字位数较多的情况。

除留余数法

  此方法为最常用的构造散列函数方法。对于散列表长为 m 的散列函数公式为:

f ( k e y ) = k e y m o d p ( p m )

  这方法不仅可以对关键字直接取模,也可在折叠、平方取中后再取模。
  若散列表表长为 m , 通常 p 为小于或等于表长(最好接近 m ) 的最小质数或不包含小子20质因子的合数。

随机数法

  选择一个随机数,取关键字的随机函数值为它的散列地址:

扫描二维码关注公众号,回复: 2141904 查看本文章
f ( k e y ) = r a n d o m ( k e y )

2.处理散列冲突的方法

开放定址法

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

f i ( k e y ) = ( f ( k e y ) + d i ) M O D m ( d i = 1 , 2 , 3 , . . , m 1 )

  为了不让关键字都聚集在某一块区域,一般增加平方运算:
f i ( k e y ) = ( f ( k e y ) + d i ) M O D m ( d i = 1 2 , 1 2 , 2 2 , 2 2 , . . , q 2 , q 2 , q m / 2 )

  在冲突时,对于位移量 d i ,采用随机函数计算得到,我们称之为 随机探测法:
f i ( k e y ) = ( f ( k e y ) + d i ) M O D m ( d i )

再散列函数法

  我们事先准备多个散列函数。

f i ( k e y ) = R H i ( k e y ) ( i = 1 , 2 , 3 , . . , k )

  这里 R H i 就是不同的散列函数。这种方法能够使得关键字不产生聚集,但也增加了计算的时间。

链地址法

  将所有关键字为同义词的记录存储在一个单链表中,我们称这种表为同义词子表,在散列表中只存储所有同义词子表的头指针。
  它提供了绝不会出现找不到地址的保障,但也带来了查找时需要遍历单链装的性能损耗。

公共溢出区法

  为所有冲突的关键字建立了一个公共的溢出区来存放。在查找时,对给定值通过散列函数计算出散列地址后,先与基本表的相应位置进行比对,如果相等,则查找成功; 如果不相等,则到溢出表去进行顺序查找。

3.算法实现

  下面我们来看下相关的代码:

#include<vector>
#include<iostream>
#define NULLKEY - 32768
using namespace std;
class HashTable
{
public:
    HashTable(int n);
    ~HashTable();
    void InsertHash(int key);//插入关键字进散列表
    int SearchHash(int key);//查找关键字
    void Show();//显示散列表
private:
    int Hash(int key);//散列函数
    vector<int> elem;//数据元素
    int count;//当前数据元素个数
    int m;//散列表长度
};

#include "HashTable.h"
#include<iostream>
using namespace std;
HashTable::HashTable(int n = 30) :count(0), m(n)
{
    for (int i = 0; i < n; i++)
    {
        elem.push_back(NULLKEY);
    }

}


HashTable::~HashTable()
{

}
int HashTable::Hash(int key)//散列函数
{
    return key % m;//除留余数法
}
void HashTable::InsertHash(int key)//插入关键字进散列表
{
    int addr = Hash(key);
    while (NULLKEY != elem[addr])
    {
        addr = (addr + 1) % key;
    }
    elem[addr] = key;
    count++;
}
int HashTable::SearchHash(int key)//查找关键字
{
    int addr=Hash(key);
    while (elem[addr] != key)
    {
        addr = (addr + 1)%m;
        if (NULLKEY == elem[addr] || Hash(key) == addr)
        {
            return -1;
        }
    }
    return addr;

}
void HashTable::Show()//显示散列表
{
    for (int i = 0; i < m; i++)
    {
        cout << elem[i]<<" ";
    }
    cout << endl;
}

  测试主程序:

#include<iostream>
#include<vector>
#include "HashTable.h"
using namespace std;

int main()
{
    int n,e,val;
    vector<int> vec;
    cout << "输入数据元素个数:";
    cin >> n;
    cout << "输入数据元素:"<<endl;
    for (int i = 0; i < n; i++)
    {
        cin >> e;
        vec.push_back(e);
    }
    HashTable H(n);
    for (int i = 0; i < n; i++)
        H.InsertHash(vec[i]);
    cout << "散列表:"<<endl;
    H.Show();
    cout << "输入要查找的数据元素:" ;
    cin >> val;
    e = H.SearchHash(val);
    if (-1 == e)
        cout << "查找失败" << endl;
    else
        cout << "查找的数据元素在散列表中的位置下标:" << e << endl;
    system("pause");
    return 0;
}

  在VS上运行结果如下:
1

猜你喜欢

转载自blog.csdn.net/hu_weichen/article/details/80820728