一、哈希表
哈希表是一个通过哈希函数来计算数据存储位置的数据结构,通常支持如下操作:
insert(key,value):插入键值对(key,value)
get(key):如果存在键为key的键值对则返回其value,否则返回空
delete(key):删除键为key的键值对
直接寻址表:key为k的元素放到k位置上
改进直接寻址表:哈希 Hashing
构建大小为m的寻址表T
key为k的元素放到h(k)位置上
h(k)是个函数,将key的域U映射到表T[0,1,…,m-1]上
哈希表,又称为散列表,是一种线性表的存储结构。哈希表由一个直接寻址表和一个哈希函数组成,哈希函数h(k)将元素关键字k作为自变量,返回元素的存储下标。
1、哈希冲突:
由于哈希表大小是有限的,而要存储的值的总数量是无限的,因此对于任何哈希函数,都会出现两个不同元素映射到同一个位置上的情况,这种情况称为哈希冲突
比如h(k)=k%7,h(0)=h(7)=h(14)
2、解决哈希冲突:
(1)开放寻址法:如果哈希函数返回的位置已经有值,则可以向后探查新的位置来存储这个值
线性探查:如果位置i被占用,则探查i+1,i+2…
二次探查:如果位置i被占用,则探查i+1^2,i-1^2,i+2^2,i-2^2…
二度哈希:有n个哈希函数,当使用第一个哈希函数h1发生冲突时,则尝试使用h2、h3…
(2)拉链法:哈希表每个位置都连接一个链表,当冲突发生时,冲突的元素将被加到该位置链表的最后
3、常见的哈希函数:
除法哈希法:h(k)=k%m
乘法哈希法:h(k)=floor(m*(A*key%1))
全域哈希法:h_a,b(k)=((a*key+b) % p) % m a,b=1,2,…,p-1
拉链法哈希表:
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
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 _ in range(self.size)] #哈希表T每个位置是一个空链表
def h(self,k): #哈希函数h(k) 除法哈希
return k % self.size
def insert(self,k): #插入
i = self.h(k)
if self.find(k): #如果找到了相同元素 则不插入
print("元素已插入过")
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(3)
ht.insert(102)
ht.insert(508)
# print(",".join(map(str,ht.T)))
print(ht.find(1))
4、哈希表的应用
(1)字典和集合
字典和集合都是通过哈希表来实现的
a={‘name’:'anna','age':18,'gender':'female'}
使用哈希表存储字典,通过哈希函数将字典的键映射为下标,假设h('name')=3,h('age')=1,h('gender')=4,则哈希表存储为[None,18,None,'anna','female']
如果发生哈希冲突,则通过拉链法或开发寻址法解决。
(2)MD5算法
MD5曾经是密码学中常用的哈希函数,可以把任意长度的数据映射为128位的哈希值,其曾经包含如下特征:
同样的消息,其MD5值必定相同
可以快速计算出任意给定消息的MD5值
除非暴力的枚举所有可能的消息,否则不可能从哈希值反推出消息本身
两条消息之间即使只有微笑的差别,其对应的MD5值也应该是完全不同,完全不相关的
不能能在有意义的时间内人工构造两个不同的消息,使其具有相同的MD5值
如:文件的哈希值
算出文件的哈希值,若两个文件的哈希值相同,则可认为文件是相同的
用户可以利用它来验证下载的文件是否完整
云存储服务商可以利用它来判断用户要上传的文件是否已经存在于服务器上,从而实现秒传的功能,同时避免存储过多相同的文件副本。
(3)SHA2算法
SHA2具有和MD5类似的性质,安全性较重要的场合推荐使用SHA2等更安全的哈希函数
应用举例:
例如比特币系统,所有参与者需要共同解决如下问题:对于一个给定的字符串U,给定的目标哈希值H,需要计算出一个字符串V,使得U+V的哈希值与H的差小于一个给定值D。此时只能通过暴力枚举V来进行猜测。首先计算出结果的人可获得一定奖金,而某人首先计算成功的概率与其拥有的计算量成正比,所以其获得的奖金的期望值与其拥有的计算量成正比。