Answers:
请记住,二叉搜索树(基于引用)是内存有效的。他们不会保留比他们需要更多的内存。
例如,如果哈希函数具有range R(h) = 0...100
,那么即使您仅哈希20个元素,也需要分配100个(指向)元素的数组。如果要使用二进制搜索树存储相同的信息,则只会分配所需的空间以及一些有关链接的元数据。
其他人没有指出的一个优点是,二进制搜索树使您可以有效地进行范围搜索。
为了说明我的想法,我想举一个极端的例子。假设您要获取所有键在0到5000之间的元素。实际上,只有一个这样的元素,还有10000个其他键不在此范围内的元素。BST可以非常有效地进行范围搜索,因为它不搜索不可能获得答案的子树。
同时,如何在哈希表中进行范围搜索?您要么需要遍历每个存储桶空间(即O(n)),要么必须查找1,2,3,4 ...是否多达5000个。(0和5000之间的键是一个无限集吗?例如,键可以是小数)
二叉树的一个“优点”是可以遍历它以按顺序列出所有元素。对于哈希表,这不是不可能的,但不是设计成哈希结构的正常操作。
除了所有其他好的评论:
与二叉树相比,哈希表通常具有更好的缓存行为,需要较少的内存读取。对于哈希表,通常只有在访问拥有数据的引用之前,才需要进行一次读取。如果二进制树是平衡变体,则它需要k * lg(n)数量级的内存来读取某个常数k。
另一方面,如果敌人知道您的哈希函数,则该敌人可以强制您的哈希表进行碰撞,从而大大降低其性能。解决方法是从一个族中随机选择哈希函数,但是BST没有此缺点。另外,当哈希表压力过大时,您通常倾向于扩大并重新分配哈希表,这可能是一项昂贵的操作。BST的行为在这里更简单,并且不会突然分配大量数据并进行重新哈希操作。
树往往是最终的平均数据结构。它们可以充当列表,可以轻松拆分以进行并行操作,可以按照O(lg n)的顺序快速移除,插入和查找。他们没有做得特别好,但是也没有任何过分的不良行为。
最后,与散列表相比,BST在(纯)功能语言中更容易实现,并且它们不需要实施破坏性更新(上述Pascal 的持久性参数)。
BSTs are much easier to implement in (pure) functional languages compared to hash-tables
-真的吗?我想现在学习一种功能语言!
与哈希表相比,二叉树的主要优势在于,二叉树为您提供了哈希表无法(轻松,快速)执行的两个附加操作
查找最接近(不一定等于)某个任意键值(或最接近上/下)的元素
以排序的顺序遍历树的内容
两者是连接的-二叉树按排序顺序保留其内容,因此易于执行需要排序顺序的事情。
(平衡的)二叉搜索树还具有以下优势:渐进复杂度实际上是一个上限,而哈希表的“恒定”时间是摊销时间:如果哈希函数不合适,最终可能会降级为线性时间,而不是常量。
哈希表在首次创建时会占用更多空间-它将有可用的插槽供尚未插入的元素使用(无论是否插入过),二叉搜索树的大小只能满足需要是。另外,当散列表需要更多空间时,将其扩展到另一个结构可能会很耗时,但这可能取决于实现。
可以使用持久性接口来实现二叉搜索树,在该接口中返回新树,但旧树继续存在。精心实施后,新旧树共享它们的大部分节点。您不能使用标准哈希表执行此操作。
二叉树的搜索和插入速度较慢,但具有中缀遍历的非常好功能,这实际上意味着您可以按排序顺序遍历树的节点。
遍历哈希表的条目并没有多大意义,因为它们都分散在内存中。
摘自《破解编码访谈》,第六版
我们可以使用平衡二进制搜索树(BST)来实现哈希表。这为我们提供了O(log n)查找时间。这样做的好处是可以使用更少的空间,因为我们不再分配大型数组。我们还可以按顺序遍历键,有时这很有用。
如果要以排序方式访问数据,则必须与哈希表并行维护排序列表。一个很好的例子是.Net中的Dictionary。(请参阅http://msdn.microsoft.com/en-us/library/3fcwy8h6.aspx)。
这不仅会降低插入速度,而且会比b树消耗更多的内存。
此外,由于对b树进行了排序,因此很容易找到结果范围,或者执行并集或合并。
它还取决于使用情况,Hash允许定位精确匹配。如果要查询范围,则选择BST。假设您有很多数据e1,e2,e3 ..... en。
使用哈希表,您可以在恒定时间内定位任何元素。
如果要查找大于e41且小于e8的范围值,BST可以快速找到它。
关键是用于避免冲突的哈希函数。当然,我们不能完全避免冲突,在这种情况下,我们会采用链接或其他方法。这使得在最坏的情况下检索不再是恒定时间。
填充完毕后,哈希表必须增加其存储桶大小,并再次复制所有元素。这是BST所不具备的额外费用。
哈希表不适用于索引。搜索范围时,BST更好。这就是为什么大多数数据库索引使用B +树而不是哈希表的原因
如果在键上定义了一些总顺序(键是可比较的),并且您想保留顺序信息,则二进制搜索树是实现字典的不错选择。
由于BST保留订单信息,因此它为您提供了四个附加的动态设置操作,这些操作无法使用哈希表(有效地)执行。这些操作是:
像每个BST操作一样,所有这些操作的时间复杂度均为O(H)。此外,所有存储的密钥仍在BST中排序,因此使您仅需按顺序遍历树即可获得排序的密钥序列。
总而言之,如果您只想执行操作插入,删除和删除操作,则哈希表在性能上(大多数时候)是无与伦比的。但是,如果要执行上面列出的任何或所有操作,则应使用BST,最好使用自平衡BST。
哈希图是一个集合关联数组。因此,您的输入值数组将合并到存储桶中。在开放式编址方案中,您有一个指向存储桶的指针,并且每次向存储桶中添加新值时,都会发现存储桶中的可用空间。有几种方法可以执行此操作-从存储桶的开头开始,然后每次递增指针并测试其是否被占用。这称为线性探测。然后,您可以进行类似于add之类的二进制搜索,在其中您将存储桶开头之间的差值加倍,并且在每次搜索可用空间时将其加倍或递减。这称为二次探测。好。现在,这两种方法的问题在于,如果存储桶溢出到下一个存储桶地址,则您需要-
好。但是,如果您使用链表,应该不会有这样的问题吧?是的,在链接列表中您没有这个问题。考虑到每个存储桶均以链接列表开头,并且如果存储桶中有100个元素,则需要遍历这100个元素以到达链接列表的末尾,因此List.add(Element E)将花费一些时间-
链表实现的优点是,您不需要像开放式寻址实现那样对所有存储区进行内存分配操作和O(N)传输/复制。
因此,最小化O(N)操作的方法是将实现转换为二进制搜索树的实现,其中查找操作为O(log(N)),然后根据元素的值将元素添加到其位置。BST的附加功能是可以排序!