IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 数据结构与算法 -> 算法学习笔记 - 哈希(Hash) -> 正文阅读

[数据结构与算法]算法学习笔记 - 哈希(Hash)

Hash表

Hash 表又称为散列表,一般由 Hash 函数(散列函数)与链表结构共同实现。与离散化思想类似,当我们要对若干复杂信息进行统计时,可以用 Hash 函数把这些复杂信息映射到一个容易维护的值域内。因为值域变简单、范围变小,有可能造成两个不同的原始信息被 Hash 函数映射为相同的值,所以我们需要处理这种冲突情况。

有一种称为“开散列”的解决方案是,建立一个邻接表结构,以 Hash 函数的值域作为表头数组 head,映射后的值相同的原始信息被分到同一类,构成一个链表接在对应的表头之后,链表的节点上可以保存原始信息和一些统计数据。

Hash 表主要包括两个基本操作:

1. 计算 Hash 函数的值。

2. 定位到对应链表中依次遍历,比较。

无论是检查任意一个给定的原始信息在 Hash 表中是否存在,还是更新它在 Hash 表中的统计数据,都需要基于这两个基本操作进行。

当 Hash 函数设计较好时,原始信息会被比较均匀地分配到各个表头之后,从而使每次查找、统计的时间降低到“原始信息总数除以表头数组长度”。若原始信息总数与表头数组长度都是 O(N) 级别且 Hash 函数分散均匀,几乎不产生冲突,那么每次查找、统计的时间复杂度期望为 O(1)。

例如,我们要在一个长度为 N 的随机整数序列 A 中统计每个数出现了多少次。当数列 A 中的值都比较小时,我们可以直接用一个数组计数(建立一个大小等于值域的数组进行统计和映射,其实就是最简单的 Hash 思想)。当数列 A 中的值很大时,我们可以把 A 排序后扫描统计。这里我们换一种思想,尝试以下 Hash 表的做法。

设计 Hash 函数为 H(x)=(x mod P)+1,其中 P 是一个比较大的质数,但不超过 N。显然,这个 Hash 函数把数列 A 分成 P 类,我们可以依次考虑数列中的每个数 A[i],定位到 head[H(A[i])] 这个表头所指向的链表。如果该链表中不包含 A[i],我们就在表头后插入一个新节点 A[i],并在该节点上记录 A[i] 出现了 1 次,否则我们就直接找到已经存在的 A[i] 节点将其出现次数加 1。因为整数序列 A 是随机的,所以最终所有的 A[i] 会比较均匀地分散在各个表头之后,整个算法的时间复杂度可以近似到 O(N)。

上面的例子是一个非常简单的 Hash 表的直观应用。对于非随机的数列,我们可以设计更好的 Hash 函数来保证其时间复杂度。同样地,如果我们需要维护的是比大整数复杂得多的信息的某些性质(如是否存在、出现次数等),也可以用 Hash 表来解决。

字符串 Hash

下面介绍的字符串 Hash 函数把一个任意长度的字符串映射成一个非负整数,并且其冲突概率几乎为零。

取一固定值 P,把字符串看作 P 进制数,并分配一个大于 0 的数值,代表每种字符。一般来说,我们分配的数值都远小于 P。例如,对于小写字母构成的字符串,可以令 a=1,b=2,...,z=26。取一固定值 M,求出该 P 进制数对 M 的余数,作为该字符串的 Hash 值。

一般来说,我们取 P=131 或 P=13331,此时 Hash 值产生冲突的概率极低,只要 Hash 值相同,我们就可以认为原字符串是相等的。通常我们取 M=2^64,即直接使用 unsigned long long 类型存储这个 Hash 值,在计算时不处理算术溢出问题,产生溢出时相当于自动对 2^64 取模,这样可以避免低效的取模(mod)运算。

除了在极特殊构造的数据上,上述 Hash 算法很难产生冲突,一般情况下上述 Hash 算法完全可以出现在题目的标准解答中。我们还可以多取一些恰当的 P 和 M 的值(例如大质数),多进行几组 Hash 运算,当结果都相同时才认为原字符串相等,就更加难以构造出使这个 Hash 产生错误的数据。

对字符串的各种操作,都可以直接对 P 进制数进行算术运算反映到 Hash 值上。

如果我们已知字符串 S 的 Hash 值为 H(S),那么在 S 后添加一个字符 c 构成的新字符串 S+c 的 Hash 值就是 H(S+c)=(H(S)*P+value[c])mod M。其中乘 P 就相当于 P 进制下的左移运算,value[c] 是我们为 c 选定的代表数值。

如果我们已知字符串 S 的 Hash 值为 H(S),字符串 S+T 的 Hash 值为 H(S+T),那么字符串 T 的 Hash 值 H(T)=(H(S+T)-H(S)*P^length(T))mod M。这就相当于通过 P 进制下在 S 后边补 0 的方式,把 S 左移到与 S+T 的左端对齐,然后二者相减就得到了 H(T)。

根据上面两种操作,我们可以通过 O(N) 的时间预处理字符串所有前缀 Hash 值,并在 O(1) 的时间内查询它的任意子串的 Hash 值。

  数据结构与算法 最新文章
【力扣106】 从中序与后续遍历序列构造二叉
leetcode 322 零钱兑换
哈希的应用:海量数据处理
动态规划|最短Hamilton路径
华为机试_HJ41 称砝码【中等】【menset】【
【C与数据结构】——寒假提高每日练习Day1
基础算法——堆排序
2023王道数据结构线性表--单链表课后习题部
LeetCode 之 反转链表的一部分
【题解】lintcode必刷50题<有效的括号序列
上一篇文章      下一篇文章      查看所有文章
加:2022-08-19 19:31:09  更:2022-08-19 19:31:13 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/7 5:20:09-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码