联系hashgameCONTACT hashgame
地址:广东省广州市
手机:13988889999
电话:020-88889999
邮箱:admin@qq.com
查看更多
Rhashgamehashgame
你的位置: 首页 > hashgame

HASH GAME - Online Skill Game ET 300详解数据结构之散列(哈希)表pdf

发布时间:2025-01-24 22:31:48  点击量:

  HASH GAME - Online Skill Game GET 300

HASH GAME - Online Skill Game GET 300详解数据结构之散列(哈希)表pdf

  详解数据结构之散列(哈希)表 1.散列表查找步骤 散列表,最有用的基本数据结构之一。是根据关键码的值直接进行 访问的数据结构,散列表的实现常常叫做散列(hasing )。散列是一种 用于以常数平均时间执行插入、删除和查找的技术 ,下面我们来看一下 散列过程 。我们的整个散列过程主要分为两步 : 1. 通过散列函数计算记录的散列地址,并按此散列地址存储该记录。 就好比麻辣鱼,我们就让它在川菜区,糖醋鱼,我们就让它在鲁菜 区。但是我们需要注意的是,无论什么记录我们都需要用同一个散 列函数计算地址,然后再存储 。 2. 当我们查找时,我们通过同样的散列函数计算记录的散列地址,按 此散列地址访问该记录。因为我们存和取的时候用的都是一个散列 函数,因此结果肯定相同。 刚才我们在散列过程中提到了散列函数,那么散列函数是什么呢 ? 我们假设某个函数为 f,使得存储位置 = f (key) ,那样我们就能通 过查找关键字不需要比较就可获得需要的记录的存储位置 。这种存储技 术被称为散列技术 。散列技术是在通过记录的存储位置和它的关键字之 间建立一个确定的对应关系 f ,使得每个关键字 key 都对应一个存储 位置 f(key)。见下图 这里的 f 就是我们所说的散列函数(哈希)函数。我们利用散列技 术将记录存储在一块连续的存储空间中,这块连续存储空间就是我们本 文的主人公散列 (哈希) 上图为我们描述了用散列函数将关键字映射到散列表 。 但是大家有没有考虑到这种情况,那就是将关键字映射到同一个槽 中的情况,即 f(k4) = f(k3) 时。这种情况我们将其称之为冲突,k3 和 k4 则被称之为散列函数 f 的同义词,如果产生这种情况,则会让我 们查找错误 。 幸运的是我们能找到有效的方法解决冲突 。 首先我们可以对哈希函数下手,我们可以精 心 设计哈希函数,让其 尽 可能少 的产生冲突,所以我们创 建哈希函数时应遵 循 以下规 则 : 必 须 是一致 的。 1. 假设你 输 入辣子 鸡 丁 时得到的是在看,那么每次 输 入辣子 鸡 丁 时,得到的也 必 须 为在看。如果不是这样,散列表将毫 无用处 。 计算简 单 。 2. 假设我们设计了一个算法,可以保 证 所有关键字都不会 冲突,但是这个算法计算复 杂 ,会耗 费 很 多 时间,这样的话 就大大 降 低 了查找效率 ,反 而 得不偿 失 。所以咱们散列函数的计算时间 不应该超 过其他 查找技术与 关键字的比较时间,不然的话 我们干 嘛 不使用其他 查找技术呢? 3. 散列地址分布 均匀 我们刚才说了冲突的带 来的问题 ,所以我们最好 的办 法就是让散列地址尽 量 均匀 分布 在存储空间中 ,这样即保 证 空间的有效利用,又 减 少 了处 理 冲突而 消 耗 的时间。 现在我们 已 经对散列表,散列函数等 知 识 有所了解啦 ,那么我们来 看几 种常用的散列函数构造 方法 。这些 方法的共 同点 为都是将原 来的数 字按某种规 律 变 成 了另 一个数字。所以是很 容 易 理 解的 。 2 .散列函数构造方 法 2.1 直接定址 法 如果我们对盈 利为 0-9 的菜品设计哈希表,我们则直接可以根据作 为地址,则 f(key) = key; 即下面这种情况: 有没有感 觉 上面的图很 熟 悉 ,没错我们经 常用的数组 其实就是一张 哈希表,关键码就是数组 的索 引 下标 ,然后我们通过下标 直接访问数组 中的元 素 。 另 外 我们假设每道 菜的成 本为 50 块,那我们还 可以根据盈 利+成 本 来作 为地址,那么则 f(key) = key + 50 。也 就是说我们可以根据线 性 函数值作 为散列地址 。 f(key) = a * key + b a,b 均为常数 优 点 :简 单 、均匀 、无冲突。 应用场 景 :需要事 先知 道 关键字的分布 情况,适 合 查找表较小 且 连 续的情况 2.2 数字分析 法 该方法也 是十 分简 单 的方法。就是分析 我们的关键字,取其中一 段 ,或 对其位移 ,叠 加 ,用作 地址。 比如我们的学 号 ,前 6 位都是一样 的,但是后面 3 位都不相同,我们则可以用学 号 作 为键,后面的 3 位 做为我们的散列地址。如果我们这样还 是容 易 产生冲突,则可以对抽 取 数字再进行处 理 。我们的 目的只有一个,提供 一个散列函数将关键字合 理 地分配 到散列表的各 位置。这里我们提到了一种新 的方式 抽 取,这也 是在散列函数中经 常用到的手段 。 优 点 :简 单 、均匀 、适 用于关键字位数较大的情况 应用场 景 :关键字位数较大,知 道 关键字分布 情况且 关键字的若 干 位较均匀 2.3 折叠 法 其实这个方法也 很 简 单 ,也 是处 理 我们的关键字然后用作 我们的散 列地址 ,主要思 路 是将关键字从 左 到右 分割 成 位数相等 的几 部 分,然后 叠 加 求 和,并按散列表表长 ,取后几 位作 为散列地址 。 比如我们的关键字是 123456789 ,则我们分为三 部 分 123 ,456 , 789 然后将其相加 得 1368 然后我们再取其后三 位 368 作 为我们的散列 地址 。 优 点 :事 先不需要知 道 关键字情况 应用场 景 :适 合 关键字位数较多 的情况 2.4 除法散列 法 在用来设计散列函数的除法散列法中,通过取 key 除以 p 的余 数,将关键字映射到 p 个槽中的某一个上 ,对于散列表长 度 为 m 的散 列函数公式 为 f(k) = k mod p (p = m ) 例 如,如果散列表长 度 为 12,即 m = 12 ,我们的参 数 p 也 设为 12,那 k = 100 时 f(k) = 100 % 12 = 4 由于 只 需要做一次 除法操 作 ,所 以除法散列法是非 常快 的 。 由上面的公式 可以看 出 ,该方法的重 点 在于 p 的取值,如果 p 值 选 的不好,就可能会容 易 产生同义词。见下面这种情况。我们哈希表长 度 为 6 ,我们选 择 6 为 p 值,则有可能产生这种情况,所有关键字都得 到了 0 这个地址数 。 那我们在选 用除法散列法时选 取 p 值时应该遵 循 怎 样的规 则呢 ? • m 不应为 2 的幂 ,因为如果 m = 2^p ,则 f(k) 就是 k 的 p 个 最低 位数字。例 12 % 8 = 4 ,12 的二 进制 表示 位 1100,后三 位为 100 。 • 若 散列表长 为 m ,通常 p 为 小 于或 等 于表长 (最好接近 m )的最 小 质 数或 不包 含 小 于 20 质 因子 的合 数 。 合 数:合 数是指 在大于 1 的整数中除了能被 1 和本身 整除外 ,还 能被其 他 数(0 除外 )整除的数。 质 因子 :质 因子 (或 质 因数)在数论里是指 能整除给 定正 整数的质 数 。 注:这里的 2 、3、5 为质 因子 还 是上面的例 子 ,我们根据规 则选 择 5 为 p 值,我们再来看。这 时我们发 现 只有 6 和 36 冲突,相对来说就好了很 多 。 优 点 :计算效率 高 ,灵 活 应用场 景 :不知 道 关键字分布 情况 2.5 乘法散列 法 构造 散列函数的乘 法散列法主要包 含 两个步骤 • 用关键字 k 乘 上常数 A(0 A 1) ,并提取 k A 的小 数部 分 • 用 m 乘 以这个值,再向下取整 散列函数为 f (k) = ⌊ m (kA mod 1) ⌋ 这里的 kA mod 1 的含 义是取 keyA 的小 数部 分,即 kA - ⌊kA⌋ 。 优 点 :对 m 的选 择 不是特 别 关键一般 选 择 它为 2 的某个幂 次 (m = 2 ^ p ,p 为某个整数 ) 应用场 景 :不知 道 关键字情况 2.6 平方取中 法 这个方法就比较简 单 了,假设关键字是 321 ,那么他 的平方就是 103041,再抽 取中间的 3 位就是 030 或 304 用作 散列地址。再比如关 键字是 1234 那么它的平方就是 1522756 ,抽 取中间 3 位就是 227 用作 散列地址 。 优 点 :灵 活 ,适 用范 围 广 泛 适 用场 景 :不知 道 关键字分布 ,而 位数又 不是很 大的情况 。 2.7 随机数 法 顾 名 思 义,取关键字的 随 机 函数值为它的散列地址 。也 就是 f(key) = random (key)。这里的 random 是 随 机 函数。(具 体 解析 见 随 机 探 测 法 ) 适 用场 景 :关键字的长 度 不等 时 上面我们的例 子 都是通过数字进行举 例 ,那么如果是字符 串 可不可 以作 为键呢?当然也 是可以的,各 种各 样的符 号 我们都可以转 换 成 某种 数字来对待 ,比如我们经 常接触 的 ASCII 码 ,所以是同样适 用的 。 以上就是常用的散列函数构造 方法,其实他 们的中心 思 想 是一致 的,将关键字经 过加 工 处 理 之后变 成 另 外 一个数字,而 这个数字就是我 们的存储位置 ,是不是有一种间谍 传 递 情报 的感 觉 。 一个好的哈希函数可以帮 助 我们尽 可能少 的产生冲突,但是也 不能 完 全 避 免 产生冲突,那么遇 到冲突时应该怎 么做呢?下面给 大家带 来几 种常用的处 理 散列冲突的方法 。 3 .处理散列冲突的方 法 我们在使用 hash 函数之后发 现关键字 key1 不等 于 key2 ,但是 f(key1) = f(key2),即有冲突,那么该怎 么办 呢?不急 我们慢 慢 往 下 看 。 3.1 开放地址 法 开 放 地址法就是一旦 发 生冲突,就去 寻 找下一个空的散列地址 。只 要列表足 够 大,空的散列地址总 能找到,并将记录存入。为了使用开 放 寻 址法插入一个元 素 ,需要连续地检 查散列表,或 称为探 查,我们常用 的有线 性 探 测 、二 次 探 测 和 随 机 探 测 。 线 性 探 测 法 下面我们先来看一下线 性 探 测 ,公式 : 我们来看一个例 子 ,我们的关键字集 合 为 {12,67 ,56 ,16,25 , 37 ,22 ,29 ,15,47 ,48 ,21 } ,表长 为 12,我们再用散列函数 f(key) = key mod 12 。 我们求 出 每个 key 的 f(key)见下表 我们查看上表发 现,前 五 位的 f(key) 都不相同,即没有冲突,可 以直接存入,但是到了第 六 位 f(37) = f(25) = 1,那我们就需要利用 上面的公式 f(37) = f (f(37) + 1 ) mod 12 = 2 ,这其实就是我们 的订 包 间的做法。下面我们看一下将上面的所有数存入哈希表是什么情 况 吧 。 注: 蓝 色 为计算哈希值,红 色 为存入哈希表 这种解决冲突的开 放 地址法称为线 性 探 测 法 。 另 外 我们在解决冲突的时候,会遇 到 48 和 37 虽 然不是同义词, 却 争 夺 一个地址的情况,我们称其为堆 积 。因为堆 积 使得我们需要不断 的处 理 冲突,插入和查找效率 都会大大 降 低 。 考虑一下这种情况,若 是我们的最后一位不为 21 ,为 34 时会有什 么事 情发 生呢? 此时他 第 一次 会落 在下标 为 10 的位置,那么如果继 续使用线 性 探 测 的话 ,则需要通过不断 取余 后得到结果,数据量 小 还 好,要是很 大的 话 那也 太 慢 了吧 。但是明 明他 的前 面就有一个空房 间呀 ,如果向 前 移动 只 需移 动 一次 即可。不要着 急 ,前 辈 们 已 经 帮我们想 好了解决方法。 二 次 探 测 法 其实理 解了我们的上个例 子 之后,这个一下就能整 明 白 了,根本不 用 费脑 子 ,这个方法就是更 改 了一下 d i 的取值 注:这里的是 -1^2 为负 值 而 不是 (-1)^2 所以对于我们的 34 来 说,当 d i = -1 时,就可以找到空位置了 。 二 次 探 测 法的 目的就是为了不让关键字聚 集 在某一块区域 。另 外 还 有一种有趣 的方法,位移 量 采 用 随 机 函数计算得到 随 机 探 测法 大家看到这是不又 有新 问题 了,刚才我们在散列函数构造 规 则的第 一条 中说: • 必 须 是一致 的,假设你 输 入辣子 鸡 丁 时得到的是在看,那么每 次 输 入辣子 鸡 丁 时,得到的也 必 须 为在看。如果不是这样,散 列表将毫 无用处 。 那么问题 来了,我们使用 随 机数作 为他 的偏 移 量 ,那么我们查找的 时候 岂不是查不到了?因为我们 di 是 随 机 生成 的呀 ,这里的随 机其实 是伪 随 机数。伪 随 机数含 义为,我们设置 随 机种子 相同,则不 断 调 用 随 机 函数可以生成 不会重 复 的数列,我们在查找时,用同样的随 机种子 , 它每次 得到的数列是相同的,那么相同的 di 就能得到相同的散列地址 。 随 机种子 (Random Seed )是计算机 专 业 术语 ,一种以随 机数作 为对 象 的以真 随 机数(种子 )为初 始 条 件 的随 机数。一般 计算机 的随 机数都是 伪 随 机数,以一个真 随 机数(种子 )作 为初 始 条 件 ,然后用一定的算法不 停 迭 代 产生 随 机数 通过上面的测 试 是不是一下就秒 懂 啦 ,使用相同的随 机种子 ,生成 的数列是相同的。所以为什么我们可以使用随 机数作 为它的偏 移 量 。下 面我们再来看一下其他 的函数处 理 散列冲突的方法。 再哈希法 这个方法其实也 特 别 简 单 ,利用不同的哈希函数再求 得一个哈希地 址,直到不 出现冲突为止 。 f, (key) = RH , ( key ) (i = 1,2,3,4… ..k) 这里的 RH ,就是不同的散列函数,你 可以把 我们之前 说过的那些 散 列函数都用上,每当发 生冲突时就换 一个散列函数,相信 总 有一个能够 解决冲突的 。这种方法能使关键字不产生 聚 集 ,但是代 价 就是增 加 了计 算时间 。 链 地址法 key 不同 f(key) 相同的情况,我们将这些 同义词存储在一个单 链 表中,这种表叫做 同义词子 表 ,散列表中只存储同义词子 表的头 指 针 。 我们还 是用刚才的例 子 ,关键字集 合 为 {12,67 ,56 ,16,25 ,37 ,22 , 29 ,15,47 ,48 ,21 } ,表长 为 12,我们再用散列函数 f(key) = key mod 12 。 我们用了链 地址法之后就再也 不存在冲突了,无论有多 少 冲突,我 们 只 需在同义词子 表中添 加 结点 即可。下面我们看下链 地址法的存储情 况。 链 地址法 虽 然能不产生冲突,但是也 带 来了查找时需要遍 历 单 链 表 的性 能消 耗 。 公共 溢 出 区法 冲突的各 位我先给 你 安 排 个地方呆 着 ,这样你 就有地方住 了。我们 为所有冲突的关键字建立了一个公共 的溢 出 区来存放 。 那么我们怎 么进行查找呢?我们首先通过散列函数计算 出散列地址 后,先于基本表对比,如果不相等 再到溢 出表去 顺 序 查找。 这种解决冲 突的方法,对于冲突很 少 的情况性 能还 是非 常高 的。 散列表查找算法 线 . ( ) 首先需要定义散列列表的结构以及 一些 相关常数,其中 elem 代 表 散列表数据存储数组 ,count 代 表的是当前 插入元 素 个数, size 代 表哈 希表容 量 ,NULLKEY 散列表初 始 值,然后我们如果查找成 功 就返 回 索 引 , 如果不存在该元 素 就返 回 元 素 不存在。我们将哈希表初 始 化 ,为数组 元 素 赋 初 值。 插入操 作 的具 体 步骤 : 1. 通过哈希函数( 除法散列法) ,将 key 转 化 为数组 下标 ; 2. 如果该下标 中没有元 素 ,则插入,否 则说 明有冲突,则利用线 性 探 测 法处 理 冲突。详 细 步骤 见注释 查找操 作 的具 体 步骤 : 1. 通过哈希函数(同插入时一样),将 key 转 化 成 数组 下标 2. 通过数组 下标 找到 key 值,如果 key 一致 ,则查找成 功 ,否 则 利用线 性 探 测 法继 续查找 。 下面我们来看一下完 整代 码

【返回列表页】

顶部

地址:广东省广州市  电话:020-88889999 手机:13988889999
Copyright © 2018-2025 哈希游戏(hash game)官方网站 版权所有 非商用版本 ICP备案编: