ACM 选手带你玩转哈希表!

2024-02-13 05:20
文章标签 玩转 哈希 acm 选手

本文主要是介绍ACM 选手带你玩转哈希表!,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

大家好呀,我是你们的 Rocky0429。

之前我在讲数组的时候说过:要查一个数在数组中的位置,那可是太费劲了,只能从头开始一个个的比较,直到找到相等的才算完事。

这个方法,说实话也太笨了,简直不是我这种懒人应该做的事。

就不能有种方法直接看到这个数,就直接在数组中查到位置嘛?!

461cdd85068f027c0ffb3dbc76141fe1.jpeg

诶,你别说,还真有。

因为总有比我更懒的,蛋蛋懒是只能躺着,人家大佬懒是直接动手解决,果然那句”懒是第一生产力“,诚不我欺!

这个就是我今天要给小婊贝们讲的:哈希表

0eb80770b95a465489d125ecaae28f84.png

ea9cd8bb1793fc015953926dbd1c6670.png

   哈希表

哈希表,别名儿叫散列表,洋名儿叫 Hash Table。

我在上面讲,希望有种方法,直接看到数,就知道它在数组中的位置,其实里就用到了哈希思想。

哈希思想就是说不用一些无用的比较,直接可以通过关键字 key 就能找到它的存储位置。

我来举个场景,可能更清楚点:

狗蛋班有 40 个学生,每个学生的学号由入学年份 + 年级 + 班级 + 编号组成,例如 2021210129 是 2021 年入学的 21 级 01 班的 29 号同学。

现在有个需求:我们需要快速找到某个同学的个人信息。

那这个时候我们就要建立一张表,按理来说我要是想要知道某个同学的个人信息,其实就知道学号就好了,但是在这不行,学号的值实在太大了,我们不能把学号当作下标。

学号不可以,那啥可以?我们定睛一看,咦,编号可以呀,编号是从 1 ~ 40。

那咋取到编号?不就是学号对 2021210100 取余就 ok 了嘛。

此时,如果我们想查学号为 2021210129 的学生个人信息,只要访问下标为 29 的数据即可。

其实这就可以在时间复杂度为 O(1) 内解决找到。

秒男实锤。

ac5bc21067255fb06c9b54553d984347.jpeg

用公式表示就是:存储位置 = f(关键字)

这里的 f 又叫做哈希函数,每个 key 被对应到 0 ~ N-1 的范围内,并且放在合适的位置。

在上面的例子中 f(key) = key % 2021210100。

986079f8fcbdd195d74f171b96ec787b.png

存储时,通过同一个哈希函数的计算 key 的哈希地址,并按照此哈希地址存储该 key。

最后形成的表就是哈希表,它主要是面向查找的存储结构,简化了比较的过程,提高了效率。

90778100a55f1ca5b014a1a64868e623.jpeg

   哈希示例

上面看明白的话,那再举个小例子加深点印象。

有个 n = 10 的数组,哈希函数 f(key) = key % 10,将 4,10,11,19,29,39 散列到数组中。

哈希函数确定后,剩下的就是计算关键字对应的存储位置。

1. 4 % 10 = 4,所以将 4 放入下标为 4 的位置。

6cedb43a6e44c1e5411cc85f4f6d05d8.png

2. 10 % 10 = 0,所以将 10 放入下标为 0 的位置。

3e6013996785bf5656e08133c3ead811.png

3. 11 % 10 = 1,所以将 11 放入下标为 1 的位置。

bbe485db4ef086c1f74e9246d7021b6b.png

4. 19 % 10 = 9,所以将 19 放入下标为 9 的位置。

3751c5e083023dc0663776a8259d8a04.png

5. 29 % 10 = 9,所以将 29 放入下标为 9 的位置。

但是现在问题来了,下标为 9 的这个坑已经被 19 占了,这 29 计算出来的下标冲突了。

这种情况有个学名,叫:哈希(散列)冲突

   处理哈希冲突

对于两个不相等的关键字 key1 和 key2,若 f(key1) = f(key2),这就是哈希冲突。

key1 占了坑,那 key2 只能想别的办法,啥办法呢?

一般处理哈希冲突有以下几种办法:

  • 开放定址法

  • 再哈希(散列)函数法

  • 链地址法

  • 。。。

开放定址法

开放定址法就是:一旦发生冲突,就选择另外一个可用的位置。

开放定址法有 2 个最常用的策略:

  • 线性探测法

  • 二次探测法

线性探测法

线性探测法,顾名思义,直来直去的探测。

且看它的公式:

f(key) = (f(key) + di) % m (di = 1, 2, 3, ... , m-1)。

我还是用“哈希示例”中的例子:

n = 10 的数组,哈希函数 f(key) = key % 10,将 4,10,11,19,29,39 散列到数组中。

3f3053011eaf6f8d48b4df36703fd12e.png

到了 29 的时候,29 % 10 = 9。

但此时下标 9 已经有了元素 19,所以此时探测下一个位置 (9 + 1) % 10 = 0。

下标为 0 的位置上已经有了元素 10,所以继续探测下一个位置 (9 + 2) % 10 = 1。

下标为 1 的位置上也有了元素 11,所以还得继续探测下一个位置 (9 + 3) % 10 = 2。

下标为 2 的位置上总算是空的,因此 29 找到了家:

9cda7de1b87c829a773e61d50bd3f24b.png

不知道你发现了没,对于 29 这个来说,本来只是和 19 冲突,整着整着和 10,11 也冲突了。

这样就使得每次要处理好几次冲突,而且这样会出现大量数字聚集在一个区域的情况,大大降低了插入和查找的效率。

后来不知道哪个大佬在线性的基础上做了改进,捣鼓出二次探测法。

二次探测法

二次探测法就是把之前的 di 整成了平方,公式如下:

f(key) = (f(key) + di) % m (di = 1², -1², 2², -2², ..., q², -q², q ≤ m/2)

比如对于 29 来说,下标为 9 的位置上呆了19,所以此时探测下一个位置 (9 + 1²) % 10 = 0。

下标为 0 的位置上占了元素 10,那下一次就探测上一个位置 (9 - 1²) % 10 = 8。

下标为 8 的位置上空着,直接占住:

1b9149b8a66776346acc1d7fcbd2364a.png

再哈希(散列)函数法

再哈希的话,就是不只是一个哈希函数,而是使用一组哈希函数 f1(key)、f2(key)、f3(key)....。

当使用第一个哈希函数计算到的存储位置被占了,那就用第二个哈希函数计算,反正总会有一个散列函数能把冲突解决掉。

依次类推,直到找到空的位置,然后占住。

当然这种方法不会出现大量数字聚集在一个区域的情况,但这种情况明显增加了计算的时间。

链地址法

是谁说出现冲突后只能找别的坑位的,几个人蹲一个坑它不香嘛。

650c22d7e714254f0167ce128eb18247.jpeg

可能真的有巨佬觉得香,然后就整出了链地址法。

链地址法呢是将得出同一个结果的 key 放在一个单链表中,哈希表存储每条单链表的头指针。

还是用老例子:n = 10 的数组,哈希函数 f(key) = key % 10,将 4,10,11,19,29,39 散列。

最后得到如下图:

2f6ca552c7ff4786cfed34f8711a783f.png

你看,链地址法就不会出现此坑被占,就得去找别的坑的情况。

大家一起蹲,这样就绝不会出现找不到地址的情况,而是直接插到对应的单链表中即可,所以插入的时间复杂度为 O(1)

当然有得必有失,这样的情况必然造成了查找上的性能损耗,这里的查找的时间复杂度为 O(k),其中 k = n / 单链表条数

239aeb41e16919d92b9e8e78a1a93059.png

好啦,到这里哈希表就讲完辣,是不是看起来还挺简单的。

哈希表作为非常高高高高高效的查找数据结构,丢掉了关键字之间反复无意义的比较,直接一步到位查找结果,非常顶。

这么看来它处理冲突啥的这点屁事就显得不是那么烦人了,毕竟有得有失才对嘛。

希望小婊贝们看完就能学会,如果有什么问题我们留言区见。

顺便大家不要忘记我的赞在看呀,么么哒。

我是 Rocky0429,我们下次见~

推荐阅读 👍:ACM 选手带你玩转时间复杂度和空间复杂度

推荐阅读 👍:ACM 选手带你玩转数组

推荐阅读 👍:ACM 选手带你玩转链表

推荐阅读 👍:ACM 选手带你玩转栈和队列

推荐阅读 👍:ACM 选手带你玩转字符串

这篇关于ACM 选手带你玩转哈希表!的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/704580

相关文章

哈希leetcode-1

目录 1前言 2.例题  2.1两数之和 2.2判断是否互为字符重排 2.3存在重复元素1 2.4存在重复元素2 2.5字母异位词分组 1前言 哈希表主要是适合于快速查找某个元素(O(1)) 当我们要频繁的查找某个元素,第一哈希表O(1),第二,二分O(log n) 一般可以分为语言自带的容器哈希和用数组模拟的简易哈希。 最简单的比如数组模拟字符存储,只要开26个c

认识、理解、分类——acm之搜索

普通搜索方法有两种:1、广度优先搜索;2、深度优先搜索; 更多搜索方法: 3、双向广度优先搜索; 4、启发式搜索(包括A*算法等); 搜索通常会用到的知识点:状态压缩(位压缩,利用hash思想压缩)。

usaco 1.3 Prime Cryptarithm(简单哈希表暴搜剪枝)

思路: 1. 用一个 hash[ ] 数组存放输入的数字,令 hash[ tmp ]=1 。 2. 一个自定义函数 check( ) ,检查各位是否为输入的数字。 3. 暴搜。第一行数从 100到999,第二行数从 10到99。 4. 剪枝。 代码: /*ID: who jayLANG: C++TASK: crypt1*/#include<stdio.h>bool h

哈希表的底层实现(1)---C++版

目录 哈希表的基本原理 哈希表的优点 哈希表的缺点 应用场景 闭散列法 开散列法 开放定值法Open Addressing——线性探测的模拟实现 超大重点部分评析 链地址法Separate Chaining——哈希桶的模拟实现 哈希表(Hash Table)是一种数据结构,它通过将键(Key)映射到值(Value)的方式来实现快速的数据存储与查找。哈希表的核心概念是哈希

哈希表的封装和位图

文章目录 2 封装2.1 基础框架2.2 迭代器(1)2.3 迭代器(2) 3. 位图3.1 问题引入3.2 左移和右移?3.3 位图的实现3.4 位图的题目3.5 位图的应用 2 封装 2.1 基础框架 文章 有了前面map和set封装的经验,容易写出下面的代码 // UnorderedSet.h#pragma once#include "HashTable.h"

秒变高手:玩转CentOS 7软件更换的方法大全

在 CentOS 7 中更换软件源可以通过以下步骤完成。更换源可以加快软件包的下载速度,特别是当默认源速度较慢时。以下是详细步骤: 前言 为了帮助您解决在使用CentOS 7安装不了软件速度慢的问题,我们推出了这份由浪浪云赞助的教程——“CentOS7如何更换软件源加快下载速度”。 浪浪云,以他们卓越的弹性计算、云存储和网络服务受到广泛好评,他们的支持和帮助使得我们可以将最前沿的技术知识分

【408数据结构】散列 (哈希)知识点集合复习考点题目

苏泽  “弃工从研”的路上很孤独,于是我记下了些许笔记相伴,希望能够帮助到大家    知识点 1. 散列查找 散列查找是一种高效的查找方法,它通过散列函数将关键字映射到数组的一个位置,从而实现快速查找。这种方法的时间复杂度平均为(

PHP: 深入了解一致性哈希

前言 随着memcache、redis以及其它一些内存K/V数据库的流行,一致性哈希也越来越被开发者所了解。因为这些内存K/V数据库大多不提供分布式支持(本文以redis为例),所以如果要提供多台redis server来提供服务的话,就需要解决如何将数据分散到redis server,并且在增减redis server时如何最大化的不令数据重新分布,这将是本文讨论的范畴。 取模算法 取模运

哈希表题总结

哈希表题总结 hot100两数之和字母异位词分组最长连续序列 hot100 两数之和 题目链接: 1.两数之和 代码: class Solution {public int[] twoSum(int[] nums, int target) {Map<Integer,Integer> map = new HashMap<>();int n = nums.length;for

【吊打面试官系列-Redis面试题】说说 Redis 哈希槽的概念?

大家好,我是锋哥。今天分享关于 【说说 Redis 哈希槽的概念?】面试题,希望对大家有帮助; 说说 Redis 哈希槽的概念? Redis 集群没有使用一致性 hash,而是引入了哈希槽的概念,Redis 集群有 16384 个哈希槽,每个 key 通过 CRC16 校验后对 16384 取模来决定放置哪个槽, 集群的每个节点负责一部分 hash 槽。