python数据结构之哈希表
哈希表(Hash table)
众所周知,HashMap是一个用于存储Key-Value键值对的集合,每一个键值对也叫做Entry。这些个键值对(Entry)分散存储在一个数组当中,这个数组就是HashMap的主干。
使用哈希表可以进行非常快速的查找操作,查找时间为常数,同时不需要元素排列有序;python的内建数据类型:字典,就是用哈希表实现的。
python中的这些东西都是哈希原理:字典(dictionary)、集合(set)、计数器(counter)、默认字典Defaut dict)、有序字典(Order dict).
我来尝试在不使用字典的情况下实现哈希表结构,我们需要定义一个包含 键->值 映射 的数据结构,同时实现以下两种操作:Get和 Add。
Add方法的原理
比如调用 hashMap.Add("apple", 0) ,插入一个Key为"apple"的元素。这时候我们需要利用一个哈希函数来确定Entry的插入位置(index):
Get方法的原理
Get方法根据Key来查找Value的时候, 首先会把输入的Key做一次Hash映射,得到对应的index:
index = Hash(“apple”)
一种简单的实现方法
建立一个线性表,使用元组来实现 key-value 的映射关系
- """
- 线性表结构
- """
- class LinearMap(object):
- def __init__(self):
- self.items = []
- # 往表中添加元素
- def add(self, k, v):
- self.items.append((k,v))
- # 线性方式查找元素
- def get(self, k):
- for key, value in self.items:
- if key == k: # 键存在,返回值,否则抛出异常
- return value
- raise KeyError
- '''
- 我们可以在使用add添加元素时让items列表保持有序,而在使用get时采取二分查找方式,时间复杂度为O(log n)。
然而往列表中插入一个新元素实际上是一个线性操作,所以这种方法并非最好的方法。
同时,我们仍然没有达到常数查找时间的要求。- '''
改进版本:
尽管get操作的增长依然是线性,但BetterMap类使得我们离哈希表更近一步:
- '''
- 将总查询表分割为若干段较小的列表,比如100个子段。
- 通过hash函数求出某个键的哈希值,再通过计算,得到往哪个子段中添加或查找。
- 相对于从头开始搜索列表,时间会极大的缩短。
- '''
- class BetterMap(object):
- #利用LinearMap对象作为子表,建立更快的查询表
- def __init__(self,n=100):
- self.maps = [] # 总表格
- for i in range(n): # 根据n的大小建立n个空的子表
- self.maps.append(LinearMap())
- def find_map(self,k): # 通过hash函数计算索引值
- index = hash(k) % len(self.maps)
- return self.maps[index] # 返回索引子表的引用
- # 寻找合适的子表(linearMap对象),进行添加和查找
- def add(self, k, v):
- m = self.find_map(k)
- m.add(k,v)
- def get(self, k):
- m = self.find_map(k)
- return m.get(k)
由于每个键的hash值必然不同,所以对hash值取余的值基本也是不同的;
当n=100时, BetterMap的查找速度大约是LinearMap的100倍。
Hashtable的实现
- class HashMap(object):
- def __init__(self):
- # 初始化总表为,容量为2的表格(含两个子表)
- self.maps = BetterMap(2)
- self.num = 0 # 表中数据个数
- def get(self,k):
- return self.maps.get(k)
- def add(self, k, v):
- # 若当前元素数量达到临界值(子表总数)时,进行重排操作
- # 对总表进行扩张,增加子表的个数为当前元素个数的两倍!
- if self.num == len(self.maps.maps):
- self.resize()
- # 往重排过后的 self.map 添加新的元素
- self.maps.add(k, v)
- self.num += 1
- def resize(self):
- #重排操作,添加新表, 注意重排需要线性的时间
- # 先建立一个新的表,子表数 = 2 * 元素个数
- new_maps = BetterMap(self.num * 2)
- for m in self.maps.maps: # 检索每个旧的子表
- for k,v in m.items: # 将子表的元素复制到新子表
- new_maps.add(k, v)
- 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)。
python数据结构之哈希表的更多相关文章
- Python 中的哈希表
Python 中的哈希表:对字典的理解 有没有想过,Python中的字典为什么这么高效稳定.原因是他是建立在hash表上.了解Python中的hash表有助于更好的理解Python,因为Pytho ...
- 数据结构 5 哈希表/HashMap 、自动扩容、多线程会出现的问题
上一节,我们已经介绍了最重要的B树以及B+树,使用的情况以及区别的内容.当然,本节课,我们将学习重要的一个数据结构.哈希表 哈希表 哈希也常被称作是散列表,为什么要这么称呼呢,散列.散列.其元素分布较 ...
- 数据结构与算法Python版 熟悉哈希表,了解Python字典底层实现
Hash Table 散列表(hash table)也被称为哈希表,它是一种根据键(key)来存储值(value)的特殊线性结构. 常用于迅速的无序单点查找,其查找速度可达到常数级别的O(1). 散列 ...
- 数据结构是哈希表(hashTable)
哈希表也称为散列表,是根据关键字值(key value)而直接进行访问的数据结构.也就是说,它通过把关键字值映射到一个位置来访问记录,以加快查找的速度.这个映射函数称为哈希函数(也称为散列函数),映射 ...
- 算法与数据结构基础 - 哈希表(Hash Table)
Hash Table基础 哈希表(Hash Table)是常用的数据结构,其运用哈希函数(hash function)实现映射,内部使用开放定址.拉链法等方式解决哈希冲突,使得读写时间复杂度平均为O( ...
- 数据结构HashMap哈希表原理分析
先看看定义:“散列表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构.也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度. 哈希 ...
- 数据结构,哈希表hash设计实验
数据结构实验,hash表 采用链地址法处理hash冲突 代码全部自己写,转载请留本文连接, 附上代码 #include<stdlib.h> #include<stdio.h> ...
- js:数据结构笔记7--哈希表
哈希表(散列表):通过哈希函数将键值映射为一个字典; 哈希函数:依赖键值的数据类型来构建一个哈希函数: 一个基本的哈希表:(按字符串计算键值) function HashTable() { this. ...
- C++数据结构之哈希表
哈希表的定义:哈希表是一种根据关键码去寻找值的数据映射结构,该结构通过把关键码映射的位置去寻找存放值的地方.键可以对应多个值(即哈希冲突),值根据相应的hash公式存入对应的键中. 哈希函数的构造要求 ...
随机推荐
- vuejs中使用echart图表
首先安装echart npm i echarts -S 加下来以使用这个图表为例 在vue组件中像这样使用: <template> <div :class="classNa ...
- 【GIS】Cesium1.49编译
1.npm install 2.npm install --save-dev gulp 3.gulp default 4.npm run build 5.npm start 遇到问题 1.gulp不好 ...
- 03python条件判断与缩进
if...else...来实现 知识点: 1.多个流程使用elif xxxx : 2.if elif else的语句执行顺序从上而下,只要条件满足,if语句就结束了. 3. 缩进:其他语言用{}包含语 ...
- X-WAF简单测试体验
X-WAF 最近才关注到的一款云WAF,花了一些时间搭建了一个环境,并做了一些测试,感觉比较适合新手来练习WAF Bypass. X-WAF是一款适用中.小企业的云WAF系统,让中.小企业也可以非常方 ...
- RF采用SSHLibary库执行sudo命令,提示sudo: sorry, you must have a tty to run sudo错误的解决办法
经了解Execute Command and Start Command两个关键字执行linux命令会新增一个shell,并且可能改变环境配置,如果要确保环境不被改变,则需采用Write和Read方法 ...
- iOS开发--UIButton 设置圆角 边框颜色 点击回调方法
UIButton *signBtn = [UIButton buttonWithType:UIButtonTypeCustom]; signBtn.frame = CGRectMake(, , , ) ...
- Selenium 管理 Cookies
使用 Selenium ,还可以方便地对 Cookies 进行操作,例如获取.添加 .删除 Cookies 等 from selenium import webdriver browser = web ...
- 使用jquery的 $.grep实现es6的filter效果
现在es6是趋势,习惯了es6会方便的多,但是有时候由于项目的限制,会限制es6的使用,在处理数组的时候,往往会想将一个数组进行过滤产生另一个新数组,如es6的filter 如下,我又一个数组,[1, ...
- Linux(Ubuntu) 下如何解压 .tar.gz 文件
在终端输入以下命令即可解压: tar -zxvf YOUR_FILE_NAME.tar.gz 如果出现“权限不够”的错误提示,在命令前加上 sudo ,即 sudo tar -zxvf YOUR_FI ...
- eagle学习汇总
一.原理图编辑器 1. 编辑->全局属性->可以设置全局变量,选择“文本框”,以‘>’开头代表引用全局属性的值. 2. 绘制->Frame->可绘制原理图边框,一般选择“ ...