python数据结构与算法——哈希表

哈希表 学习笔记

参考翻译自:《复杂性思考》 及对应的online版本:http://greenteapress.com/complexity/html/thinkcomplexity004.html

使用哈希表可以进行非常快速的查找操作,查找时间为常数,同时不需要元素排列有序

python的内建数据类型:字典,就是用哈希表实现的

为了解释哈希表的工作原理,我们来尝试在不使用字典的情况下实现哈希表结构。

我们需要定义一个包含 键->值 映射 的数据结构,同时实现以下两种操作:

add(k, v):

  Add a new item that maps from key k to value v.

  With a Python dictionary,d, this operation is written d[k] = v.

get(target):

  Look up and return the value that corresponds to key target.

  With a Python dictionary, d, this operation is written d[target] or d.get(target).

一种简单是实现方法是建立一个线性表,使用元组来实现 key-value 的映射关系

 1 class LinearMap(object):
 2     """ 线性表结构 """
 3     def __init__(self):
 4         self.items = []
 5
 6     def add(self, k, v):    # 往表中添加元素
 7         self.items.append((k,v))
 8
 9     def get(self, k):       # 线性方式查找元素
10         for key, val in self.items:
11             if key==k:      # 键存在,返回值,否则抛出异常
12                 return val
13         raise KeyError

我们可以在使用add添加元素时让items列表保持有序,而在使用get时采取二分查找方式,时间复杂度为O(log n)。 然而往列表中插入一个新元素实际上是一个线性操作,所以这种方法并非最好的方法。同时,我们仍然没有达到常数查找时间的要求。

我们可以做以下改进,将总查询表分割为若干段较小的列表,比如100个子段。通过hash函数求出某个键的哈希值,再通过计算,得到往哪个子段中添加或查找。相对于从头开始搜索列表,时间会极大的缩短。尽管get操作的增长依然是线性,但BetterMap类使得我们离哈希表更近一步:

 1 class BetterMap(object):
 2     """ 利用LinearMap对象作为子表,建立更快的查询表 """
 3     def __init__(self,n=100):
 4         self.maps = []          # 总表格
 5         for i in range(n):      # 根据n的大小建立n个空的子表
 6             self.maps.append(LinearMap())
 7
 8     def find_map(self,k):       # 通过hash函数计算索引值
 9         index = hash(k) % len(self.maps)
10         return self.maps[index] # 返回索引子表的引用
11
12     # 寻找合适的子表(linearMap对象),进行添加和查找
13     def add(self, k, v):
14         m = self.find_map(k)
15         m.add(k,v)
16
17     def get(self, k):
18         m = self.find_map(k)
19         return m.get(k)

测试一下:

 1 if __name__=="__main__":
 2     table = BetterMap()
 3     pricedata = [("Hohner257",257),
 4                  ("SW1664",280),
 5                  ("SCX64",1090),
 6                  ("SCX48",830),
 7                  ("Super64",2238),
 8                  ("CX12",1130),
 9                  ("Hohner270",620),
10                  ("F64C",9720),
11                  ("S48",1988)]
12
13     for item, price in pricedata:
14         table.add(k=item, v=price)
15
16     print table.get("CX12")
17     # >>> 1130
18     print table.get("QIMEI1248")
19     # >>> raise KeyError

由于每个键的hash值必然不同,所以对hash值取余的值基本也是不同的。

当n=100时, BetterMap的查找速度大约是LinearMap的100倍。

明显,BetterMap的查找速度受到参数n的限制,同时其中每个LinearMap的长度不固定,使得子段中的元素依然是线性查找。如果,我们能够限制每个子段的最大长度,这样在单个子段中查找的时间负责度就有一个固定上限,则LinearMap.get方法的时间复杂度就成为了一个常数。由此,我们仅仅需要跟踪元素的数量,每当某个LinearMap中的元素数量超过阈值时, 对整个hashtable进行重排,同时增加更多的LinearMap,这样子就可以保证查找操作为一个常数啦。

以下是hashtable的实现:

 1 class HashMap(object):
 2     def __init__(self):
 3         # 初始化总表为,容量为2的表格(含两个子表)
 4         self.maps = BetterMap(2)
 5         self.num = 0        # 表中数据个数
 6
 7     def get(self,k):
 8         return self.maps.get(k)
 9
10     def add(self, k, v):
11         # 若当前元素数量达到临界值(子表总数)时,进行重排操作
12         # 对总表进行扩张,增加子表的个数为当前元素个数的两倍!
13         if self.num == len(self.maps.maps):
14             self.resize()
15
16         # 往重排过后的 self.map 添加新的元素
17         self.maps.add(k, v)
18         self.num += 1
19
20     def resize(self):
21         """ 重排操作,添加新表, 注意重排需要线性的时间 """
22         # 先建立一个新的表,子表数 = 2 * 元素个数
23         new_maps = BetterMap(self.num * 2)
24
25         for m in self.maps.maps:  # 检索每个旧的子表
26             for k,v in m.items:   # 将子表的元素复制到新子表
27                 new_maps.add(k, v)
28
29         self.maps = new_maps      # 令当前的表为新表

重点关注 add 部分,该函数检查元素个数与BetterMap的大小,如果相等,则“平均每个LinearMap中的元素个数为1”,然后调用resize方法。

resize创建一个新表,大小为原来的两倍,然后对旧表中的元素“rehashes 再哈希”一 遍,放到新表中。

resize过程是线性的,听起来好像很不怎么好,因为我们要求的hashtable具有常数时间。但是,要知道我们并不需要经常进行重排操作,所以add操作在绝大部分时间中都是常数的,偶然出现线性。由于对n个元素进行add操作的总时间与n成比例,所以每次add的平均时间就是一个常数!

假设我们要添加32个元素,过程如下:

1. 由于初始长度为2,前两次add不需要重排,第1,2次 总时间为 2

2. 第3次add,重排为4,耗时2,第3次时间为 3

3. 第4次add,耗时1    到目前为止,总时间为 6

4. 第5次add,重排为 8,耗时4,第5次时间为5

5. 第6~8次   共耗时3      到目前为止,总时间为 6+5+3 = 14

6. 第9次add,重排16,  耗时8,第9次时间为9

7. 第10~16次,共耗时7, 到目前为止,总时间为 14+9+7 = 30

在32次add后,总时间为62的单位时间,由以上过程可以发现一个规律,在n个元素add之后,当n为2的幂,则当前总单位时间为 2n-2,所以平均add时间绝对小于2单位时间。

当n为2的幂时,为最合适的数量,当n变大之后,平均时间为稍微上升,但重要的是,我们达到了O(1)。

时间: 2024-11-13 02:12:04

python数据结构与算法——哈希表的相关文章

数据结构与算法--------哈希表

1.概述: 数据结构:哈希表 插入时间复杂度:O(1) 删除时间复杂度:O(1) 查找时间复杂度: 优点: 哈希表的操作速度比较快,如插入.删除的时间复杂度都是常量O(1),可以在一秒内查找上千条记录 哈希表的编程实现相对容易 缺点: 哈希表不能被填满.哈希表被基本填满的时候,性能会急剧下降,所以为了保证性能,你一定要确保你哈希表的容量的大小是足够的 哈希表到其他哈希表的数据迁移过程是一个费时的过程(如定期将数据迁移到更大的哈希表中去会很费时).如果一开始没有预估好你的数据量的大小,初始时创建的

python数据结构与算法 34 归并排序

归并排序 在提高排序算法性能的方法中,有一类叫做分而治之.我们先研究其中第一种叫做归并排序.归并排序使用递归的方法,不停地把列表一分为二.如果列表是空或只有一个元素,那么就是排好序的(递归基点),如果列表有超过1个的元素,那么切分列表并对两个子列表递归使用归并排序.一旦这两个列表排序完成,称为"归并"的基本操作开始执行.归并是把两个有序列表合并成一个新的有序列表的过程.图10是我们熟悉的列表样例分解过程,图11是归并的过程. 图10  切分过程 图11  归并过程 以下是mergeSo

Python数据结构与算法--List和Dictionaries

Lists 当实现 list 的数据结构的时候Python 的设计者有很多的选择. 每一个选择都有可能影响着 list 操作执行的快慢. 当然他们也试图优化一些不常见的操作. 但是当权衡的时候,它们还是牺牲了不常用的操作的性能来成全常用功能. 本文地址:http://www.cnblogs.com/archimedes/p/python-datastruct-algorithm-list-dictionary.html,转载请注明源地址. 设计者有很多的选择,使他们实现list的数据结构.这些选

python数据结构与算法 35 快速排序

快速排序 快速排序也使用了分而治之的策略来提高性能,而且不需要额外的内存,但是这么做的代价就是,列表不是对半切分的,因而,性能上就有所下降. 快速排序选择一个数值,一般称为"轴点",虽然有很多选取轴点的方法,我们还是简单地把列表中第一个元素做为轴点了.轴点的作用是帮助把列表分为两个部分.列表完成后,轴点所在的位置叫做"切分点",从这一点上把列表分成两部分供后续调用. 图12所示,54将作为轴点.这个例子我们已经排过多次了,我们知道54在排好序后将处于现在31的位置上

8. 蛤蟆的数据结构进阶八哈希表相关概念

8. 蛤蟆的数据结构进阶八哈希表相关概念 本篇名言:"作家当然必须挣钱才能生活,写作,但是他决不应该为了挣钱而生活,写作.--马克思" 前些笔记我们学习了二叉树相关.现在我们来看下哈希表.这篇先来看下哈希表的相关概念 欢迎转载,转载请标明出处:http://blog.csdn.net/notbaron/article/details/47347273 1.  哈希表的概念 哈希表(HashTable)也叫散列表,是根据关键码值(Key Value)而直接进行访问的数据结构.它通过把关键

python数据结构与算法 38 分析树

分析树 树的结构完成以后,该是时候看看它能做点什么实事儿了.这一节里,我们研究一下分析树.分析树能够用于真实世界的结构表示,象语法或数学表达式一类的. 图1 一个简单语句的分析树 图1所示是一个简单语句的层级结构,把语句表示为树结构可以让我们用子树来分析句子的组成部分. 图2 ((7+3)?(5?2))的分析树 我们也可以把数学表达式如((7+3)?(5?2))表示为分析树,如图2.此前我们研究过完全括号表达式,这个表达式表达了什么呢?我们知道乘法的优先级比加减要高,但因为括号的关系,在做乘法之

java数据结构与算法之顺序表与链表深入分析

转载请注明出处(万分感谢!): http://blog.csdn.net/javazejian/article/details/52953190 出自[zejian的博客] 关联文章: java数据结构与算法之顺序表与链表设计与实现分析 java数据结构与算法之双链表设计与实现 ??数据结构与算法这门学科虽然在大学期间就已学习过了,但是到现在确实也忘了不少,因此最近又重新看了本书-<数据结构与算法分析>加上之前看的<java数据结构>也算是对数据结构的进一步深入学习了,于是也就打算

python数据结构与算法 36 树的基本概念

树 学习目标 理解什么是树及使用方法 学会使用树实现映射 用列表实现树 用类和引用实现树 用递归实现树 用堆实现优先队列 树的例子 前面我们学习过栈和队列这类线性数据结构,并且体验过递归,现在我们学习另一种通用数据结构,叫做树.树在计算机科学中应用广泛,象操作系统.图形学.数据库系统.网络等都要用到树.树和他们在自然界中的表哥--植物树--非常相似,树也有根,有分枝,有叶子.不同之处是,数据结构的树,根在顶上,而叶子在底部. 在开始学习之前,我们来研究几个普通的例子.第一个是生物学上的分级树.图

数据结构实验:哈希表

http://acm.sdut.edu.cn/sdutoj/problem.php?action=showproblem&problemid=1480 #include <stdio.h> #include <string.h> int n; #define N 100001 //N的值取比哈希表长度略大的(质数) int a[100001],b[100001]; int main() { int i,j,ad,t; scanf("%d",&n)