【问题标题】:Hashtable size said to be arbitrary, why?Hashtable 的大小说是任意的,为什么?
【发布时间】:2015-02-11 22:19:55
【问题描述】:

我正在学习抽象数据类型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 特定示例中,它不仅仅是“任意”。就是可以存储多少个值。

我错过了什么吗?

【问题讨论】:

  • 我认为这是一种“最小散列”——这是你能做的最少的事情,而且仍然有一些你可以称之为散列函数的东西。显然,您可以通过添加一些代码来扩展它,例如,如果散列已满(或者更有可能,如果负载变得太高)来调整散列的大小。这可能是你尝试的一个很好的练习。
  • 它不是关于存储在 HashTable 中的大小,我认为我指的是制作简单的哈希函数。为此使用素数,例如here.
  • 感谢您确认这是多么基本。我认为我对哈希表如何工作的基本知识是有限的,因此会让人感到困惑。不过,看起来,制作一个具有更大素数的表将允许您拥有更多可用插槽,并且需要更少的重新哈希,并且在每个插槽中搜索的项目更少(如果您将数据插槽扩展到持有多个值)。一般来说,这些物品会分散得更薄。这不正确吗?
  • @ApathyBear 您似乎对“重新散列”的含义感到困惑。通常,哈希表允许固定的占用率,例如。 G。 2/3或类似的东西。所以,如果你有 e。 G。一个有 11 个槽的哈希表,在调整大小之前它可以包含 7 个值。当具有 7 个值的 11 槽表被指示插入第 8 个值时,它分配一个更大的表(例如具有 19 个槽的表),重新计算每个旧条目的哈希和槽索引,将旧条目从旧表到新表,将一个新条目插入新表,然后释放旧表。 …
  • @ApathyBear ... 这就是所谓的重新散列过程,随着表大小的增长,它会重复多次。

标签: python map hashmap hashtable


【解决方案1】:

为什么这是任意的?

因为他可以选择任何其他小的素数。

似乎槽的数量与 [...] 可以存储多少值直接相关

是的,这无关紧要。如果你需要增加你的哈希表,你可以调整大小(重新分配)并重新哈希它。这不是作者要说的。

【讨论】:

  • 感谢您的回答。我在我的 OP 中评论了另一个问题。如果您能详细说明一下,那将对我有很大帮助!
【解决方案2】:

The Paramagnetic Croiss 回答了您的主要问题。数字 11 当然意味着如果不重新分配表格并重新散列所有元素,您不能容纳超过 11 个元素,所以显然它在 的意义上不是任意的。但从某种意义上说,只要数字是素数(并且,是的,大于您将要插入的数量),作者打算演示的所有内容都会得到相同的结果,这是任意的。*

* 特别是,如果您的元素是自然数,并且您的表大小是素数,并且与最大整数相比足够小,那么% size 将是一个很好的散列函数。

但是对于您的后续问题:

不过,看起来,使用更大的素数制作一个表可以让您有更多可用的插槽,并且需要更少的重新哈希,并且在每个插槽中搜索的项目更少(如果您扩展了数据槽来保存多个值)。这些物品通常会散布得更薄。这不正确吗?

如果我对您的理解正确,那么您没有使用正确的词,这就是您得到令人困惑的答案的原因。您的示例代码使用了一个名为rehash 的函数,但这是一种误导。重新散列是进行探测的一种方法,但不是你这样做的方式;你只是在做线性探测和双重散列的组合。*更常见的是,当人们谈论重新散列时,他们谈论的是你在扩大散列表后所做的事情,并且必须将旧表中的每个值重新散列到新的。

* 当你的哈希函数像key%size 这样简单时,区别就很模糊了……

无论如何,是的,更多的负载(如果您在 M 个存储桶中有 N 个元素,则您有 N/M 个负载)意味着更多的探测,这很糟糕。取最极端的元素,在负载 1.0 时,平均操作将不得不探查半个表才能找到正确的存储桶,这使得哈希表与暴力搜索数组一样低效。

但是,当您减少负载时,回报会很快下降。您可以为任何特定的哈希实现绘制精确的曲线,但是您通常使用的经验法则(对于像这样的封闭哈希)是,将负载降低到 2/3 以下通常是不值得的。请记住,更大的哈希表有成本也有好处。假设您在一台具有 64 字节高速缓存行的 32 位机器上。因此,11 个指针适合单个高速缓存行;在任何散列操作之后,下一个保证是缓存命中。但是 17 个指针被分成两个缓存行;在任何哈希操作之后,下一个只有 50% 的机会被缓存命中。*

* 当然,实际上你的循环内部有足够的空间来使用 2 个缓存行来创建一个哈希表;这就是为什么当 N 为个位数时,人们通常根本不担心性能……但是您可以看到,对于较大的哈希表,保留过多的空白空间意味着更多的 L1 缓存未命中,更多的 L2 缓存未命中,在最坏的情况下甚至更多的 VM 页面丢失。

【讨论】:

  • 这帮助很大。谢谢!
【解决方案3】:

好吧,没有人可以预测未来,因为您永远不知道数据结构用户实际上会在容器中放入多少值。 所以你从一些小事开始,不要吃太多内存,然后根据需要增加和重新hash。

【讨论】:

    猜你喜欢
    • 2011-02-05
    • 1970-01-01
    • 1970-01-01
    • 2020-01-27
    • 2020-05-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多