当我在大学学习数据结构课程时,我学到了以下公理:
在最坏的情况下,将新数字插入堆需要 O(logn)(取决于作为叶插入时树到达的高度)
< / LI>从空堆开始,使用 n 插入构建一堆 n 节点,总计为 O(n)时间,使用摊销分析
在最坏情况下删除最小值 O(logn)时间(取决于新顶节点在与最后一个叶子交换后达到的低点)
逐个删除所有最小值,直到堆为空,取 O(nlogn)时间复杂度
提醒:&#34; heapsort&#34;的步骤算法是:
我的问题是:为什么在清空堆时,摊销分析技巧不起作用,导致堆排序算法采用 O(nlogn)时间而不是 O(n)时间?
答案 0 :(得分:1)
假设您只是通过比较它们来了解两个对象的相对排名,那么就无法在时间O(n)中将所有元素从二进制堆中出列。如果你能做到这一点,那么你可以通过在时间O(n)中构建一个堆,然后在时间O(n)中出列所有内容,在时间O(n)中对列表进行排序。但是,排序下限表示比较排序,为了正确,平均必须具有Ω(n log n)的运行时间。换句话说,你不能太快从堆中出队,或者你打破了排序障碍。
还有一个问题是,为什么从二进制堆中出列n个元素需要时间O(n log n)而不是更快。这显示有点棘手,但这是基本的想法。考虑一下你在堆上出列的前半部分。查看实际出列的值,并考虑它们在堆中的位置。排除底行的那些,所有出列的其他东西必须一次渗透到堆顶部一次,以便被移除。您可以显示堆中有足够的元素来保证单独使用时间Ω(n log n),因为这些节点中大约有一半将位于树的深处。这解释了为什么摊销的论证不起作用 - 你不断地将深层节点拉到堆上,因此节点必须行进的总距离很大。将其与heapify操作进行比较,其中大多数节点行进的距离非常短。
答案 1 :(得分:1)
让我“数学地”向您展示我们如何计算将任意数组转换为堆(让我称之为“堆构建”)然后使用堆排序对其进行排序的复杂性。
为了将数组转换为堆,我们必须查看每个有子节点的节点并“堆化”(接收)该节点。您应该问问自己我们进行了多少次比较;如果你仔细想想,你会看到(h = 树高):
让我们举个例子。假设有一个包含 15 个元素的数组,即树的高度为 h = log2(15) = 3:
好的,一般来说:
T(n) = sum(i=0 to h) 2^i * (h-i)
但是如果你还记得 h = log2(n),我们有
T(n) = sum(i=0 to log2(n)) 2^i * (log2(n) - i) =~ 2n
现在,这里的分析非常相似。每次我们“删除”最大元素(根)时,我们都会移动到树中最后一片叶子的根,堆化它并重复直到最后。那么,我们在这里进行了多少次比较?
让我们举个例子。假设有一个包含 15 个元素的数组,即树的高度为 h = log2(15) = 3:
好的,一般来说:
T(n) = sum(i=0 to h) 2^i * i
但是如果你还记得 h = log2(n),我们有
T(n) = sum(i=0 to log2(n)) 2^i * i =~ 2nlogn
直观地,您可以看到 heapsort 无法“摊销”他的成本,因为每次我们增加节点数量时,我们必须做更多的比较,而我们在堆构建功能中正好相反!你可以在这里看到:
所以: