我正在学习抽象数据类型here。最近我一直在阅读有关使用Map(或某些数据结构,如dict)进行散列的内容。
以下是代码的外观:
class HashTable:
def __init__(self):
self.size = 11
self.slots = [None] * self.size
self.data = [None] * self.size
def put(self,key,data):
hashvalue = self.hashfunction(key,len(self.slots))
if self.slots[hashvalue] == None:
self.slots[hashvalue] = key
self.data[hashvalue] = data
else:
if self.slots[hashvalue] == key:
self.data[hashvalue] = data #replace
else:
nextslot = self.rehash(hashvalue,len(self.slots))
while self.slots[nextslot] != None and \
self.slots[nextslot] != key:
nextslot = self.rehash(nextslot,len(self.slots))
if self.slots[nextslot] == None:
self.slots[nextslot]=key
self.data[nextslot]=data
else:
self.data[nextslot] = data #replace
def hashfunction(self,key,size):
return key%size
def rehash(self,oldhash,size):
return (oldhash+1)%size
def get(self,key):
startslot = self.hashfunction(key,len(self.slots))
data = None
stop = False
found = False
position = startslot
while self.slots[position] != None and \
not found and not stop:
if self.slots[position] == key:
found = True
data = self.data[position]
else:
position=self.rehash(position,len(self.slots))
if position == startslot:
stop = True
return data
def __getitem__(self,key):
return self.get(key)
def __setitem__(self,key,data):
self.put(key,data)
现在在教科书中,作者声明哈希表的大小是任意的。见这里:
请注意,已选择哈希表的初始大小 11.虽然这是任意的,但重要的是大小是素数,以便碰撞解决算法可以如此 尽可能高效。
为什么这是武断的?似乎给定的时隙数与可以存储的值的数量直接相关。我知道其他哈希表可能是灵活的,并且能够将更多数据存储到一个数据槽中,但在 THIS 特定示例中,它并不是“任意”的。它确切地存储了多少个值。
我在这里错过了什么吗?
答案 0 :(得分:2)
为什么这是武断的?
因为他本可以选择任何其他小素数。
是的,那是无关紧要的。如果需要增加哈希表,则需要调整大小(重新分配)并重新哈希。这不是作者所说的。似乎插槽的数量与[...]可以存储多少个值直接相关
答案 1 :(得分:1)
The Paramagnetic Croiss回答了你的主要问题。数字11当然意味着你不能重新分配你的表并重新分析你的所有元素,你不能适应超过11个元素,所以很明显它在 意义上并不是随意的。但是在某种意义上它是任意的,只要数字是素数(并且,是的,大于您将要执行的插入数量),作者打算演示的所有内容都会得到相同的结果。 *
*特别是,如果你的元素是自然数,并且你的表大小是素数,并且与最大整数相比足够小,% size
会产生很好的哈希函数。
但是对于你的后续问题:
看起来,制作一个具有更大素数的表将允许您拥有更多可用的插槽,并且需要更少的重新连接,并且在每个插槽中搜索的项目较少(如果您扩展了数据槽可容纳多个值)。这些物品一般会变得更薄。这不正确吗?
如果我理解你,你就没有使用正确的词语,这就是为什么你会得到令人困惑的答案。您的示例代码使用名为rehash
的函数,但这具有误导性。 Rehashing是一种进行探测的方法,但它并不是你做这种方式的方式;你只是在进行线性探测和双重散列的组合。*更常见的是,当人们谈论重组时,他们会谈论你在扩展哈希表之后所做的事情并且不得不重新发布每个值。旧表进入新表。
*当您的哈希函数与key%size
一样简单时,区别是不明确的......
无论如何,是的,更多的负载(如果M桶中有N个元素,你有N / M负载)意味着更多的探测,这是不好的。要获取最极端的元素,在加载1.0时,平均操作必须探测一半表以找到正确的存储桶,使得哈希表与搜索数组的蛮力一样低效。
然而,当你减少负荷时,回报会很快下降。您可以为任何特定的哈希实现绘制精确的曲线,但是您经常使用的经验法则(对于这样的闭合哈希)是将负载降低到2/3以下通常是不值得的。请记住,更大的哈希表既有成本也有好处。我们假设您使用的是具有64字节高速缓存行的32位计算机。因此,11个指针适合单个缓存行;在任何散列操作之后,保证下一个是高速缓存命中。但是17个指针分成两个缓存行;在任何散列操作之后,下一个散列操作只有50%的可能性。*
*当然,实际上你的循环中有足够的空间来为哈希表使用2个缓存行;这就是为什么人们在N为单位数时根本不担心性能的原因......但你可以看到更大的哈希表如何,保留太多的空白空间意味着更多的L1缓存未命中,更多的二级缓存错过了,在最坏的情况下,甚至更多的VM页面未命中。
答案 2 :(得分:0)
好吧,没有人可以预测未来,因为你永远不知道数据结构用户实际将多少值放入容器中。 所以你从一些小的东西开始,不要吃太多的记忆,然后根据需要增加和重新组合。