散列表 / Hash Table


散列表与散列函数

散列表是一种将关键字映射到特定数组位置的一种数据结构,而将关键字映射到0至TableSize-1过程的函数,即为散列函数

  1. Hash Table:
  2. [0] -> A
  3. [1] -> B
  4. [2] -> C
  5. [3] -> D
  6. [4] -> E

下面以一个简单的散列函数 Hash(Key)=Key mod TableSize为例,完成一个散列表的实现。

Note: 为方便起见,这里选用了一个非素数作为TableSize,适宜的TableSize应为一个素数。

完整代码

  1. from collections import Iterable
  2.  
  3. class CollisionError(Exception):
  4. pass
  5.  
  6. class HashTable:
  7. """
  8. Hash Table:
  9. [0] -> A
  10. [1] -> B
  11. [2] -> C
  12. [3] -> D
  13. [4] -> E
  14. """
  15. def __init__(self, size, fn):
  16. self._array = [None for i in range(size)]
  17. self._hashing = fn
  18.  
  19. def __str__(self):
  20. return '\n'.join('[%d] %s' % (index, item) for index, item in enumerate(self._array))
  21.  
  22. def find(self, item):
  23. hash_code = self._hashing(item)
  24. value = self._array[hash_code]
  25. return value if value == item else None, hash_code
  26.  
  27. def insert(self, *args):
  28. for i in args:
  29. if isinstance(i, Iterable):
  30. for j in i:
  31. self._insert(j)
  32. else:
  33. self._insert(i)
  34.  
  35. def _insert(self, item):
  36. if item is None:
  37. return
  38. hash_code = self._hashing(item)
  39. value = self._array[hash_code]
  40. if value is not None and value != item: # Handle value 0 and value existed situation.
  41. raise CollisionError('Hashing value collided!')
  42. self._array[hash_code] = item
  43.  
  44. def delete(self, item):
  45. hash_code = self._hashing(item)
  46. if self._array[hash_code] != item:
  47. raise KeyError('Key error with %s' % item)
  48. self._array[hash_code] = None
  49.  
  50. def show(self):
  51. print(self)
  52.  
  53. @property
  54. def size(self):
  55. return len(self._array)
  56.  
  57. @property
  58. def load_factor(self):
  59. element_num = sum(map(lambda x: 0 if x is None else 1, self._array))
  60. return element_num/self.size
  61.  
  62. def make_empty(self):
  63. self._array = [None for i in range(self.size)]
  64.  
  65. def kmt_hashing(size):
  66. # Key = Key mod TableSize
  67. return lambda x: x % size
  68.  
  69. def test(h):
  70. print('\nShow hash table:')
  71. h.show()
  72.  
  73. print('\nInsert values:')
  74. h.insert(7, 8, 9)
  75. h.insert(range(7))
  76. h.show()
  77. print('\nInsert values (existed):')
  78. h.insert(1)
  79. h.show()
  80. print('\nInsert value (collided):')
  81. try:
  82. h.insert(11)
  83. except CollisionError as e:
  84. print(e)
  85.  
  86. print('\nFind value:')
  87. print(h.find(7))
  88. print('\nFind value (not existed):')
  89. print(h.find(77))
  90.  
  91. print('\nDelete value:')
  92. h.delete(7)
  93. h.show()
  94. print('\nDelete value (not existed):')
  95. try:
  96. h.delete(111)
  97. except KeyError as e:
  98. print(e)
  99.  
  100. print('\nLoad factor is:', h.load_factor)
  101. print('\nClear hash table:')
  102. h.make_empty()
  103. h.show()
  104.  
  105. if __name__ == '__main__':
  106. test(HashTable(10, kmt_hashing(10)))

分段解释

首先导入一个可迭代类,用于判断参数类型时使用,并定义一个散列冲突异常类

  1. from collections import Iterable
  2.  
  3. class CollisionError(Exception):
  4. pass

接着定义一个散列表类,构造函数接收两个参数,一个用于设置散列表的大小,一个用于设置散列函数,

Note: 由于Python的列表无法像C语言中的数组一样提前声明大小,因此这里的列表需要先用None进行填充。

  1. class HashTable:
  2. """
  3. Hash Table:
  4. [0] -> A
  5. [1] -> B
  6. [2] -> C
  7. [3] -> D
  8. [4] -> E
  9. """
  10. def __init__(self, size, fn):
  11. self._array = [None for i in range(size)]
  12. self._hashing = fn

再重载__str__方法,用于更加清晰的显示散列表,

  1. def __str__(self):
  2. return '\n'.join('[%d] %s' % (index, item) for index, item in enumerate(self._array))

定义散列表的find方法,find方法的时间复杂度为O(1),查找时仅需根据键值计算哈希值,再从散列表中获取元素即可。返回查找到的结果和对应哈希值,若未找到元素则返回None和最后查找的位置。

Note: O(1)的前提是散列函数足够简单快速

  1. def find(self, item):
  2. hash_code = self._hashing(item)
  3. value = self._array[hash_code]
  4. return value if value == item else None, hash_code

定义散列表的insert方法,首先对传入的参数进行判断,若为可迭代对象则迭代插入,否则直接插入。私有的插入方法将利用散列函数对插入值进行散列计算,然后插入对应位置,若对应位置已被占有,则引发一个冲突异常。

  1. def insert(self, *args):
  2. for i in args:
  3. if isinstance(i, Iterable):
  4. for j in i:
  5. self._insert(j)
  6. else:
  7. self._insert(i)
  8.  
  9. def _insert(self, item):
  10. if item is None:
  11. return
  12. hash_code = self._hashing(item)
  13. value = self._array[hash_code]
  14. if value is not None and value != item: # Handle value 0 and value existed situation.
  15. raise CollisionError('Hashing value collided!')
  16. self._array[hash_code] = item

定义散列表的delete方法,当需要删除某个值时,同样先进行散列计算,找到对应散列位置,若该位置的值与删除值不同,则引发一个键错误异常,若相同或为None,则直接删除该元素。

  1. def delete(self, item):
  2. hash_code = self._hashing(item)
  3. if self._array[hash_code] != item:
  4. raise KeyError('Key error with %s' % item)
  5. self._array[hash_code] = None

接着定义散列表几个基本方法,包括显示散列表,获取散列表大小,计算装填因子和清空散列表。

  1. def show(self):
  2. print(self)
  3.  
  4. @property
  5. def size(self):
  6. return len(self._array)
  7.  
  8. @property
  9. def load_factor(self):
  10. element_num = sum(map(lambda x: 0 if x is None else 1, self._array))
  11. return element_num/self.size
  12.  
  13. def make_empty(self):
  14. self._array = [None for i in range(self.size)]

最后,定义一个简单的散列函数Hash(Key)=Key mode TableSize。

  1. def kmt_hashing(size):
  2. # Key = Key mod TableSize
  3. return lambda x: x % size

以及一个测试函数,对散列表进行测试。

首先显示一个初始的散列表,

  1. def test(h):
  2. print('\nShow hash table:')
  3. h.show()

得到结果

  1. Show hash table:
  2. [0] None
  3. [1] None
  4. [2] None
  5. [3] None
  6. [4] None
  7. [5] None
  8. [6] None
  9. [7] None
  10. [8] None
  11. [9] None

接着测试插入方法,向散列表中插入元素

  1. print('\nInsert values:')
  2. h.insert(7, 8, 9)
  3. h.insert(range(7))
  4. h.show()

得到结果

  1. Insert values:
  2. [0] 0
  3. [1] 1
  4. [2] 2
  5. [3] 3
  6. [4] 4
  7. [5] 5
  8. [6] 6
  9. [7] 7
  10. [8] 8
  11. [9] 9

尝试插入已存在的元素,则没有影响,而尝试插入一个冲突元素,则会引发一个冲突异常

  1. print('\nInsert values (existed):')
  2. h.insert(1)
  3. h.show()
  4. print('\nInsert value (collided):')
  5. try:
  6. h.insert(11)
  7. except CollisionError as e:
  8. print(e)

显示结果

  1. Insert values (existed):
  2. [0] 0
  3. [1] 1
  4. [2] 2
  5. [3] 3
  6. [4] 4
  7. [5] 5
  8. [6] 6
  9. [7] 7
  10. [8] 8
  11. [9] 9
  12.  
  13. Insert value (collided):
  14. Hashing value collided!

尝试查找一个存在的元素和一个不存在的元素

  1. print('\nFind value:')
  2. print(h.find(7))
  3. print('\nFind value (not existed):')
  4. print(h.find(77))

得到结果

  1. Find value:
  2. (7, 7)
  3.  
  4. Find value (not existed):
  5. (None, 7)

尝试删除一个存在元素和一个不存在的元素

  1. print('\nDelete value:')
  2. h.delete(7)
  3. h.show()
  4. print('\nDelete value (not existed):')
  5. try:
  6. h.delete(111)
  7. except KeyError as e:
  8. print(e)

得到结果

  1. Delete value:
  2. [0] 0
  3. [1] 1
  4. [2] 2
  5. [3] 3
  6. [4] 4
  7. [5] 5
  8. [6] 6
  9. [7] None
  10. [8] 8
  11. [9] 9
  12.  
  13. Delete value (not existed):
  14. 'Key error with 111'

查看装载因子,最后清空散列表

  1. print('\nLoad factor is:', h.load_factor)
  2. print('\nClear hash table:')
  3. h.make_empty()
  4. h.show()

得到结果

  1. Load factor is: 0.9
  2.  
  3. Clear hash table:
  4. [0] None
  5. [1] None
  6. [2] None
  7. [3] None
  8. [4] None
  9. [5] None
  10. [6] None
  11. [7] None
  12. [8] None
  13. [9] None

一个基本的散列表基本建立完成,但还存在一个插入冲突的问题没有解决,对于插入冲突现象,解决的方式主要有分离链接法开放定址法,具体内容可参考相关阅读。

相关阅读


1. 分离链接法

2. 开放定址法

Python与数据结构[4] -> 散列表[0] -> 散列表与散列函数的 Python 实现的更多相关文章

  1. Python与数据结构[3] -> 树/Tree[0] -> 二叉树及遍历二叉树的 Python 实现

    二叉树 / Binary Tree 二叉树是树结构的一种,但二叉树的每一个节点都最多只能有两个子节点. Binary Tree: 00 |_____ | | 00 00 |__ |__ | | | | ...

  2. Python与数据结构[1] -> 栈/Stack[0] -> 链表栈与数组栈的 Python 实现

    栈 / Stack 目录 链表栈 数组栈 栈是一种基本的线性数据结构(先入后出FILO),在 C 语言中有链表和数组两种实现方式,下面用 Python 对这两种栈进行实现. 1 链表栈 链表栈是以单链 ...

  3. Python与数据结构[2] -> 队列/Queue[0] -> 数组队列的 Python 实现

    队列 / Queue 数组队列 数组队列是队列基于数组的一种实现,其实现类似于数组栈,是一种FIFO的线性数据结构. Queue: <--| 1 | 2 | 3 | 4 | 5 |<-- ...

  4. Python与数据结构[4] -> 散列表[2] -> 开放定址法与再散列的 Python 实现

     开放定址散列法和再散列 目录 开放定址法 再散列 代码实现 1 开放定址散列法 前面利用分离链接法解决了散列表插入冲突的问题,而除了分离链接法外,还可以使用开放定址法来解决散列表的冲突问题. 开放定 ...

  5. python对redis的常用操作 上 (对列表、字符串、散列结构操作)

    这里的一切讨论均基于python的redis-py库. 安装使用: pip install redis 然后去获取一个redis客户端: redis_conn = redis.Redis(host=R ...

  6. python --整理数据结构(列表)

    该整理来源于:https://www.runoob.com/python3/python3-data-structure.html 列表 python中列表是可变的,这是它区别于字符串和元组的最重要的 ...

  7. Python常见数据结构--列表

       列表 Python有6个序列的内置类型,但最常见的是列表和元组. 序列都可以进行的操作包括索引,切片.加.乘.检查成员. 此外,Python已经内置确定序列的长度以及确定最大和最下的元素的方法. ...

  8. (python)数据结构------列表

    一.数字的处理函数 (一)int() 取整数部分,与正负号无关,举例如下: print(int(-3.6), int(-2.5), int(-1.4)) print(int(3.6), int(2.5 ...

  9. Oracle表分区分为四种:范围分区,散列分区,列表分区和复合分区(转载)

    一:范围分区 就是根据数据库表中某一字段的值的范围来划分分区,例如: 1 create table graderecord 2 ( 3 sno varchar2(10), 4 sname varcha ...

随机推荐

  1. [CF1019A]Elections

    题目大意:有$n$个人,$m$个政党,每个人都想投一个政党,但可以用一定的钱让他选你想让他选的政党. 现在要$1$号政党获胜,获胜的条件是:票数严格大于其他所有政党.求最小代价 题解:暴力枚举其他政党 ...

  2. POJ3415 Common Substrings 【后缀数组 + 单调栈】

    常见的子串 时间限制: 5000MS   内存限制: 65536K 提交总数: 11942   接受: 4051 描述 字符串T的子字符串被定义为: Ť(我,ķ)= Ť 我 Ť 我 1 ... Ť I ...

  3. C++——OOP面向对象理解

    从Rob Pike 的 Google+上的一个推看到了一篇叫<Understanding Object Oriented Programming>的文章,我先把这篇文章简述一下,然后再说说 ...

  4. [学习笔记]Tarjan&&欧拉回路

    本篇并不适合初学者阅读. SCC: 1.Tarjan缩点:x回溯前,dfn[x]==low[x]则缩点. 注意: ①sta,in[]标记. ②缩点之后连边可能有重边. 2.应用: SCC应用范围还是很 ...

  5. javascript中arguments的应用——不定项传参求和

    <script type="text/javascript"> window.onload=function(){ function sum(){ var result ...

  6. CSS3学习笔记之径向展开菜单

    效果截图: HTML代码: <div class="menu-wrap"> <nav> <a href="" class=&quo ...

  7. 从一段字符串中去除数字的shell方法

  8. bzoj 1076 状压DP

    我们设w[i][s]为当前到第i关,手中的物品为s的时候,期望得分为多少,其中s为二进制表示每种物品是否存在. 那么就比较容易转移了w[i][s]=(w[i-1][s']+v[j]) *(1/k),其 ...

  9. bzoj 2324 ZJOI 营救皮卡丘 费用流

    题的大概意思就是给定一个无向图,边有权值,现在你有k个人在0点,要求走到n点,且满足 1:人们可以分头行动,可以停在某一点不走了 2:当你走到x时,前x-1个点必须全部走过(不同的人走过也行,即分两路 ...

  10. echarts图表自适应浏览器窗口的大小

    echarts问题描述 当浏览器窗口发生变化时,echarts图表未能自适应浏览器的变化,产生如下的效果 解决方案 在$(function(){}中添加 window.onresize = funct ...