python hash table_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 的映射关系

"""线性表结构"""

classLinearMap(object):def __init__(self):

self.items=[]#往表中添加元素

defadd(self, k, v):

self.items.append((k,v))#线性方式查找元素

defget(self, k):for key, value inself.items:if key == k: #键存在,返回值,否则抛出异常

returnvalueraiseKeyError'''我们可以在使用add添加元素时让items列表保持有序,而在使用get时采取二分查找方式,时间复杂度为O(log n)。

然而往列表中插入一个新元素实际上是一个线性操作,所以这种方法并非最好的方法。

同时,我们仍然没有达到常数查找时间的要求。'''

改进版本:

尽管get操作的增长依然是线性,但BetterMap类使得我们离哈希表更近一步:

'''将总查询表分割为若干段较小的列表,比如100个子段。

通过hash函数求出某个键的哈希值,再通过计算,得到往哪个子段中添加或查找。

相对于从头开始搜索列表,时间会极大的缩短。'''

classBetterMap(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对象),进行添加和查找

defadd(self, k, v):

m=self.find_map(k)

m.add(k,v)defget(self, k):

m=self.find_map(k)return m.get(k)

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

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

Hashtable的实现

classHashMap(object):def __init__(self):#初始化总表为,容量为2的表格(含两个子表)

self.maps = BetterMap(2)

self.num= 0 #表中数据个数

defget(self,k):returnself.maps.get(k)defadd(self, k, v):#若当前元素数量达到临界值(子表总数)时,进行重排操作

#对总表进行扩张,增加子表的个数为当前元素个数的两倍!

if self.num ==len(self.maps.maps):

self.resize()#往重排过后的 self.map 添加新的元素

self.maps.add(k, v)

self.num+= 1

defresize(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)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值