C++:哈希表

目录

哈希的概念

直接定址法

哈希冲突

负载因子 

哈希函数

除法散列法/除留余数法

乘法散列法

处理哈希冲突

开放定址法

线性探测

二次探测

双重散列

链地址法

哈希表的实现

哈希表的结构

闭散列(开放定址法)

结构

插入

查找

删除

开散列(链地址法)

结构

插入

查找

删除

完整代码


哈希的概念

哈希又称散列,是一种组织数据的方式

它的本质是通过哈希函数把关键字key跟存储位置建立一个映射关系,查找时再通过这个哈希函数计算出key存储的位置,进行快速查找

所以它的查找时间复杂度能达到恐怖的O(1)

直接定址法

直接定址法可以是一个26个大小的数组arr[26]来代表26个英文字母,这时候这26个空间就和这26个英文字母建立了映射关系

 . - 力扣(LeetCode)

class Solution {
public:
    int firstUniqChar(string s) {
        int hash[26];
        for (auto str : s)
            hash[str - 'a']++;
        for (int i = 0; i < s.size(); i++)
            if (hash[s[i] - 'a'] == 1)
                return i;
        return -1;
    }
};

哈希冲突

当我们使用直接定址法的时候,当数据映射到同一个位置的时候就把它叫做哈希冲突或者哈希碰撞

哈希冲突是不可避免的 

为了减少哈希冲突,我们可以使用一个比较好的哈希函数来减少哈希冲突

负载因子 

若哈希表的大小为M,已经映射存储的数据个数为N,那么 负载因子 = N / M

负载因子越大,哈希冲突的概率越高,空间利用率越高

负载因子越小,哈希冲突的概率越低,空间利用率越低

哈希函数

除法散列法/除留余数法

假设哈希表的大小为M

那么通过key除以M的余数作为映射位置的下标

哈希函数为:hashi = key % M 

乘法散列法

乘法散列法对哈希表的大小M没有要求

第一步:用关键字key乘上常数A(0 < A < 1),并抽取key*A的小数部分

第二部:后再用M乘以key*A的小数部分,再向下取整

哈希函数为:hashi = floor(M * ((A * key) % 1.0))

这里最重要的是A的值如何设定

Knuth认为A = 0.6180339887......(黄金分割点)比较好

处理哈希冲突

主要有两种方法,开放定址法和链地址法

开放定址法

线性探测

从发生冲突的位置开始,依次线性向后探测,直到寻找到下一个没有存储数据的位置为止

如果走到哈希表尾,则回绕到哈希表头的位置

h(key) = hash0 = key % M,若hash0冲突,则线性探测公式为

hc(key, i) = hashi = (hash0 + i) % M, i = {1, 2, 3..., M - 1}(负载因子小于1,最多探测M-1次,一定能找到一个位置存储)

二次探测

从发生冲突的位置开始,依次左右按二次方跳跃式探测,直到寻找到下一个没有存储数据的位置为止,如果从右走到哈希表尾,则回绕到哈希表头的位置,如果往左走到哈希表头,则回绕到哈希表尾的位置

h(key) = hash0 = key % M,若hash0冲突,则二次线性探测公式为

hc(key, i) = hashi = (hash0 +/- i^2) % M, i = {1, 2, 3, ....., M / 2}

当hashi < 0时,需要hash += M

双重散列

当第一个哈希函数计算出的值发生冲突,使用第二个哈希函数计算出一个跟key相关的偏移量值,不断往后探测,直到寻找到下一个没有数据的位置为止

h1(key) = hash0 = key % M,hash0位置冲突了,则双重探测公式为

hc(key, i) = hashi = (hash0 + i * h2(key)) % M,i = {1, 2, 3, ....., M}

链地址法

开放定址法中所有的元素都放到哈希表里,链地址法中所有的数据不再直接存储在哈希表中

哈希表里只需要存储一个指针,当没有数据映射这个位置时,指针为空,当有多个数据映射这个位置时,我们把这些冲突的数据链接成一个链表,挂在哈希表当前位置的下面

链地址法也叫做拉链法或者哈希桶

哈希表的实现

哈希表的结构

enum State
{
	EXIST,
	EMPTY,
	DELETE
};

template<class K, class V>
struct HashData
{
	pair<K, V> _kv;
	State _state = EMPTY;
};

因为我们删除一个值后无法判断这个值是存在还是删除,所以我们可以用一个State状态来标记当前位置的是一个什么状态,所以我们需要枚举出三个状态区分

template<class K&g
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ragef

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值