关联数组通常使用Hashtables实现。但最近,我才知道它们也可以使用Trees实现。有人可以解释如何使用树实现吗?
最后,关于哈希表的一个一般性问题。人们说搜索时间在哈希表中是O(1)。但是当我们说O(1)时,我们是否考虑在查找哈希值之前计算哈希值?如果我们的键是字符串,那么,我们需要访问字符串的所有字符以找到哈希值。那么,要搜索一个元素,是否需要O(n)+ O(1)时间?
答案 0 :(得分:3)
散列表解决方案将获取存储在其中的对象的散列(重要的是要注意散列是基元,通常是整数或长整数)并将该散列用作数组索引,并存储对象的引用数组中的索引。要解决冲突问题,此数组中的索引通常包含保存实际引用的链接列表节点。
检查对象是否在数组中就像散列它一样简单,查看散列引用的索引,并比较与那里的每个对象的相等性,这是一个以分摊的常量时间运行的操作,因为哈希表如果碰撞开始累积,可能会变大。
现在提问。
我们应该使用简单的二叉树还是BST?
BST代表二进制搜索树。它与“简单”二叉树之间的区别在于它是严格排序的。最佳实现还将尝试保持树平衡。通过平衡的有序树搜索比无序树更容易,因为关于目标元素的位置的某些假设是无法在无序树中进行的。如果可行,你应该使用BST。
我们如何表示树中的键?我们是否计算了键上的哈希函数并根据整数哈希值插入(键,值)?
这就是哈希表的作用。由于可能发生冲突,甚至哈希表也必须按值存储密钥以进行相等性检查。 BST根本不存储哈希,因为在所有非简单的情况下,从哈希值确定排序顺序将非常困难。您可以使用(键,值)对而不进行任何散列。
如果我们假设我们计算哈希值并插入树中,为什么人们会说树或有序?它保留了什么顺序以及如何保存?这个订单给我们带来了什么?
正如您所注意到的那样,它不起作用。所以我们存储密钥的值而不是哈希值。对树进行排序(与无序树相对)在搜索时给出了巨大的优势(O(log(N))而不是O(N))。在无序结构中,必须彻底搜索某些特定元素,因为它可以驻留在结构中的任何位置。在有序结构中,它只存在于值小于其值的键上方,以及值大于其值的键下方。考虑一本教科书。如果页面是随机顺序,您是否会更容易或更难跳到特定页面?
如果我们的键是字符串,那么,我们需要访问字符串的所有字符以找到hashvalue。那么,要搜索一个元素,是否需要O(n)+ O(1)时间?
之前我问过自己同样的问题,实际上这取决于哈希函数。 “摊销常数”查询的最坏情况时间是:
O(H) + O(C)
O(H)是散列函数的最坏情况复杂度。智能哈希函数可能只查看字符串的前几十个字符,或最后几个字符,或中间的某些字符或其他内容。散列函数不必是安全的,它只需要是确定性的(即为相同的对象返回相同的值)。无论你的功能有多好,无论如何你都会遇到冲突,所以如果你可以做一些额外的工作以获得更多可碰撞的功能,那么它通常是值得的。
O(C)是比较密钥的最坏情况复杂性。不成功的查找知道没有匹配,因为表中没有其哈希值的条目。然而,成功的查找总是必须将提供的密钥与表中的密钥进行比较,否则存在密钥实际上不是匹配而仅仅是冲突的可能性。最糟糕的情况是,如果一个哈希值存在多个冲突,则必须将所提供的密钥一个接一个地与所有存储的冲突密钥进行比较,直到找到匹配或所有比较都失败。这就是为什么它只是分摊常数时间而不仅仅是恒定时间:随着表的增长,碰撞的机会减少,所以这种情况发生频率降低,搜索某些元素集合所需的平均时间总是倾向于常数
答案 1 :(得分:0)
当人们说散列是O(1)(或者你可以说O(1 + 0n)并且树是O(log n)时,它们意味着n是集合的大小。 你是对的,它需要O(m)(其中m是当前检查的字符串的长度)aditional工作,但通常m有一些上限,n往往变大。因此,对于两种实现,m都可以被认为是常量。 而m在绝对意义上更有影响力。因为您在访问的每个节点中都与密钥进行比较,所以在散列中您只计算散列并将整个密钥与由散列确定的一个存储桶中的所有值进行比较(具有良好的散列函数和足够大的表,通常应该只有一个值)