对heapsort的直观理解?

时间:2012-01-20 08:06:21

标签: java algorithm sorting heapsort

在学校,我们正在学习Java中的排序算法,我的作业是Heap Sort。我做了我的阅读,我试图尽可能多地发现,但似乎我无法掌握这个概念。

我不是要求你给我写一个Java程序,如果你可以像Heap Sort那样简单地向我解释。

7 个答案:

答案 0 :(得分:118)

是的,所以基本上你采取堆并拉出堆中的第一个节点 - 因为第一个节点保证最大/最小,具体取决于排序方向。棘手的是首先重新平衡/创建堆。

我需要两个步骤来理解堆进程 - 首先将其视为树,绕过它,然后将该树转换为数组,以便它可能有用。

第二部分基本上首先遍历树宽度,从左到右将每个元素添加到数组中。所以下面的树:

                                    73                          
                                 7      12          
                               2   4  9   10    
                             1          

将是{73,7,12,2,4,9,10,1}

第一部分需要两个步骤:

  1. 确保每个节点都有两个子节点(除非您没有足够的节点来执行此操作,如上图所示。
  2. 确保每个节点都比其子节点更大(如果先排序最小,则更小)。
  3. 所以要堆积一个数字列表,将每个数字添加到堆中,然后按顺序执行这两个步骤。

    要在上面创建我的堆,我将首先添加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 ,删除并返回堆中最大的元素。

在非常高的级别,算法的工作原理如下:

  • 将数组的每个元素插入到新的二进制堆。
  • 对于i = n降至1:
    • 在堆上调用 Delete-Max 以获取堆中最大的元素。
    • 将此元素写入位置i。

这会对数组进行排序,因为 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))排序算法。您可以稍微阅读一下herehere。它几乎可以通过将您尝试排序的所有元素放入堆中,然后构建从最大元素到最小元素的排序数组。您将继续重构堆,并且由于最大元素始终位于堆的顶部(根),因此您可以继续使用该元素并将其放在已排序数组的后面。 (也就是说,你将反向构建排序数组)

为什么这个算法是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);

  }
}