Hash 哈希表和算法思路详解

合集 - 游戏相关好玩的算法(6)

1.Hash 哈希表和算法思路详解2022-07-13

2.动态格子算法2022-09-133.地下城迷宫地图生成算法2022-12-104.AStar寻路算法2022-12-195.柏林噪声算法(Perlin Noise)2023-03-306.柏林噪声分形&幻想大陆地图生成2023-06-02

收起

概述

  • 哈希表是一种可以满足快速查找数据结构,时间复杂度接近O(1)。
  • 哈希函数是无限集到有限集的映射。
  • 处理数据量大,查找效率要求高时推荐使用hash容器。
  • hash不一定优于数组查找,尤其在n比较小的情况下,可能hash算法的代价更高
  • 问题:
    • 什么情况下考虑使用哈希容器?
    • 常用的哈希思路有哪些?
    • 评判哈希算法标准有哪些?
    • 哈希冲突是如何产生的?如何解决?
    • 如何构造一个hash算法?应注意哪些问题?

评判哈希算法标准

  • 效率高。
  • 映射分布均匀。

基础hash思路

直接寻址法:

取关键字key,使用线性函数 Hash(key) = a * key + b。

数字分析法:

在一个班级里,同龄学生很多。在取学生年龄作为key时,应避免以年份作为key组成部分。

平方取中法:

key取平方,截取中间的几位作为新的key。数学计算的性质乘积中间几位和乘数每一位都有关,充分混合key每一位对生成的哈希值的影响,使映射分布更均匀。

取余法:

Hash(key) = key % m

相乘取整法:

Hash(key) = floor(frac(key * A), m), 0<A<1

  • floor 取整,frac 取小数
  • 此法避免像除余法中结果对m过于依赖。

随机数法

Hash(key) = rand(key)

  • 据我所知C#的object采用此方法,使用元数据中的几位存hash值。

折叠法:

将关键字按固定长度分成几段然后相加。

  • 如:Hash(1234,m = 2) = 46。
  • 关键字较长时可以考虑使用此方法。

哈希冲突

产生原因

由于哈希函数是无限集到有限集的映射,换而言之,有限集的元素对应n个无限集的元素,哈希碰撞是不可避免的。

解决办法

开放地址法

当关键字key的哈希地址p=H(key)出现冲突时,递归调用p = Hi(p)直到没有冲突。

Hi=(H(key)+di)Hi=(H(key)+di) % m   i=1,2,,3....,ni=1,2,,3....,n

  • H(key) 为哈希函数
  • m 为表长
  • di 为增量序列

根据增量序列di的不同,又分为:

  • 线性探测:di = 1,2,3,......
  • 二次探测: di = ±1^2, ±2^2,.......
  • 随机探测: di = random(di,seed)
    • random 为 无状态的伪随机发生函数(所谓无状态,即无论多少次调用,random(a) = b不变)
    • seed 一个确定不变的随机数种子

链式地址法

结构示意
pos1
pos2 -> val -> val
pos3 -> val
pos4
...

无限集映射到有限集,有限集的每个元素对应一个链表,链表存储无限集映射到有限集的n个元素。

再哈希法

Hi=RHi(key)i=1,2,…,k

递归调用哈希函数序列中的函数,直到没有冲突。

建立公共溢出区法

建立溢出链表,如发生哈希碰撞,则使用溢出链表。

哈希冲突解决方法优缺点分析

开放散列:链式地址法(桶链法)

  • 优点:
    • 添加删除方便,避免动态调整开销
    • 桶链表内存动态分配,减少内存浪费
    • 当哈希表size很大时,指针的性能消耗可以忽略
  • 缺点:
    • 动态分配内存,内存不紧凑,随机访问性差,序列化性能差。
    • 对于预先知道所有元素,可以实现没有冲突的完美hash函数,此时效率会远低于封闭散列。

封闭散列:开放地址法,再哈希法 ...

  • 优点:
    • 内存紧凑,随机访问性能好,序列化性能好。
    • 预先知道所有元素e,可以实现完美hash函数,此时效率远高于开放散列。
  • 缺点:
    • 所有条目数量不能超过数组的长度,扩容/收紧频繁,性能消耗大。
    • 碰撞探测消耗性能。
    • 当数组长度很大时,有内存浪费。

哈希算法进阶实例分析

这是取自lua5.4的

-- lua 5.4

unsigned int luaS_hash (const char *str, size_t l, unsigned int seed,
                        size_t step) {
  unsigned int h = seed ^ cast_uint(l);
  for (; l >= step; l -= step)
    h ^= ((h<<5) + (h>>2) + cast_byte(str[l - 1]));
  return h;
}

#define lmod(s,size) \
	(check_exp((size&(size-1))==0, (cast_int((s) & ((size)-1)))))

(h << 5) + (h >> 2)
= (((h << 5) << 2) + ((h >> 2) << 2) >> 2)
= ((h << 7) + h) >> 2
= (129 * h) >> 2

  • 和伪随机数生成算法一样,要让生成的数尽量随机--二进制数的每一个位取0或1的概率都是50%。

  • 移位,异或运算充分混合每一位的影响,而加法运算引起多个位的反转,使hash值的每一个位更加不可预测,以接近不可逆的单向函数。

  • (h << 5) + (h >> 2) = (129 * h) >> 2。 乘法可以被拆分为加法和移位的组合(即(h << 7)+h ),以混合哈希值。不过(h << 7 - h) = 127h 会更好些,127是梅森素数(2^n -1)。与线性同余算法(LCG)生成伪随机数一样,梅森素数127,只需一次移位运算和一次加法运算,且不会被分解,随机数分布更加均匀。
    image

    • 非素数会被分解成更小的素数的乘积,参与运算时容易被分解,上例中a和c可以提取公因数d,模 = n = c/d。
  • a%b = a&(b-1) 当 b = 2^n 时等式成立,lua哈希表的长度保证符合等式成立的条件,lmod使用位运算代替取余运算,效率更高。

  • 算法实际应用详情请参考我的文章

进阶哈希算法

下面是一些进阶哈希算法的思路,需要花费一些时间学习。

合集: 游戏相关好玩的算法

分类: 算法

Hash 哈希表和算法思路详解 - 寡人正在Coding - 博客园 (cnblogs.com)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Oracle中的Hash Join是一种基于哈希表的连接算法,它通常用于处理大型数据集上的连接操作。它的基本原理是将连接的两个数据集分别通过哈希函数映射到不同的哈希表中,并在哈希表中查找匹配的行,最终将匹配的行合并为结果集。 具体来说,Hash Join算法分为两个阶段: 1. 构建哈希表:首先,将连接的左表(较小的数据集)通过哈希函数计算出哈希值,然后将每一行插入到哈希表中对应的桶中。这个过程称为“构建哈希表”。 2. 执行连接操作:接下来,将连接的右表(较大的数据集)的每一行也通过哈希函数计算出哈希值,然后在左表的哈希表中查找是否有匹配的行。如果找到了匹配的行,就将它们合并为一条结果行输出。 可以看出,Hash Join 算法的性能取决于哈希表的构建和查找速度。当哈希表足够小,可以全部缓存在内存中时,Hash Join 算法的性能是非常高的。但如果哈希表太大,无法全部缓存在内存中,那么就会产生大量的磁盘I/O操作,从而严重影响性能。 下面是一个简单的案例,假设我们有两个表A和B,它们的结构如下: 表A: ``` id name 1 Alice 2 Bob 3 Charlie 4 David ``` 表B: ``` id age 1 20 2 25 3 30 ``` 现在我们要将这两个表按照id进行连接。使用Hash Join算法,具体的执行步骤如下: 1. 构建哈希表:首先,将表A中的每一行都通过哈希函数计算出哈希值,并插入到哈希表中对应的桶中。这个过程如下: ``` Bucket 1: {1, Alice} Bucket 2: {2, Bob} Bucket 3: {3, Charlie} Bucket 4: {4, David} ``` 2. 执行连接操作:接下来,遍历表B中的每一行,也通过哈希函数计算出哈希值,并在表A的哈希表中查找是否有匹配的行。具体的查找过程如下: ``` Row {1, 20}: 查找Bucket 1,找到匹配的行{1, Alice},将它们合并为结果行{1, Alice, 20} Row {2, 25}: 查找Bucket 2,找到匹配的行{2, Bob},将它们合并为结果行{2, Bob, 25} Row {3, 30}: 查找Bucket 3,找到匹配的行{3, Charlie},将它们合并为结果行{3, Charlie, 30} ``` 最终,Hash Join算法输出的结果集如下: ``` id name age 1 Alice 20 2 Bob 25 3 Charlie 30 ``` 可以看出,Hash Join算法非常适合处理大型数据集上的连接操作,它的性能比其他连接算法如Nested Loop Join和Merge Join都要高效。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值