数据结构与算法Python版 散列函数设计与冲突解决方案

server/2024/12/23 10:00:13/

文章目录

  • 一、散列函数设计
  • 二、冲突解决方案
  • 三、映射


一、散列函数设计

散列函数设计-折叠法

  • 基本步骤:将数据项按照位数分为若干段,再将几段数字相加,最后对散列表大小求余,得到散列值
  • 例如:电话号码62767255,可以两位两位分为4段(62、76、72、55),相加(62+76+72+55=265),散列表包括11个槽,那么就是265%11=1,所以h(62767255)=1
  • 有时候折叠法还会包括一个隔数反转的步骤,作为一种微调手段。比如(62、76、72、55)隔数反转为(62、67、72、55)
python">def remainder(lst, table_size):"""散列函数设计-折叠法"""# 每两位一组grouped_lst = [lst[i : i + 2] for i in range(0, len(lst), 2)]# 求和total = sum([int(i) for i in grouped_lst])# 求余return total % table_sizetest_lst = "62767255"
print(remainder(test_lst, 11))### 输出结果
1

散列函数设计-平方取中法

  • 基本步骤:首先将数据项做平方运算,然后取平方数的中间两位,再对散列表的大小求余
  • 例如:对44进行散列,首先44*44=1936,然后取中间的93,对散列表大小11求余,93%11=5
def mid_square(num, table_size):"""散列函数设计-平方取中法"""num_square = num**2s = str(num_square)mid = len(s) // 2# 取中间两位mid_s = s[mid - 1 : mid + 1]return int(mid_s) % table_sizetest_num = 44
print(mid_square(test_num, 11))### 输出结果
5

散列函数设计-非数项

  • 基本步骤:把字符串中的每个字符看作ASCII码,再将这些整数累加,对散列表大小求余
  • 例如:cat,ord(‘c’)==99, ord(‘a’)==96, ord(‘t’)==116,和为312,结果312%11=4
python">def str_hash(s, table_size):total = sum([ord(i) for i in s])return total % table_sizetest_str = "cat"
print(str_hash(test_str, 11))### 输出结果
4

散列函数设计原则

  • 散列函数不能成为存储过程和查找过程的计算负担

二、冲突解决方案

冲突解决方案-开放定址Open Addressing

  • 如果两个数据项被散列映射到同一个槽,需要一个系统化的方法散列表中保存第二个数据项,这个过程称为“解决冲突”
  • 基本步骤:当发生冲突时,从冲突的槽开始往后扫描,直到碰到一个空槽,然后把冲突的数据项保存到该空槽。
  • 这种寻找空槽的技术称为“开放定址open addressing”。向后逐个槽寻找的方法则是开放定址技术中的**“线性探测linear probing“**
  • 采用线性探测方法来解决散列冲突的话,则散列表的查找也遵循同样的规则。如果在散列位置没有找到查找项的话,就必须向后做顺序查找。直到找到查找项,或者碰到空槽(查找失败)。

示例:把44、55、20逐个插入到散列表中。h(44)=0,0#槽已被占据,向后找到并放到1#槽; h(55)=0同样;h(20)=9,向后,再从头开始找到3#槽保存。

在这里插入图片描述

冲突解决方案- 线性探测的改进

  • 线性探测法的一个缺点是有 聚 集 (clustering)的趋势。即如果同一个槽冲突的数据项较多的话,这些数据项就会在槽附近聚集起来,从而连锁式影响其它数据项的插入。
  • 改进方法一:从逐个探测改为跳跃式探测。例如以+3的步长向后寻找空槽。
  • 改进方法二:再散列rehashing。跳跃式探测的再散列通式是:rehash(pos)= (pos+skip)% sizeoftable。注意skip的取值,不能被散列表大小整除。
  • 改进方法三:二次探测quadratic probing。不再固定skip的值,而是逐步增加skip值,如1、3、5、7、9。

冲突解决方案- 数据项链Chaining

  • 将容纳单个数据项的槽扩展为容纳数据项集合(或者对数据项链表的引用)。
  • 散列表中的每个槽可以容纳多个数据项,如果有散列冲突发生,只需要简单地将数据项添加到数据项集合中。查找数据项时则需要查找同一个槽中的整个集合。

在这里插入图片描述

三、映射

映射 Map

  • 映射是键key-值data关联的无序集合,关键码key具有唯一性,通过关键码key可以唯一确定一个数据值
  • 使用映射的优势在于,给定关键码key,能够很快得到关联的数据值data。为了达到快速查找的目标,使用散列表来实现,这样查找可以达到最快O(1)的性能

抽象数据类型-映射 Map

方法/操作描述
Map()创建一个空映射,返回空映射对象
put(key, val)将key-val关联对加入映射中,如果key已存在,将val替换旧关联值。加入或更新成功返回True,否则返回False
get(key)给定key,返回关联的数据值,如不存在,则返回None
del通过del map[key]的语句形式删除key-val关联
len()返回映射中key-val关联的数目
in通过key in map的语句形式,返回key是否存在于关联中,布尔值

Python实现抽象数据类型-映射

  • 散列表的大小,虽然可以是任意数,但考虑到要让冲突解决算法能有效工作,应该选择为素数。
python">class MyMap:def __init__(self, size=11):self.size = size  # 整个散列表的大小# 初始化slots列表用于保存keyself.slots = [None] * self.size# 初始化data列表用于保存数据项self.data = [None] * self.sizedef put(self, key, val):hash_val = self.hash_func(key)# 槽位为空if self.slots[hash_val] == None:self.slots[hash_val] = keyself.data[hash_val] = valreturn True# key已存在,更新valif self.slots[hash_val] == key:self.data[hash_val] = valreturn Trueposition = hash_valwhile True:# 再散列向后查找position = self.re_hash(position)# 槽位为空if self.slots[position] == None:self.slots[position] = keyself.data[position] = valreturn True# key已存在,更新valif self.slots[position] == key:self.data[position] = valreturn True# 找了一圈,没找到key或空的槽位if hash_val == position:return Falsedef get(self, key):start_slot = self.hash_func(key)# 没有冲突并且找到if self.slots[start_slot] == key:return self.data[start_slot]position = start_slotwhile True:# 再散列向后查找position = self.re_hash(position)# 遇到空的槽位或已经找了一圈,查找失败返回Noneif self.slots[position] == None or position == start_slot:return None# 找到返回if self.slots[position] == key:return self.data[position]def len(self):"""数据项的数量"""count = 0for i in self.slots:if i != None:count += 1return countdef hash_func(self, key):"""散列函数:采用简单求余方法"""return key % self.sizedef re_hash(self, old_hash):"""再散列函数:冲突解决则采用线性探测“加1”"""return (old_hash + 1) % self.size# 通过特殊方法实现[]访问def __getitem__(self, key):return self.get(key)def __setitem__(self, key, val):return self.put(key, val)

示例:使用上述定义的映射数据类型,进行测试并输出结果

python">h = MyMap()
h[54] = "cat"
h[26] = "dog"
h[93] = "lion"
h[17] = "tiger"
h[77] = "bird"
h[31] = "cow"
h[44] = "goat"
h[55] = "pig"
h[20] = "chicken"
print(h.len())
print(h.slots)
print(h.data)
print(h[20])
print(h[17])
h[20] = "duck"
print(h[20])
print(h[99])### 输出结果
9
[77, 44, 55, 20, 26, 93, 17, None, None, 31, 54]
['bird', 'goat', 'pig', 'chicken', 'dog', 'lion', 'tiger', None, None, 'cow', 'cat']
chicken
tiger
duck
None

映射-算法分析

  • 在最好的情况下,可以提供O(1)常数级时间复杂度的查找性能。其它情况下,需要使用负载因子λ进行评估。如果λ较小,散列冲突的几率就小;否则,冲突的几率就大。
    • 采用线性探测的开放定址法来解决冲突,λ在0~1之间:成功的查找,平均需要比对次数为1/2(1+1/(1-λ));不成功的查找,平均比对次数为1/2(1+1/((1-λ)^2))
    • 采用数据链来解决冲突,λ可大于1:成功的查找,平均需要比对次数为1+λ/2;不成功的查找,平均比对次数为λ

您正在阅读的是《数据结构算法Python版》专栏!关注不迷路~


http://www.ppmy.cn/server/152460.html

相关文章

ajax同步执行async:false无效的解决方法

无效的情况: function ManHourCheck() {var StartDate $("#StartDate").val();//日报日期var EndDate $("#EndDate").val();//完成日期var UserID $("#UserID").val();//员工ID$.ajax({async: false,//加了这一行也没用!!!!!!!!!!…

【大语言模型】ACL2024论文-28 TTM-RE: 增强记忆的文档级关系抽取

【大语言模型】ACL2024论文-28 TTM-RE: 增强记忆的文档级关系抽取 目录 文章目录 目录文章信息摘要研究背景问题与挑战如何解决创新点算法模型实验效果推荐阅读指数:★★★★☆ 后记 文章信息 TTM-RE: 增强记忆的文档级关系抽取 摘要 本文提出了TTM-RE&#xff…

面向对象编程:原理、实践与应用

面向对象编程:原理、实践与应用 一、编程范式的演进 (一)从面向过程到面向对象 编程领域的发展见证了编程范式的不断演进,其中面向过程编程和面向对象编程是两种具有重要影响力的范式。面向过程编程以其直观的步骤执行逻辑&…

JDBC 入门教程

Java Database Connectivity (JDBC) 是 Java 平台提供的一种与各种数据库连接的方式和规范。通过 JDBC,开发者可以在 Java 平台上完成数据库的查询、更新和操作。本文将详细认识 JDBC 的基础概念,并通过实战例子介绍其使用方法。 1. JDBC 概念 JDBC 接口…

qt 类中的run线程

在Qt中,QThread类的run()方法是线程的执行入口,它是由QThread内部自动调用的,而不是用户直接调用。 详细解释: QThread类: QThread是Qt的线程类,提供了用于多线程操作的接口。我们可以创建QThread对象并将…

文心一言对接FreeSWITCH实现大模型呼叫中心

文心一言对接FreeSWITCH实现大模型呼叫中心 作者:开源大模型智能呼叫中心FreeIPCC,Github:https://github.com/lihaiya/freeipcc 随着人工智能技术的快速发展,特别是大规模语言模型(LLM)的应用&#xff0…

Chromium GN目标指南 - 查看GN目标(三)

引言 在前面的文章中,我们介绍了 Chromium 构建系统中的 GN 的基本概念、目录结构和常用工具,并通过构建一个简单的 Demo 学习了如何编写和使用 executable 目标。在本篇文章中,我们将学习如何查看和挑选合适的 GN 目标,以便于我…

sql server索引优化语句

第一步 建一个测试表 --create table TestUsers --( -- Id int primary key identity(1,1), -- Username varchar(30) not null, -- Password varchar(10) not null, -- CreateDateTime datetime not null --)第二步 插入100w数据 大概1分钟执行时间 ----插入数据…