使用python实现哈希表、字典、集合操作


Posted in Python onDecember 22, 2019

哈希表

哈希表(Hash Table, 又称为散列表),是一种线性表的存储结构。哈希表由一个直接寻址表和一个哈希函数组成。哈希函数h(k)将元素关键字k作为自变量,返回元素的存储下标。

简单哈希函数:

除法哈希:h(k) = k mod m乘法哈希:h(k) = floor(m(kA mod 1)) 0<A<1

假设有一个长度为7的数组,哈希函数h(k) = k mod 7,元素集合{14, 22, 3, 5}的存储方式如下图:

使用python实现哈希表、字典、集合操作

哈希冲突

由于哈希表的大小是有限的,而要存储的值的总数量是无限的,因此对于任何哈希函数,都会出现两个不同的元素映射到同一个位置上的情况,这种情况叫做哈希冲突。

比如:h(k) = k mod 7, h(0) = h(7) = h(14) = ...

解决哈希冲突--开放寻址法

开放寻址法:如果哈希函数返回的位置已经有值,则可以向后探查新的位置来存储这个值

线性探查:如果位置i被占用,则探查i+1, i+2,...二次探查:如果位置i被占用,则探查i+12, i-12, i+22, i-22,...二度哈希:有n个哈希函数,当使用第一个哈希函数h1发生冲突时,则尝试使用h2, h3,...

解决哈希冲突--拉链法

拉链法:哈希表每一个位置都连接一个链表,当冲突发生时,冲突的元素将被加到该位置链表的最后。

使用python实现哈希表、字典、集合操作

哈希表的实现

class Array(object):

 def __init__(self, size=32, init=None):
  self._size = size
  self._items = [init] * size

 def __getitem__(self, index):
  return self._items[index]

 def __setitem__(self, index, value):
  self._items[index] = value

 def __len__(self):
  return self._size

 def clear(self, value=None):
  for i in range(len(self._items)):
   self._items[i] = value

 def __iter__(self):
  for item in self._items:
   yield item


class Slot(object):
 """
 定义一个 hash 表数组的槽(slot 这里指的就是数组的一个位置)
 hash table 就是一个数组,每个数组的元素(也叫slot槽)是一个对象,对象包含两个属性 key 和 value。

 注意,一个槽有三种状态,看你能否想明白。相比链接法解决冲突,探查法删除一个 key 的操作稍微复杂。
 1.从未使用 HashMap.UNUSED。此槽没有被使用和冲突过,查找时只要找到 UNUSED 就不用再继续探查了
 2.使用过但是 remove 了,此时是 HashMap.EMPTY,该探查点后边的元素仍然可能是有key的,需要继续查找
 3.槽正在使用 Slot 节点
 """

 def __init__(self, key, value):
  self.key, self.value = key, value


class HashTable(object):
 UNUSED = None # 没被使用过
 EMPTY = Slot(None, None) # 使用却被删除过

 def __init__(self):
  self._table = Array(8, init=HashTable.UNUSED) # 保持 2*i 次方
  self.length = 0

 @property
 def _load_factor(self):
  # load_factor 超过 0.8 重新分配
  return self.length / float(len(self._table))

 def __len__(self):
  return self.length

 # 进行哈希
 def _hash(self, key):
  return abs(hash(key)) % len(self._table)

 # 查找key
 def _find_key(self, key):
  """
  解释一个 slot 为 UNUSED 和 EMPTY 的区别
  因为使用的是二次探查的方式,假如有两个元素 A,B 冲突了,
  首先A hash 得到是 slot 下标5,A 放到了第5个槽,之后插入 B 因为冲突了,所以继续根据二次探查方式放到了 slot下标8。
  然后删除 A,槽 5 被置为 EMPTY。然后我去查找 B,
  第一次 hash 得到的是 槽5,但是这个时候我还是需要第二次计算 hash 才能找到 B。
  但是如果槽是 UNUSED 我就不用继续找了,我认为 B 就是不存在的元素。这个就是 UNUSED 和 EMPTY 的区别。
  """
  origin_index = index = self._hash(key) # origin_index 判断是否又走到了起点,如果查找一圈了都找不到则无此元素
  _len = len(self._table)
  while self._table[index] is not HashTable.UNUSED:
   if self._table[index] is HashTable.EMPTY: # 注意如果是 EMPTY,继续寻找下一个槽
    index = (index * 5 + 1) % _len
    if index == origin_index:
     break
    continue
   if self._table[index].key == key: # 找到了key
    return index
   else:
    index = (index * 5 + 1) % _len # 没有找到继续找下一个位置
    if index == origin_index:
     break

  return None

 # 找能插入的槽
 def _find_slot_for_insert(self, key):
  index = self._hash(key)
  _len = len(self._table)
  while not self._slot_can_insert(index): # 直到找到一个可以用的槽
   index = (index * 5 + 1) % _len
  return index

 # 槽是否能插入
 def _slot_can_insert(self, index):
  return self._table[index] is HashTable.EMPTY or self._table[index] is HashTable.UNUSED

 # in operator,实现之后可以使用 in 操作符判断
 def __contains__(self, key):
  index = self._find_key(key)
  return index is not None

 # 添加元素
 def add(self, key, value):
  if key in self: # update
   index = self._find_key(key)
   self._table[index].value = value
   return False
  else:
   index = self._find_slot_for_insert(key)
   self._table[index] = Slot(key, value)
   self.length += 1
   if self._load_factor >= 0.8:
    self._rehash()
   return True

 # 槽不够时,重哈希
 def _rehash(self):
  old_table = self._table
  newsize = len(self._table) * 2
  self._table = Array(newsize, HashTable.UNUSED)

  self.length = 0

  for slot in old_table:
   if slot is not HashTable.UNUSED and slot is not HashTable.EMPTY:
    index = self._find_slot_for_insert(slot.key)
    self._table[index] = slot
    self.length += 1

 # 获取值
 def get(self, key, default=None):
  index = self._find_key(key)
  if index is None:
   return default
  else:
   return self._table[index].value

 # 移除
 def remove(self, key):
  index = self._find_key(key)
  if index is None:
   raise KeyError()
  value = self._table[index].value
  self.length -= 1
  self._table[index] = HashTable.EMPTY
  return value

 # 遍历
 def __iter__(self):
  for slot in self._table:
   if slot not in (HashTable.EMPTY, HashTable.UNUSED):
    yield slot.key

哈希表的使用

h = HashTable()
h.add('a', 0)
h.add('b', 1)
h.add('c', 2)
print(len(h)) # 3
print(h.get('a')) # 0
print(h.get('b')) # 1
print(h.get('hehe')) # None
h.remove('a')
print(h.get('a')) # None
print(sorted(list(h))) # ['b', 'c']

字典

字典是另一种可变容器模型,且可存储任意类型对象。

字典的每个键值key=>value对用冒号:分割,每个键值对之间用逗号,分割,整个字典包括在花括号{}中 ,格式如下所示:

d = {key1 : value1, key2 : value2 }

使用python实现哈希表、字典、集合操作

基于哈希表实现字典

class Array(object):

 def __init__(self, size=32, init=None):
  self._size = size
  self._items = [init] * size

 def __getitem__(self, index):
  return self._items[index]

 def __setitem__(self, index, value):
  self._items[index] = value

 def __len__(self):
  return self._size

 def clear(self, value=None):
  for i in range(len(self._items)):
   self._items[i] = value

 def __iter__(self):
  for item in self._items:
   yield item


class Slot(object):
 """
 定义一个 hash 表数组的槽(slot 这里指的就是数组的一个位置)
 hash table 就是一个数组,每个数组的元素(也叫slot槽)是一个对象,对象包含两个属性 key 和 value。

 注意,一个槽有三种状态,看你能否想明白。相比链接法解决冲突,探查法删除一个 key 的操作稍微复杂。
 1.从未使用 HashMap.UNUSED。此槽没有被使用和冲突过,查找时只要找到 UNUSED 就不用再继续探查了
 2.使用过但是 remove 了,此时是 HashMap.EMPTY,该探查点后边的元素仍然可能是有key的,需要继续查找
 3.槽正在使用 Slot 节点
 """

 def __init__(self, key, value):
  self.key, self.value = key, value


class HashTable(object):
 UNUSED = None # 没被使用过
 EMPTY = Slot(None, None) # 使用却被删除过

 def __init__(self):
  self._table = Array(8, init=HashTable.UNUSED) # 保持 2*i 次方
  self.length = 0

 @property
 def _load_factor(self):
  # load_factor 超过 0.8 重新分配
  return self.length / float(len(self._table))

 def __len__(self):
  return self.length

 # 进行哈希
 def _hash(self, key):
  return abs(hash(key)) % len(self._table)

 # 查找key
 def _find_key(self, key):
  """
  解释一个 slot 为 UNUSED 和 EMPTY 的区别
  因为使用的是二次探查的方式,假如有两个元素 A,B 冲突了,
  首先A hash 得到是 slot 下标5,A 放到了第5个槽,之后插入 B 因为冲突了,所以继续根据二次探查方式放到了 slot下标8。
  然后删除 A,槽 5 被置为 EMPTY。然后我去查找 B,
  第一次 hash 得到的是 槽5,但是这个时候我还是需要第二次计算 hash 才能找到 B。
  但是如果槽是 UNUSED 我就不用继续找了,我认为 B 就是不存在的元素。这个就是 UNUSED 和 EMPTY 的区别。
  """
  origin_index = index = self._hash(key) # origin_index 判断是否又走到了起点,如果查找一圈了都找不到则无此元素
  _len = len(self._table)
  while self._table[index] is not HashTable.UNUSED:
   if self._table[index] is HashTable.EMPTY: # 注意如果是 EMPTY,继续寻找下一个槽
    index = (index * 5 + 1) % _len
    if index == origin_index:
     break
    continue
   if self._table[index].key == key: # 找到了key
    return index
   else:
    index = (index * 5 + 1) % _len # 没有找到继续找下一个位置
    if index == origin_index:
     break

  return None

 # 找能插入的槽
 def _find_slot_for_insert(self, key):
  index = self._hash(key)
  _len = len(self._table)
  while not self._slot_can_insert(index): # 直到找到一个可以用的槽
   index = (index * 5 + 1) % _len
  return index

 # 槽是否能插入
 def _slot_can_insert(self, index):
  return self._table[index] is HashTable.EMPTY or self._table[index] is HashTable.UNUSED

 # in operator,实现之后可以使用 in 操作符判断
 def __contains__(self, key):
  index = self._find_key(key)
  return index is not None

 # 添加元素
 def add(self, key, value):
  if key in self: # update
   index = self._find_key(key)
   self._table[index].value = value
   return False
  else:
   index = self._find_slot_for_insert(key)
   self._table[index] = Slot(key, value)
   self.length += 1
   if self._load_factor >= 0.8:
    self._rehash()
   return True

 # 槽不够时,重哈希
 def _rehash(self):
  old_table = self._table
  newsize = len(self._table) * 2
  self._table = Array(newsize, HashTable.UNUSED)

  self.length = 0

  for slot in old_table:
   if slot is not HashTable.UNUSED and slot is not HashTable.EMPTY:
    index = self._find_slot_for_insert(slot.key)
    self._table[index] = slot
    self.length += 1

 # 获取值
 def get(self, key, default=None):
  index = self._find_key(key)
  if index is None:
   return default
  else:
   return self._table[index].value

 # 移除
 def remove(self, key):
  index = self._find_key(key)
  if index is None:
   raise KeyError()
  value = self._table[index].value
  self.length -= 1
  self._table[index] = HashTable.EMPTY
  return value

 # 遍历
 def __iter__(self):
  for slot in self._table:
   if slot not in (HashTable.EMPTY, HashTable.UNUSED):
    yield slot.key


class DictADT(HashTable):
 # 执行dict[key]=value时执行
 def __setitem__(self, key, value):
  self.add(key, value)

 # 执行dict[key]时执行
 def __getitem__(self, key, default=None):
  if key not in self:
   raise KeyError()
  return self.get(key, default)

 # 遍历时执行
 def _iter_slot(self):
  for slot in self._table:
   if slot not in (self.UNUSED, self.EMPTY):
    yield slot

 # 实现items方法
 def items(self):
  for slot in self._iter_slot():
   yield (slot.key, slot.value)

 # 实现keys方法
 def keys(self):
  for slot in self._iter_slot():
   yield slot.key

 # 实现values方法
 def values(self):
  for slot in self._iter_slot():
   yield slot.value

字典的使用

d = DictADT()
d['a'] = 1
print(d['a']) # 1

集合

集合是一种不包含重复元素的数据结构,经常用来判断是否重复这种操作,或者集合中是否存在一个元素。

集合可能最常用的就是去重,判断是否存在一个元素等,但是 set 相比 dict 有更丰富的操作,主要是数学概念上的。

如果你学过《离散数学》中集合相关的概念,基本上是一致的。 python 的 set 提供了如下基本的集合操作, 假设有两个集合 A,B,有以下操作

  • 交集: A & B,表示同时在 A 和 B 中的元素。 python 中重载 __and__ 实现
  • 并集: A | B,表示在 A 或者 B 中的元素,两个集合相加。python 中重载 __or__ 实现
  • 差集: A - B,表示在 A 中但是不在 B 中的元素。 python 中重载 __sub__ 实现

基于哈希表实现集合

class Array(object):

  def __init__(self, size=32, init=None):
    self._size = size
    self._items = [init] * size

  def __getitem__(self, index):
    return self._items[index]

  def __setitem__(self, index, value):
    self._items[index] = value

  def __len__(self):
    return self._size

  def clear(self, value=None):
    for i in range(len(self._items)):
      self._items[i] = value

  def __iter__(self):
    for item in self._items:
      yield item


class Slot(object):
  """
  定义一个 hash 表数组的槽(slot 这里指的就是数组的一个位置)
  hash table 就是一个数组,每个数组的元素(也叫slot槽)是一个对象,对象包含两个属性 key 和 value。

  注意,一个槽有三种状态,看你能否想明白。相比链接法解决冲突,探查法删除一个 key 的操作稍微复杂。
  1.从未使用 HashMap.UNUSED。此槽没有被使用和冲突过,查找时只要找到 UNUSED 就不用再继续探查了
  2.使用过但是 remove 了,此时是 HashMap.EMPTY,该探查点后边的元素仍然可能是有key的,需要继续查找
  3.槽正在使用 Slot 节点
  """

  def __init__(self, key, value):
    self.key, self.value = key, value


class HashTable(object):
  UNUSED = None # 没被使用过
  EMPTY = Slot(None, None) # 使用却被删除过

  def __init__(self):
    self._table = Array(8, init=HashTable.UNUSED) # 保持 2*i 次方
    self.length = 0

  @property
  def _load_factor(self):
    # load_factor 超过 0.8 重新分配
    return self.length / float(len(self._table))

  def __len__(self):
    return self.length

  # 进行哈希
  def _hash(self, key):
    return abs(hash(key)) % len(self._table)

  # 查找key
  def _find_key(self, key):
    """
    解释一个 slot 为 UNUSED 和 EMPTY 的区别
    因为使用的是二次探查的方式,假如有两个元素 A,B 冲突了,
    首先A hash 得到是 slot 下标5,A 放到了第5个槽,之后插入 B 因为冲突了,所以继续根据二次探查方式放到了 slot下标8。
    然后删除 A,槽 5 被置为 EMPTY。然后我去查找 B,
    第一次 hash 得到的是 槽5,但是这个时候我还是需要第二次计算 hash 才能找到 B。
    但是如果槽是 UNUSED 我就不用继续找了,我认为 B 就是不存在的元素。这个就是 UNUSED 和 EMPTY 的区别。
    """
    origin_index = index = self._hash(key) # origin_index 判断是否又走到了起点,如果查找一圈了都找不到则无此元素
    _len = len(self._table)
    while self._table[index] is not HashTable.UNUSED:
      if self._table[index] is HashTable.EMPTY: # 注意如果是 EMPTY,继续寻找下一个槽
        index = (index * 5 + 1) % _len
        if index == origin_index:
          break
        continue
      if self._table[index].key == key: # 找到了key
        return index
      else:
        index = (index * 5 + 1) % _len # 没有找到继续找下一个位置
        if index == origin_index:
          break

    return None

  # 找能插入的槽
  def _find_slot_for_insert(self, key):
    index = self._hash(key)
    _len = len(self._table)
    while not self._slot_can_insert(index): # 直到找到一个可以用的槽
      index = (index * 5 + 1) % _len
    return index

  # 槽是否能插入
  def _slot_can_insert(self, index):
    return self._table[index] is HashTable.EMPTY or self._table[index] is HashTable.UNUSED

  # in operator,实现之后可以使用 in 操作符判断
  def __contains__(self, key):
    index = self._find_key(key)
    return index is not None

  # 添加元素
  def add(self, key, value):
    if key in self: # update
      index = self._find_key(key)
      self._table[index].value = value
      return False
    else:
      index = self._find_slot_for_insert(key)
      self._table[index] = Slot(key, value)
      self.length += 1
      if self._load_factor >= 0.8:
        self._rehash()
      return True

  # 槽不够时,重哈希
  def _rehash(self):
    old_table = self._table
    newsize = len(self._table) * 2
    self._table = Array(newsize, HashTable.UNUSED)

    self.length = 0

    for slot in old_table:
      if slot is not HashTable.UNUSED and slot is not HashTable.EMPTY:
        index = self._find_slot_for_insert(slot.key)
        self._table[index] = slot
        self.length += 1

  # 获取值
  def get(self, key, default=None):
    index = self._find_key(key)
    if index is None:
      return default
    else:
      return self._table[index].value

  # 移除
  def remove(self, key):
    index = self._find_key(key)
    if index is None:
      raise KeyError()
    value = self._table[index].value
    self.length -= 1
    self._table[index] = HashTable.EMPTY
    return value

  # 遍历
  def __iter__(self):
    for slot in self._table:
      if slot not in (HashTable.EMPTY, HashTable.UNUSED):
        yield slot.key


class SetADT(HashTable):
  # 添加元素
  def add(self, key):
    super().add(key, True)
  
  def __and__(self, other_set):
    """交集 A&B"""
    new_set = SetADT()
    for element_a in self:
      if element_a in other_set:
        new_set.add(element_a)
    return new_set

  def __sub__(self, other_set):
    """差集 A-B"""
    new_set = SetADT()
    for element_a in self:
      if element_a not in other_set:
        new_set.add(element_a)
    return new_set

  def __or__(self, other_set):
    """并集 A|B"""
    new_set = SetADT()
    for element_a in self:
      new_set.add(element_a)
    for element_b in other_set:
      new_set.add(element_b)
    return new_set

集合的使用

sa = SetADT()
sa.add(1)
sa.add(2)
sa.add(3)

sb = SetADT()
sb.add(3)
sb.add(4)
sb.add(5)

print(sorted(list(sa & sb))) # [3]
print(sorted(list(sa - sb))) # [1, 2]
print(sorted(list(sa | sb))) # [1, 2, 3, 4, 5]

总结

以上所述是小编给大家介绍的使用python实现哈希表、字典、集合操作,希望对大家有所帮助!

Python 相关文章推荐
Python实现PS滤镜的万花筒效果示例
Jan 23 Python
python获取文件真实链接的方法,针对于302返回码
May 14 Python
详解如何用django实现redirect的几种方法总结
Nov 22 Python
使用pyinstaller打包PyQt4程序遇到的问题及解决方法
Jun 24 Python
Python基于机器学习方法实现的电影推荐系统实例详解
Jun 25 Python
Python使用APScheduler实现定时任务过程解析
Sep 11 Python
Django框架下静态模板的继承操作示例
Nov 08 Python
Pandas时间序列:时期(period)及其算术运算详解
Feb 25 Python
解决windows下python3使用multiprocessing.Pool出现的问题
Apr 08 Python
如何基于线程池提升request模块效率
Apr 18 Python
Python基于jieba, wordcloud库生成中文词云
May 13 Python
基于python实现简单网页服务器代码实例
Sep 14 Python
浅析Python数字类型和字符串类型的内置方法
Dec 22 #Python
Python利用多线程同步锁实现多窗口订票系统(推荐)
Dec 22 #Python
python使用正则来处理各种匹配问题
Dec 22 #Python
Python中base64与xml取值结合问题
Dec 22 #Python
python操作cfg配置文件方式
Dec 22 #Python
python实现局域网内实时通信代码
Dec 22 #Python
python 解决flask uwsgi 获取不到全局变量的问题
Dec 22 #Python
You might like
php 日期时间处理函数小结
2009/12/18 PHP
php字符串分割函数用法实例
2015/03/17 PHP
js window.event对象详尽解析
2009/02/17 Javascript
js 小数取整的函数
2010/05/10 Javascript
JQuery中的$.getJSON 使用说明
2011/03/10 Javascript
jQuery 源码分析笔记(6) jQuery.data
2011/06/08 Javascript
jQuery 借助插件Lavalamp实现导航条动态美化效果
2013/09/27 Javascript
利用jQuery简单实现产品展示图片左右滚动功能(示例代码)
2014/01/02 Javascript
原生js和jQuery随意改变div属性style的名称和值
2014/10/22 Javascript
jQuery中[attribute!=value]选择器用法实例
2014/12/31 Javascript
JavaScript中使用Math.PI圆周率属性的方法
2015/06/14 Javascript
JavaScript setTimeout使用闭包功能实现定时打印数值
2015/12/18 Javascript
jQuery EasyUi实战教程之布局篇
2016/01/26 Javascript
Express的HTTP重定向到HTTPS的方法
2018/06/06 Javascript
CKeditor4 字体颜色功能配置方法教程
2019/06/26 Javascript
JS禁用右键、禁用Ctrl+u、禁用Ctrl+s、禁用F12的实现代码
2020/12/01 Javascript
[01:29]2014DOTA2展望TI 剑指西雅图DK战队专访
2014/06/30 DOTA
[02:47]DOTA2亚洲邀请赛 HR战队出场宣传片
2015/02/07 DOTA
[01:42]辉夜杯战队访谈宣传片—FANTUAN
2015/12/25 DOTA
详解Python3操作Mongodb简明易懂教程
2017/05/25 Python
pandas中Timestamp类用法详解
2017/12/11 Python
Python进阶之递归函数的用法及其示例
2018/01/31 Python
在django中图片上传的格式校验及大小方法
2019/07/28 Python
使用Python快乐学数学Github万星神器Manim简介
2019/08/07 Python
python3实现单目标粒子群算法
2019/11/14 Python
css3教程之倾斜页面
2014/01/27 HTML / CSS
草莓网英国官网:Strawberrynet UK
2017/02/12 全球购物
日本快乐生活方式购物网站:Shop Japan
2018/07/17 全球购物
领导的自我鉴定
2013/12/28 职场文书
打架检讨书500字
2014/01/29 职场文书
人力资源总监工作说明
2014/03/03 职场文书
医德医风演讲稿
2014/05/20 职场文书
党支部活动策划方案
2014/08/18 职场文书
2015年三好一满意工作总结
2015/07/24 职场文书
Python的flask接收前台的ajax的post数据和get数据的方法
2021/04/12 Python
python中Pyqt5使用Qlabel标签播放视频
2022/04/22 Python