散列是数据结构中较为重要的内容。两种基本方法之一的链地址法虽稍繁琐,但思路较简单,过程清晰。
散列表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找速度。这个映射函数叫做散列函数,记录的数组叫做散列表。
散列函数:除留余数法(取关键字被某个不大于散列表表长m的数p除后所得的余数为散列地址)建立哈希表,并判断给定值是否在散列表中。
本代码使用链冲突法解决冲突,即散列到相同地址后以建立子链的方式解决元素冲突,因此不考虑总元素大于散列表大小而溢出的情况。重复元素只存一个。
输入
第一行数字表示要建立的散列表的大小,第二行表示要作为元素建立散列表的整数序列(注意:元素之间以空格分开,第一行散列表大小并不表示本行元素的数量),第三行表示要插入元素的数量n,接下来n行表示待插入元素值;下一行表示要删除元素的数量m,接下来m行表示待删除元素值;下一行表示要查找元素的数量r,接下来r行表示待查找元素值。
输出
插入无输出,对于每个待删除元素,元素若在散列表中,删除即可,若不在则输出 ‘Delete Error’,注意单词间空格;执行完插入删除后,查找元素是否在散列表中,若在则在相应行输出True, 否则输出False。
#散列表,通过链地址法解决冲突
#链表结点类,有数据域和指针域
class Node:
def __init__(self,data = None):
self.data = data
self.next = None
class Hashtable:
def __init__(self,size):#这种写法构建哈希表时需提供哈希表长度
#创建空列表作为哈希表,列表每个元素都是node类型
self.data = [Node()] * size
self.size = size
#哈希函数使用除留余数法。数据除以哈希表长然后取余
def hash_function(self,key,size):
return key % size
#向哈希表中插入数据,通过逐一插入构建哈希表
def put(self,key):
#求待插数据的哈希值
hash_value = self.hash_function(key,self.size)
#若链表中下标为哈希值的位置还没有被其他数据占据,就直接把待插数据(key)放到那个位置
if self.data[hash_value].data == None:
self.data[hash_value].data = key
#如果已被占据,就在以【哈希表中下标为哈希值的位置】为起点的链表中顺次检查,直到检查到空位置
else:
#上面链表起点位置空的情况已经帮忙建立好了结点,把数据域改变成key就好了。
#链表起点已被占据时,需要新建一个结点存储数据
temp = Node(key)
#p指向以【哈希表中下标为哈希值的位置】为起点的链表的头节点。
p = self.data[hash_value]
#向后逐个检查
while p.next != None:
p = p.next
#现在存p已经指向了链表的末尾,p的next连接上temp即可
p.next = temp
#判断某值(key)是不是在该哈希表里的函数
def get(self,key):
#获得要判断元素的哈希值
hash_value = self.hash_function(key,self.size)
#相同哈希值的元素在哈希表相应位置链表中的存储没有规律
#简单情况,该哈希值下的链表头等于key就说明找到
if self.data[hash_value].data == key:
return True
#哈希值对应的链表头存储的不是key时
else:
#p存储链表头
p = self.data[hash_value]
#只要没有碰到key,也没有到链表末尾,就一直向后寻找
while p != None and p.data != key:
p = p.next
#退出了上方循环后,还没有到链表的末尾,说明已找到
if p != None and p.data == key:
return True
#整个链表都未找到,说明整个哈希表里没有存储key的节点
return False
#在哈希表中删除数据为key的节点
def delete(self,key):
#如果输入的key在这个哈希表中根本没有,返回错误
if not self.get(key):
return 'Delete Error'
#否则,哈希表中有存储此数据的节点。先找该数据对应的哈希值。
hash_value = self.hash_function(key,self.size)
#和get函数完全类似。若哈希表中对应哈希值的链表的头部数据就和要删除的数据相符合,直接将该节点(链表头)数据域改变为none。
#空间有浪费,但不影响插入查找。
if self.data[hash_value].data == key:
self.data[hash_value].data = None
#否则,要删除的数据就在那个对应的链表中
else:
#p先指向那个位置的链表的头节点
p = self.data[hash_value]
#pre储存该链表当前节点之前的节点。设置pre,为了删除目标后将它上一个节点和后一个节点连接。初始时p指向头,pre为空即可。
pre = None
#找到要删除的位置,每一步更新p和pre
while p != None and p.data != key:
pre = p
p = p.next
#如果到末尾仍未找到(这种情况也可以去掉)
if p == None:
return 'Delete Error'
else:
pre.next = p.next
#按照要求建表、插入、删除、查找。
n = int(input())
#建表
h = Hashtable(n)
lst = list(set(input().split()))
slst = [int(i) for i in lst]
for i in slst:
h.put(i)
#插入
n = int(input())
for i in range(n):
tmp = int(input())
h.put(tmp)
#删除
n = int(input())
for i in range(n):
tmp = int(input())
tmp1 = h.delete(tmp)
if tmp1 == 'Delete Error':
print('Delete Error')
#查找
n = int(input())
for i in range(n):
tmp = int(input())
print(h.get(tmp))