我了解了如何通过密钥访问您的收藏集。但是,哈希函数本身在幕后有很多操作,不是吗?
假设您有一个非常有效的很好的哈希函数,它仍然可能需要执行许多操作。
可以解释吗?
我了解了如何通过密钥访问您的收藏集。但是,哈希函数本身在幕后有很多操作,不是吗?
假设您有一个非常有效的很好的哈希函数,它仍然可能需要执行许多操作。
可以解释吗?
Answers:
在
HashFunc
本身有很多幕后操作
确实是这样。但是,这些操作的数量取决于键的大小,而不取决于插入键的哈希表的大小:对于具有十个或10个表的键,计算哈希函数的操作数是相同的一万个条目。
这就是为什么通常将哈希函数的调用视为O(1)的原因。这对于固定大小的键(整数值和固定长度的字符串)可以很好地工作。它还为具有实际上限的大小可变的按键提供了不错的近似值。
但是,通常,哈希表的访问时间为O(k),其中k
哈希键大小的上限为。
n
除非至少一个项目至少由log(n)
位表示,否则不可能有一个包含不同项目的哈希表。
the number of these operations depends on the size of the key
以及散列数据的大小。
k
不需要是一个上限。查找时间在密钥大小中是线性的,因此密钥大小确实O(k)
在哪里k
。如果k
被理解为上限,则实际上是O(1)
。
O(1)
并不意味着即时。O(1)
表示常量,不考虑数据大小。散列函数需要花费一定的时间,但是该时间不会随集合的大小扩展。
GetHashCode()
以某种方式组合这些项目的哈希码。如果我要实现这样的类,那么对于最初的实现,我将GetHashCode()
完全像那样实现。我当然也会在以后更改它。
这意味着无论您的集合有多大,都将花费几乎相同的时间来检索其任何成员。
因此,换句话说,具有5个成员的Dictionary将假设coud需要大约0.002 ms来访问其中一个成员,而由25个成员组成的Dictionary应当花费相似的时间。大O表示算法的复杂度超过集合大小,而不是实际的语句或执行的函数
如果将字典/地图实现为HashMap
,则它的最佳情况下复杂度为O(1)
,因为在没有键冲突的情况下,最好的情况是它需要精确计算要检索的关键元素的哈希码。
一个哈希地图可能有最坏情况下运行复杂的O(n)
,如果你有很多关键的碰撞或非常糟糕的散列函数,因为在这种情况下,它会降低到保存数据的整个阵列的线性扫描。
另外,O(1)
并不意味着立即,而是意味着它具有恒定的数量。因此,为字典选择正确的实现方式也可能取决于集合中元素的数量,因为如果只有很少的条目,则该函数具有非常高的恒定成本将变得更加糟糕。
这就是为什么字典/地图在不同情况下的实现方式不同的原因。对于Java,有多种不同的实现,C ++使用红色/黑色树,等等。您是根据数据数量和最佳/平均/最坏情况下的运行效率来选择它们的。
HashMap
在检测到多个冲突的情况下诉诸平衡树。
请参阅文章“ O(1)访问时间”是什么意思?
散列函数中的操作数无关紧要,只要集合中每个元素花费相同(恒定)的时间即可。例如,访问2个元素的集合中的一个元素需要0.01毫秒,但是访问2,000,000,000个元素的集合中的一个元素也需要0.01毫秒。尽管哈希函数可以包含数百个if语句和多个计算。
从文档:
由于T:System.Collections.Generic.Dictionary`2类被实现为哈希表,因此使用键检索值非常快,接近O(1)。
因此它可以是O(1),但可能更慢。在这里,您可以找到有关哈希表性能的另一个线程:哈希表-为什么它比数组快?
一旦考虑到越来越大的字典占用更多的内存,进一步降低缓存层次结构并最终减慢磁盘上的交换空间这一事实,就很难说这确实是O(1)。字典的性能会随着它的增大而变慢,这可能会增加O(log N)的时间复杂度。不相信我吗 自己尝试使用1、100、1000、10000等字典元素(最多1000亿),并测量实际查找一个元素所需的时间。
但是,如果您做一个简化的假设,即系统中的所有内存都是随机访问内存,并且可以在恒定时间内访问,那么您可以声明字典为O(1)。这种假设是很普遍的,即使对于具有磁盘交换空间的任何计算机而言并非如此,并且在各种情况下,考虑到CPU缓存的不同级别,这种假设仍然值得商de。
the growth
使用不同输入量度复杂度的方法。这与您有多少操作无关。例如:使用1值,您有x
秒,使用n
值,您需要roughly
x*n
秒=> O(n)。x
可能将许多操作组合在一起。