python数据结构与算法基础 第七课
tags:
- python
- 路飞学院
categories:
- python
- 数据结构
- 哈希表
- 散列表
文章目录
第一节 哈希表定义
1. 哈希表介绍
- 哈希表又称散列表。python中的集合和字典都是用哈希表实现的。
- 哈希表一个通过哈希函数来计算数据存储位置的数据结构,通常支持如下操作:
- insert(key, value):插入键值对(key,value)
- get(key): 如果存在键为key的键值对则返回其value,否则返回空值
- delete(key): 删除键为key的键值对
- 只有key没有value就是集合
2. 直接寻址表
- 当关键字的全域U比较小时,直接寻址是一种简单而有效的方法。
- 直接寻址技术缺点:
- 当域U很大时,需要消耗大量内存,很不实际
- 如果域U很大而实际出现的key很少,则大量空间被浪费
- 无法处理关键字不是数字的情况(如字符串)
3. 哈希
- 为了解决上述寻址的缺点,加上哈希函数。
- 直接寻址表: key为k的元素放到k位置上
- 改进直接寻址表:哈希(Hashing)
- 构建大小为m的寻址表T
- key为k的元素放到h(k)位置上
- h(k)是一个函数, 其将域U映射到表([0,1…m-1]
4. 哈希表
- 哈希表(Hash Table,又称为散列表)是一种线性表的存储结构。哈希表由一个直接寻址表和一个哈希函数组成。哈希函数h(k)将元素关键字k作为自变量,返回元素的存储下标。
- 假设有一个长度为7的哈希表,哈希函数h(k)=k%7。元素集合{14,22,3,5}的存储方式如下图。
5. 哈希冲突
- 哈希冲突一定会存在。
- 由于哈希表的大小是有限的,而要存储的值的总数量是无限的,因此对于任何哈希函数,都会出现两个不同元素映射到同一个位置.上的情况,这种情况叫做哈希冲突。
- 比如h(k)=k%7, h(0)=h(7)=h(14)=…
- 解决哈希冲突方式一:开放寻址法(这种方法并不好,因为总有满的时候)
- 开放寻址法:如果哈希函数返回的位置已经有值,则可以向后探查新的位置来存储这个值。
- 性探查:如果位置i被占用,则探查i+1, i+2…(效果不好,装载因子太大。太过密集)
- 探查:如果位置i被占用,则探查i+12,i-12,i+22,i-22,
- 二度哈希:有n个哈希函数,当使用第1个哈希函数h1发生冲突时,则尝试使用h2,h3…
- 开放寻址法会给查找造成一定麻烦,先查找该放位置,超后面找一直找到空位置。就可以确定该元素在不在。
- 解决哈希冲突方式一:拉链法
- 拉链法:哈希表每个位置都连接一个链表,当冲突发生时,冲突的元素将被加到该位置链表的最后。
- 链表的删除和插入也非常方便
- 查询速度其实和开放寻址法差不多的(肯定没有直接寻址法快)
- 查询速度和哈希函数的设计有关
- 常见的哈希函数
- 除法哈希法:h(k)=k % m
- 乘法哈希法:h(k) = floor(m*(A*key%1))
- 全域哈希法:ha,b(k) = ((a*key + b) mod p) mod m a,b=1,2,…p-1
第二节 哈希表实现
1. 实现一个链表
- 实现链表linklist.py.
class LinkList:
class Node:
def __init__(self, item=None):
self.item = item
self.next = None
class LinkListIterator:
def __init__(self, node):
self.node = node
def __next__(self):
if self.node:
cur_node = self.node
self.node = cur_node.next
return cur_node.item
else:
raise StopIteration
def __iter__(self):
return self
def __init__(self, iterable=None):
self.head = None
self.tail = None
if iterable:
self.extend(iterable)
def append(self, obj):
s = LinkList.Node(obj)
if not self.head:
self.head = s
self.tail = s
else:
self.tail.next = s
self.tail = s
def extend(self, iterable):
for obj in iterable:
self.append(obj)
def find(self, obj):
for n in self:
if n == obj:
return True
else:
return False
# 让类变成一个迭代器类,可以用for
def __iter__(self):
return self.LinkListIterator(self.head)
def __repr__(self):
return "<<"+",".join(map(str, self))+">>"
lk = LinkList([1, 2, 3, 4, 5])
print(lk.find(1))
print(lk)
for element in lk:
print(element)
2. 实现哈希表
class LinkList:
class Node:
def __init__(self, item=None):
self.item = item
self.next = None
class LinkListIterator:
def __init__(self, node):
self.node = node
def __next__(self):
if self.node:
cur_node = self.node
self.node = cur_node.next
return cur_node.item
else:
raise StopIteration
def __iter__(self):
return self
def __init__(self, iterable=None):
self.head = None
self.tail = None
if iterable:
self.extend(iterable)
def append(self, obj):
s = LinkList.Node(obj)
if not self.head:
self.head = s
self.tail = s
else:
self.tail.next = s
self.tail = s
def extend(self, iterable):
for obj in iterable:
self.append(obj)
def find(self, obj):
for n in self:
if n == obj:
return True
else:
return False
# 让类变成一个迭代器类,可以用for
def __iter__(self):
return self.LinkListIterator(self.head)
def __repr__(self):
return "<<"+",".join(map(str, self))+">>"
# 做一个类似集合的结构(不可重复)
class HashTable:
def __init__(self, size=101):
self.size = size
# 开一个拉链哈希表
self.T = [LinkList() for i in range(self.size)]
def h(self, k):
return k % self.size
def insert(self, k):
# 首先计算哈希值
i = self.h(k)
if self.find(k):
print("Duplicated Insert.")
else:
self.T[i].append(k)
def find(self, k):
i = self.h(k)
return self.T[i].find(k)
ht = HashTable()
ht.insert(0)
ht.insert(1)
ht.insert(4)
ht.insert(3)
ht.insert(509)
print(ht.find(509))
print(",".join(map(str, ht.T)))
第三节 哈希表的应用
1. python中应用
- 哈希表的应用一-集合与字典a= {‘name’: ‘Alex’, ‘age’: 18, 'gender: ‘Man’}
- 使用哈希表存储字典,通过哈希函数将字典的键映射为下标。假设h(‘name’) =h(‘age’)= 1, h(‘gender’) = 4,则哈希表存储为[None, 18, None, ‘Alex’,‘Man’]
- 如果发生哈希冲突,则通过拉链法或开发寻址法解决
- 列表和集合比,肯定集合查找更快
2. 加密MD5算法
- MD5(Message- Digest Algorithm 5)曾经是密码学中常用的哈希函数,可以把任意长度的数据映射为128位的哈希值,其曾经包含如下特征:
- 同样的消息,其MD5值必定相同;
- 可以快速计 算出任意给定消息的MD5值;
- 除非暴力的枚举所有可能的消息,否则不可能从哈希值反推出消息本身;
- 两条消息之间即使只有微小的差别,其对应的MD5值也应该是完全不同、完全不相关的;
- 不能在有 意义的时间内人工的构造两个不同的消息使其具有相同的MD5值。
- 应用举例:文件的哈希值算出文件的哈希值,若两个文件的哈希值相同,则可认为这两个文件是相同的。因此:
- 用户可以利用它来验证下载的文件是否完整。
- 云存储服务商可以利用它来判断用户要上传的文件是否已经存在于服务器上,从而实现秒传的功能,同时避 免存储过多相同的文件副本。
3. 加密SHA2算法
- 哈希表的应用一- SHA2算法历史.上MD5和SHA-1曾经是使用最为广泛的cryptographic hash function,但是随着密码学的发展,这两个哈希函数的安全性相继受到了各种挑战。
- 因此现在安全性较重要的场合推荐使用SHA-2等新的更安全的哈希函数。
- SHA- -2包含了一系列的哈希函数: SHA- -224, SHA- 256, SHA- -384, SHA- -512,SHA- -512/224, SHA- 512/256, 其对应的哈希值长度分别为224, 256, 384 or 512位。
- SHA- 2具有和MD5类似的性质(参见MD5算法的特征)。
- 应用举例:
- 例如,在比特币系统中,所有参与者需要共同解决如下问题:对于一个给定的字符串U,给定的国标哈希值H,需要计算出一个字符串V,使得U+V的哈希值与H的差小于一个给定值D。此时,只能通过暴力枚举V来进行猜测。首先计算出结果的人可获得一-定奖金。 而某人首先计算成功的概率与
其拥有的计算量成正比,所以其获得的奖金的期望值与其拥有的计算量成正比。
- 例如,在比特币系统中,所有参与者需要共同解决如下问题:对于一个给定的字符串U,给定的国标哈希值H,需要计算出一个字符串V,使得U+V的哈希值与H的差小于一个给定值D。此时,只能通过暴力枚举V来进行猜测。首先计算出结果的人可获得一-定奖金。 而某人首先计算成功的概率与