如果siftDown比siftUp好,我们为什么要这样做?

时间:2016-08-23 07:32:31

标签: java algorithm data-structures heap

在阅读Why siftDown is better than siftUp in heapify?等问题后,我得到了印象

  1. siftDown()优于siftUp()
  2. siftDown()总是可以替换siftUp(),反之亦然
  3. 为什么很多堆结构实现都有在insert()上调用的siftUp()?维基百科article拥有它。

2 个答案:

答案 0 :(得分:3)

向下筛选元素的BuildHeap方法是获取现有数组并将其转换为堆的最快的已知方法。它比一次插入一个项目更快,而且比从底部向上筛选元素更快。

但是,一旦构建了堆,并且您正在对数据结构进行一系列插入和删除,则在底部插入项目并将其向上筛选比在顶部插入并向下筛选更快

请记住,在n个项目的堆中,n / 2个项目位于叶级别。这意味着当您插入一个项目(通过将其添加为下一个叶​​子)时,它有50%的可能性不会被筛选:它已经在适当的位置。它有25%的可能性属于下一级别。当你在堆中向上移动时,项目筛选到该级别的概率会降低50%。

现在,您可以编写堆代码,以便始终在顶部执行插入操作,但概率对您不利。您插入的项目仍有50%的可能性最终会出现在叶级别。除非你在顶部插入,否则你将采用log(n)交换来实现它。

所以,如果你在底部插入并筛选:

50% of the time you make 0 swaps
25% of the time you make 1 swap
12.5% of the time you make 2 swaps
...

如果您插入顶部并向下筛选:

50% of the time you make log(n) swaps
25% of the time you make log(n)-1 swaps
12.5% of the time you make log(n)-2 swaps
...

想想看,它比那更糟糕。因为如果您正在插入一个项目并且它最终落在中间的某个位置,那么您必须将其移位的项目移除并将其 it 向下移动。这将最终导致整个事情发生变化。最后,在顶部插入总是花费你log(n)交换,因为你最终必须在数组的第(n + 1)个位置放置某些东西(即你必须添加一个项)。

应该很清楚,您不希望插入顶部,但在删除根时必须执行类似的操作。

删除根时,取出堆中的最后一项,将其放在根位置,然后将其向下筛选。考虑到你从叶子级别得到它,并且考虑到叶子级别包含一半的项目,有一个非常好的机会(略好于50%),它将最终回到叶级别。请注意,这不会始终导致log(n)交换,因为您没有插入项目;你只是重新调整堆。

顺便说一下,这就是为什么在编写良好的二进制堆实现中,删除根元素比插入新项更昂贵。

答案 1 :(得分:0)

[编辑此答案,以便人们不必阅读评论]

似乎让您感到困惑的是使用一定数量的输入从头开始构建树的方法。

这种算法首先创建一个随机树,然后从下到上工作,然后向下筛选它可以找到的任何东西。该算法比插入每个元素并将其筛选更快。

但是,insert方法本身只插入一个元素,而不是它们的整个列表。因此,构建树和插入一个元素之间存在差异。

当您插入一个元素并使用相同的算法时,您会进行大量无意义的比较,因为您已经知道树的大部分已经在工作。在每次迭代中(从下到上),总是只有一个可能发生变化的子树(带有新输入的子树)。因此,这是唯一真正需要关注的问题。

由于树的其余部分不是随机的,这也意味着在推送新输入后,您不需要进行筛选。它下面的一切都已经有序了。

所以最后,即使你使用算法插入一个单独的元素,你所做的只是一个简单的siftUp()操作,需要进行大量不必要的额外比较。