哈希查找算法

1.基本思想

哈希查找算法又称散列查找算法,是一种借助哈希表(散列表)查找目标元素的方法,查找效率最高时对应的时间复杂度为 O(1)。哈希查找算法适用于大多数场景,既支持在有序序列中查找目标元素,也支持在无序序列中查找目标元素。

讲解哈希查找算法之前,我们首先要搞清楚什么是哈希表。哈希表(Hash table)又称散列表,是一种存储结构,通常用来存储多个元素。和其它存储结构(线性表、树等)相比,哈希表查找目标元素的效率非常高。每个存储到哈希表中的元素,都配有一个唯一的标识(又称“索引”或者“键”),用户想查找哪个元素,凭借该元素对应的标识就可以直接找到它,无需遍历整个哈希表。

使用数组构建哈希表,最大的好处在于:可以直接将数组下标当作已存储元素的索引,不再需要为每个元素手动配置索引,极大得简化了构建哈希表的难度。我们知道,在数组中查找一个元素,除非提前知晓它存储位置处的下标,否则只能遍历整个数组。哈希表的解决方案是:各个元素并不从数组的起始位置依次存储,它们的存储位置由专门设计的函数计算得出,我们通常将这样的函数称为哈希函数。哈希函数类似于数学中的一次函数,我们给它传递一个元素,它反馈给我们一个结果值,这个值就是该元素对应的索引,也就是存储到哈希表中的位置。

举个例子,将 {20, 30, 50, 70, 80} 存储到哈希表中,我们设计的哈希函数为 y=x/10,最终各个元素的存储位置为:20->2;30->3;50->5;70->7;80->8。

假设我们想查找元素 50,只需将它带入 y=x/10 这个哈希函数中,计算出它对应的索引值为 5,直接可以在数组中找到它。借助哈希函数,我们提高了数组中数据的查找效率,这就是哈希表存储结构。

构建哈希表时,哈希函数的设计至关重要。假设将 {5, 20, 30, 50, 55} 存储到哈希表中,哈希函数是 y=x%10,可以知道,5 和 55 以及 20、30 和 50 对应的索引值是相同的,它们的存储位置发生了冲突,我们习惯称为哈希冲突或者哈希碰撞。设计一个好的哈希函数,可以降低哈希冲突的出现次数。哈希表提供了很多解决哈希冲突的方案,比如线性探测法、再哈希法、链地址法等。

2.哈希冲突的解决办法 

哈希冲突是指不同的键值被哈希函数映射到了同一个槽位上的情况。为了解决哈希冲突,常用的方法包括:

  1. 开放地址法(Open Addressing):如果发生哈希冲突,就在哈希表中寻找下一个空闲槽位,直到找到合适的位置。开放地址法的实现方式包括线性探测、二次探测和双重哈希等。

  2. 链地址法(Chaining):将哈希表中每个槽位都设为链表的头节点,每个链表节点存储键值对。当发生哈希冲突时,将新的键值对插入到对应槽位的链表中。

  3. 拉链法(Separate Chaining):与链地址法类似,不同之处在于每个槽位存储一个链表的头指针,每个链表节点存储一个键值对。

  4. 建立公共溢出区(Overflow Area):当发生哈希冲突时,将键值对插入到公共溢出区中。这种方法可以避免哈希表的大小固定,但查找时间可能会变长。

  5. 完美哈希(Perfect Hashing):在设计哈希函数时,避免冲突的发生。完美哈希函数的设计需要考虑键值的特点和分布情况,一般适用于静态数据集合。

3.开放地址法的详解 

开放地址法是一种解决哈希冲突的方法。当哈希函数将两个或多个不同的键值映射到同一个槽位时,就会发生哈希冲突。开放地址法的思想是在哈希表中寻找下一个可用的槽位,直到找到一个不冲突的位置为止。常见的开放地址法包括线性探测、二次探测和双重哈希等。

  • 线性探测:线性探测是一种最简单的开放地址法,它的思想是在哈希表中依次查找下一个可用的槽位,直到找到一个不冲突的位置为止。具体实现时,当发生哈希冲突时,将查找位置向后移动一个固定的偏移量,直到找到一个空闲槽位或者遍历整个哈希表都没有找到。线性探测的偏移量通常为1,即依次查找相邻的槽位。线性探测的优点是实现简单,空间利用率高,适用于存储的键值对数量较少的情况。但是,它容易产生“聚集现象”,即连续的哈希冲突导致查找时间变长。
  • 二次探测:二次探测是一种改进的开放地址法,它的思想是在哈希表中查找下一个可用的槽位时,依次查找一系列固定的偏移量,而不是像线性探测那样只查找一个偏移量。具体实现时,偏移量的选择可以采用一些数学公式,如:i^2,i^2+i,-i^2,-i^2+i 等。其中,i表示探测次数。二次探测可以避免线性探测的聚集现象,但仍然可能产生“二次聚集现象”。
  • 双重哈希:双重哈希是一种改进的开放地址法,它的思想是使用两个哈希函数来定位下一个可用的槽位。具体实现时,先使用第一个哈希函数计算出一个槽位,如果该槽位已经被占用,则使用第二个哈希函数计算出下一个槽位,以此类推,直到找到一个空闲槽位。双重哈希可以避免线性探测和二次探测的聚集现象,但需要使用两个哈希函数,实现较为复杂。

 代码设计如下:

//
// Created by A on 2023/6/13.
//
#include <iostream>
#include <vector>
#include <cassert>
#include <stack>
#include <queue>

using namespace std;

class NewHashTable {
public:
    NewHashTable(int destSize);

    ~NewHashTable();

    bool NeedRehash();

    void Insert(int newData);

    int Find(int destData);


private:
    int m_size;
    int m_use;
    int *m_pData;
};

NewHashTable::NewHashTable(int destSize)
        : m_size(destSize), m_use(0), m_pData(new int[m_size]) {
    for (int idx = 0; idx < m_size; ++idx) m_pData[idx] = -INT_MIN;
}

NewHashTable::~NewHashTable() {
    delete[] m_pData;
}

void NewHashTable::Insert(int newData) {
    if (NeedRehash()) {
        throw "Need Rehash";
    }

    int hashIdx = newData % m_size;

    if (m_pData[hashIdx] == INT_MIN) {
        m_pData[hashIdx] = newData;
        ++m_use;
        return;
    }

    for (int idx = 1; idx < m_size; ++idx) {
        hashIdx = (newData + idx) % m_size;
        if (m_pData[hashIdx] == INT_MIN) break;
    }

    m_pData[hashIdx] = newData;
    ++m_use;
}

int NewHashTable::Find(int destData) {
    int hashIdx = destData % m_size;

    if (m_pData[hashIdx] == INT_MIN) return -1;
    if (m_pData[hashIdx] == destData) return hashIdx;

    for (int idx = 1; idx < m_size; ++idx) {
        hashIdx = (destData + idx) % m_size;
        if (m_pData[hashIdx] == INT_MIN) return -1;
        if (m_pData[hashIdx] == destData) return hashIdx;
    }

    return -1;
}

bool NewHashTable::NeedRehash() {
    return m_use >= m_size;
}

int main() {
    NewHashTable hashTable(5);

    while (true) {
        std::cout << "请输入操作(INSERT FIND RPINT):" << std::endl;
        std::string choice;
        std::cin >> choice;

        if ("INSERT" == choice) {
            if (hashTable.NeedRehash()) {
                std::cout << "没有剩余空间,请先Rehash" << std::endl;
            } else {
                std::cout << "请输入数据:";
                int newData;
                std::cin >> newData;
                hashTable.Insert(newData);
            }
        } else if ("FIND" == choice) {
            std::cout << "请输入待查数据:";
            int newData;
            std::cin >> newData;
            std::cout << "DestIdx = " << hashTable.Find(newData) << std::endl;
        } else if ("PRINT" == choice) {
            hashTable.Print();
        } else;
    }

    return 0;
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值