在学校,我们正在学习Java中的排序算法,我的作业是Heap Sort。我做了我的阅读,我试图尽可能多地发现,但似乎我无法掌握这个概念。
我不是要求你给我写一个Java程序,如果你可以像Heap Sort那样简单地向我解释。
答案 0 :(得分:118)
是的,所以基本上你采取堆并拉出堆中的第一个节点 - 因为第一个节点保证最大/最小,具体取决于排序方向。棘手的是首先重新平衡/创建堆。
我需要两个步骤来理解堆进程 - 首先将其视为树,绕过它,然后将该树转换为数组,以便它可能有用。
第二部分基本上首先遍历树宽度,从左到右将每个元素添加到数组中。所以下面的树:
73
7 12
2 4 9 10
1
将是{73,7,12,2,4,9,10,1}
第一部分需要两个步骤:
所以要堆积一个数字列表,将每个数字添加到堆中,然后按顺序执行这两个步骤。
要在上面创建我的堆,我将首先添加10个 - 它是唯一的节点,所以无所事事。 在左边添加12作为孩子:
10
12
这满足1,但不满足2,所以我会将它们换成圆形:
12
10
添加7 - 无所事事
12
10 7
添加73
12
10 7
73
10< 73所以需要交换那些:
12
73 7
10
12< 73所以需要交换那些:
73
12 7
10
添加2 - 无所事事
73
12 7
10 2
添加4 - 无所事事
73
12 7
10 2 4
添加9
73
12 7
10 2 4 9
7< 9 - 交换
73
12 9
10 2 4 7
添加1 - 无所事事
73
12 9
10 2 4 7
1
我们有堆:D
现在你只需从顶部删除每个元素,每次将最后一个元素交换到树的顶部,然后重新平衡树:
将73取代 - 取而代之的是
1
12 9
10 2 4 7
1< 12 - 所以互换它们
12
1 9
10 2 4 7
1< 10 - 所以互换它们
12
10 9
1 2 4 7
取12关 - 用7替换
7
10 9
1 2 4
7< 10 - 交换它们
10
7 9
1 2 4
取10关 - 用4替换
4
7 9
1 2
4< 7 - 交换
7
4 9
1 2
7< 9 - 交换
9
4 7
1 2
取消9 - 用2替换
2
4 7
1
2< 4 - 交换它们
4
2 7
1
4< 7 - 交换它们
7
2 4
1
取7关 - 替换为1
1
2 4
1< 4 - 交换它们
4
2 1
取4 - 替换为1
1
2
1< 2 - 交换它们
2
1
取2 - 替换为1
1
拿1
排序列表瞧。
答案 1 :(得分:31)
考虑堆排序的一种方法是作为选择排序的巧妙优化版本。在选择排序中,排序通过重复查找尚未正确放置的最大元素,然后将其放入数组中的下一个正确位置来工作。然而,选择排序在时间O(n 2 )中运行,因为它必须进行n轮寻找一堆中最大的元素(并且最多可以有n个不同的元素来查看)和把它放到位。
直观地说,堆排序的工作原理是构建一个名为binary heap的特殊数据结构,该结构可以加速查找未放置数组元素中的最大元素。二进制堆支持以下操作:
在非常高的级别,算法的工作原理如下:
这会对数组进行排序,因为 Delete-Max 返回的元素按降序排列。删除所有元素后,将对数组进行排序。
堆排序很有效,因为堆上的插入和删除最大值操作都在O(log n)时间内运行,这意味着n次插入和删除可以是在O(n log n)时间内在堆上完成。 A more precise analysis可用于表明,无论输入数组如何,它都需要Θ(n log n)时间。
通常,堆排序采用两种主要的优化方式。首先,通过将数组本身视为堆的压缩表示,堆通常为built up in-place inside the array。如果你看一下heapsort实现,通常会看到基于乘以除以2的数组索引的异常使用;这些访问是有效的,因为它们将数组视为压缩数据结构。因此,该算法仅需要O(1)辅助存储空间。
其次,不是一次构建堆一个元素,而是通常构建堆using a specialized algorithm,它在时间Θ(n)中运行以就地构建堆。有趣的是,在某些情况下,这最终会使代码更容易阅读,因为代码可以重复使用,但算法本身对于理解和分析来说有点棘手。
您有时会看到使用ternary heap完成的操作。这样做的优点是平均速度稍快一些,但是如果你发现使用它的heapsort实现而不知道你在看什么,那么阅读起来相当棘手。其他算法也使用相同的通用结构,但使用更复杂的堆结构。 Smoothsort使用更复杂的堆来获得O(n)最佳行为,同时保持O(1)空间使用和O(n log n)最坏情况行为。 Poplar sort类似于smoothsort,但具有O(log n)空间使用率和稍好的性能。人们甚至可以想到像insertion sort and selection sort as heap sort variants这样的经典排序算法。
一旦你对heapsort有了更好的把握,你可能需要研究introsort算法,该算法结合了quicksort,heapsort和insert sort来生成一种极快的排序算法,结合了quicksort的强度(快速)平均排序),heapsort(优秀的最坏情况行为)和插入排序(小数组的快速排序)。 Introsort是C ++的std::sort
函数的许多实现中使用的东西,并且一旦你有一个工作的heapsort就不会很难实现。
希望这有帮助!
答案 2 :(得分:2)
假设您有一个特殊的数据结构(称为堆),它允许您存储数字列表,并允许您在O(lg n)
时间内检索并删除最小的数字。
您是否看到这导致了一种非常简单的排序算法?
困难的部分(实际上并不那么难)正在实现堆。
答案 3 :(得分:1)
我记得我的算法分析教授告诉我们堆排序算法的作用就像一堆碎石:
想象一下,你有一个装满砾石的袋子,你把它倒在地板上:较大的石头可能会滚到底部,较小的石头(或沙子)会留在上面。
现在,您可以将堆的最顶层保存在堆的最小值处。把剩下的堆放在你的麻袋里,重复一遍。 (或者你可以采取相反的方法并抓住你在地板上看到的最大的石头,示例仍然有效)
这或多或少是我所知道的解释堆排序如何工作的简单方法。
答案 4 :(得分:1)
也许交互式跟踪可以帮助您更好地理解算法。这是demo。
答案 5 :(得分:1)
我会看到我如何回答这个问题,因为我对堆排序的解释以及堆的内容会有点......
......呃,可怕的。
无论如何,首先,我们最好检查堆是什么:
从Wikipedia获取,堆是:
在计算机科学中,堆是一种专门的基于树的数据结构,它满足堆属性:如果B是A的子节点,则键(A)≥键(B)。这意味着具有最大密钥的元素始终位于根节点中,因此这样的堆有时称为最大堆。 (或者,如果比较相反,则最小元素始终位于根节点中,从而产生最小堆。)
实际上,堆是一个二叉树,因此任何节点的所有子节点都小于该节点。
现在,堆排序是一种O( n lg(n))排序算法。您可以稍微阅读一下here和here。它几乎可以通过将您尝试排序的所有元素放入堆中,然后构建从最大元素到最小元素的排序数组。您将继续重构堆,并且由于最大元素始终位于堆的顶部(根),因此您可以继续使用该元素并将其放在已排序数组的后面。 (也就是说,你将反向构建排序数组)
为什么这个算法是O( n lg(n))?因为堆上的所有操作都是O( lg(n)),因此,您将执行 n 操作,从而导致总的运行时间为O( n lg(n))。
我希望我可怕的咆哮帮助你!这有点罗嗦;抱歉...
答案 6 :(得分:0)
堆排序包括最简单的逻辑,时间复杂度为O(nlogn),空间复杂度为O(1)
public class HeapSort {
public static void main(String[] args) {
Integer [] a={12,32,33,8,54,34,35,26,43,88,45};
HeapS(a,a.length-1);
System.out.println(Arrays.asList(a));
}
private static void HeapS(Integer[] a, int l) {
if(l<=0)
return;
for (int i = l/2-1; i >=0 ; i--) {
int index=a[2*i+1]>a[2*i+2]?2*i+1:2*i+2;
if(a[index]>a[i]){
int temp=a[index];
a[index]=a[i];
a[i]=temp;
}
}
int temp=a[l];
a[l]=a[0];
a[0]=temp;
HeapS(a,l-1);
}
}