为什么自上而下的堆构建方法效率低于自下而上,即使它的增长顺序低于 O(n)?
Why is the top down approach of heap construction less efficient than bottom up even though its order of growth is lower O(log n) over O(n)?
O(n) 阶的堆构造自下而上的方法如何? Anany Levitin 在他的书中说,与 O(log n) 阶的自上而下方法相比,这更有效。为什么?
这对我来说似乎是一个错字。
构建堆有两种标准算法。第一种是从一个空堆开始,然后一次一个地重复向其中插入元素。每个单独的插入都需要时间 O(log n),因此我们可以将这种堆构建方式的成本上限设置为 O(n log n)。事实证明,在最坏的情况下,运行时间是 Θ(n log n),如果您以反向排序的顺序插入元素,就会发生这种情况。
另一种方法是 heapify 算法,它通过从其自己的二进制堆中的每个元素开始并逐步将它们合并在一起来直接构建堆。无论输入如何,该算法都会在 O(n) 时间内运行。
第一个算法需要时间 Θ(n log n) 的原因是,如果你查看被插入元素的后半部分,你会发现它们中的每一个都被插入到一个高度为是 Θ(log n),因此每次冒泡的成本可能很高。由于有 n / 2 个元素,每个元素都可能需要 Θ(log n) 的时间来插入,最坏情况下的运行时间是 Θ(n log n)。
另一方面,heapify 算法将大部分时间花在小堆上。一半的元素被插入到高度为 0 的堆中,四分之一插入到高度为 1 的堆中,八分之一插入到高度为 2 的堆中,等等。这意味着大部分工作都花在了将元素插入小堆中,这明显更快。
如果您认为交换是您的基本操作 -
在自上而下的构造中,首先构造树并在 nodes.The 最坏情况下调用 heapify 函数将交换 log n 次(将元素筛选到树的顶部,其中树的高度为是所有 n/2 叶节点的 log n)。这导致 O(n log n) 上限。
在自下而上的构造中,您假设所有叶节点在第一遍中都是有序的,因此现在仅在 n/2 个节点上调用 heapify。在每个级别,可能的交换数量都会增加,但发生交换的节点数量会减少。
例如-
在叶节点正上方的级别,
我们有 n/4 个节点,每个节点最多可以有 1 个交换。
在它的父级别,我们有,
n/8 个节点,每个节点最多可以进行 2 次交换,依此类推。
总而言之,我们将得出自下而上构建堆的 O(n) 效率。
泛指解决问题的方法。尤其是计算机科学算法。
自上而下:
将整个问题分解成两个或更多部分。
找到这些部分的解决方案。
如果这些部分太大而无法作为一个整体解决,请将它们进一步拆分并找到那些 sub-parts 的解决方案。
在成功解决所有部分后,根据如此创建的 sub-problem 层次结构合并解决方案。
在常规的heapify()中,我们对每个节点从上到下进行两次比较,找出三个元素中最大的:
- Parent 节点左 child
- 第一次与第二次比较的较大节点child
自下而上:
将问题分解成尽可能小的(和实用的)部分。
寻找这些小 sub-problem 的解决方案。
迭代地(一次又一次)合并您获得的解决方案,直到您合并所有这些解决方案以获得“大”问题的最终解决方案。方法的主要区别在于拆分与合并。您要么从大开始,然后根据需要“向下”拆分,要么从最小的开始,然后“向上”合并到最终解决方案。
另一方面,Bottom-up Heapsort 仅比较两个 children 并跟随较大的 child 到树的末尾(“top-down” ).从那里,算法返回树根(“bottom-up”)并搜索大于根的第一个元素。从这个位置开始,所有元素向根移动一个位置,根元素放在已经空闲的字段中。
O(n) 阶的堆构造自下而上的方法如何? Anany Levitin 在他的书中说,与 O(log n) 阶的自上而下方法相比,这更有效。为什么?
这对我来说似乎是一个错字。
构建堆有两种标准算法。第一种是从一个空堆开始,然后一次一个地重复向其中插入元素。每个单独的插入都需要时间 O(log n),因此我们可以将这种堆构建方式的成本上限设置为 O(n log n)。事实证明,在最坏的情况下,运行时间是 Θ(n log n),如果您以反向排序的顺序插入元素,就会发生这种情况。
另一种方法是 heapify 算法,它通过从其自己的二进制堆中的每个元素开始并逐步将它们合并在一起来直接构建堆。无论输入如何,该算法都会在 O(n) 时间内运行。
第一个算法需要时间 Θ(n log n) 的原因是,如果你查看被插入元素的后半部分,你会发现它们中的每一个都被插入到一个高度为是 Θ(log n),因此每次冒泡的成本可能很高。由于有 n / 2 个元素,每个元素都可能需要 Θ(log n) 的时间来插入,最坏情况下的运行时间是 Θ(n log n)。
另一方面,heapify 算法将大部分时间花在小堆上。一半的元素被插入到高度为 0 的堆中,四分之一插入到高度为 1 的堆中,八分之一插入到高度为 2 的堆中,等等。这意味着大部分工作都花在了将元素插入小堆中,这明显更快。
如果您认为交换是您的基本操作 -
在自上而下的构造中,首先构造树并在 nodes.The 最坏情况下调用 heapify 函数将交换 log n 次(将元素筛选到树的顶部,其中树的高度为是所有 n/2 叶节点的 log n)。这导致 O(n log n) 上限。
在自下而上的构造中,您假设所有叶节点在第一遍中都是有序的,因此现在仅在 n/2 个节点上调用 heapify。在每个级别,可能的交换数量都会增加,但发生交换的节点数量会减少。
例如- 在叶节点正上方的级别, 我们有 n/4 个节点,每个节点最多可以有 1 个交换。 在它的父级别,我们有, n/8 个节点,每个节点最多可以进行 2 次交换,依此类推。 总而言之,我们将得出自下而上构建堆的 O(n) 效率。
泛指解决问题的方法。尤其是计算机科学算法。
自上而下:
将整个问题分解成两个或更多部分。 找到这些部分的解决方案。 如果这些部分太大而无法作为一个整体解决,请将它们进一步拆分并找到那些 sub-parts 的解决方案。 在成功解决所有部分后,根据如此创建的 sub-problem 层次结构合并解决方案。
在常规的heapify()中,我们对每个节点从上到下进行两次比较,找出三个元素中最大的:
- Parent 节点左 child
- 第一次与第二次比较的较大节点child
自下而上:
将问题分解成尽可能小的(和实用的)部分。 寻找这些小 sub-problem 的解决方案。 迭代地(一次又一次)合并您获得的解决方案,直到您合并所有这些解决方案以获得“大”问题的最终解决方案。方法的主要区别在于拆分与合并。您要么从大开始,然后根据需要“向下”拆分,要么从最小的开始,然后“向上”合并到最终解决方案。
另一方面,Bottom-up Heapsort 仅比较两个 children 并跟随较大的 child 到树的末尾(“top-down” ).从那里,算法返回树根(“bottom-up”)并搜索大于根的第一个元素。从这个位置开始,所有元素向根移动一个位置,根元素放在已经空闲的字段中。