【问题标题】:An intuitive understanding of heapsort?对堆排序的直观理解?
【发布时间】:2012-02-14 20:08:40
【问题描述】:

在学校,我们目前正在学习 Java 中的排序算法,我的作业是堆排序。我做了我的阅读,我试图尽可能多地找出,但似乎我无法理解这个概念。

我并不是要你给我写一个 Java 程序,如果你能尽可能简单地向我解释一下堆排序是如何工作的。

【问题讨论】:

  • 如果不知道你没有得到什么,我会说我可能会写你已经读过的东西(充其量)你能解释一下你不明白的地方吗?
  • 告诉我们您的理解以及您遇到的问题。 “堆是具有这些和这些约束的二叉树,这意味着对于一些高度为 H 的树,这些东西成立,这基本上意味着其他一些东西随之而来。所以,最小堆只是每个节点的孩子都有的地方与其父级的特定关系,但我不明白您如何在堆排序中执行某些特定的关键步骤。”
  • 你把你读到的东西链接起来,提到你不明白的部分,然后我们再讨论这些怎么样?

标签: java algorithm sorting heapsort


【解决方案1】:

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

我需要两个步骤来理解堆过程 - 首先将其视为一棵树,仔细研究它,然后将这棵树变成一个数组,以便它有用。

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

                                    73                          
                                 7      12          
                               2   4  9   10    
                             1          

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

第一部分需要两个步骤:

  1. 确保每个节点都有两个子节点(除非您没有足够的节点来执行上述树中的操作。
  2. 确保每个节点都比其子节点更大(或者如果先排序 min 则更小)。

所以要堆积一个数字列表,您将每个数字添加到堆中,然后按顺序执行这两个步骤。

要在上面创建我的堆,我将首先添加 10 - 这是唯一的节点,所以无事可做。 添加 12 作为左边的孩子:

    10
  12

这满足 1,但不满足 2,所以我将它们交换:

    12
  10

加 7 - 无所事事

    12
  10  7

添加 73

          12
       10     7
    73

10

          12
       73     7
    10

12

          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

          73
       12     9
    10   2  4   7

加 1 - 无所事事

          73
       12     9
    10   2  4   7
  1

我们有我们的堆 :D

现在您只需从顶部删除每个元素,每次将最后一个元素交换到树的顶部,然后重新平衡树:

去掉 73 - 把 1 放在它的位置

          1
       12     9
    10   2  4   7

1

          12
        1    9
    10   2  4   7

1

          12
       10     9
     1   2  4   7

去掉 12 - 替换为 7

          7
       10     9
     1   2  4   

7

          10
       7     9
     1   2  4   

去掉 10 - 替换为 4

          4
       7     9
    1   2  

4

          7
       4     9
    1   2  

7

          9
       4     7
    1   2 

去掉 9 - 替换为 2

          2
       4     7
    1   

2

          4
       2     7
    1  

4

          7
       2     4
    1  

去掉 7 - 替换为 1

          1
       2     4

1

          4
       2     1

取 4 - 替换为 1

          1
       2

1

          2
       1

取 2 - 替换为 1

          1

拍1

排序列表瞧。

【讨论】:

  • 优秀的一步一步的解释!描述得这么透彻,需要极大的耐心!
  • 谢谢你——我只是在推特上抱怨简短的答案让我得到很多分,而冗长的答案让我得到这么少的分数,但是教授的表扬让这一切都变得有价值;)
  • 非常感谢。我非常感谢您为此付出的努力。
【解决方案2】:

将堆排序视为一种巧妙优化的选择排序版本。在选择排序中,排序的工作原理是反复查找尚未正确放置的最大元素,然后将其放入数组中的下一个正确位置。但是,选择排序在 O(n2) 时间内运行,因为它必须执行 n 轮从一堆中找到最大的元素(并且可以查看多达 n 个不同的元素)并且将其放置到位。

直观地说,堆排序通过建立一个称为binary heap 的特殊数据结构来工作,该结构可以加快从未放置的数组元素中找到最大元素的速度。二进制堆支持以下操作:

  • 插入,将元素插入到堆中,并且
  • Delete-Max,删除并返回堆中最大的元素。

在非常高的层次上,该算法的工作原理如下:

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

这会对数组进行排序,因为 Delete-Max 返回的元素是按降序排列的。删除所有元素后,将对数组进行排序。

堆排序是高效的,因为堆上的 InsertDelete-Max 操作都在 O(log n) 时间内运行,这意味着可以进行 n 次插入和删除在 O(n log n) 时间内在堆上完成。 A more precise analysis 可以用来证明,事实上,不管输入数组如何,它都需要 Θ(n log n) 时间。

通常,堆排序采用两个主要优化。首先,通过将数组本身视为堆的压缩表示,堆通常是built up in-place inside the array。如果您查看堆排序实现,您通常会看到基于乘以和除以 2 的数组索引的不寻常用法;这些访问有效,因为它们将数组视为压缩数据结构。因此,该算法只需要 O(1) 的辅助存储空间。

其次,不是一次构建一个元素的堆,而是通常构建堆using a specialized algorithm,它在时间 Θ(n) 中运行以就地构建堆。有趣的是,在某些情况下,这最终使代码更易于阅读,因为代码可以重复使用,但算法本身变得更难理解和分析。

您有时会看到使用ternary heap 完成堆排序。这具有平均速度稍快的优势,但是如果您在不知道您在看什么的情况下找到使用它的堆排序实现,那么阅读它可能会相当棘手。其他算法也使​​用相同的通用结构但更复杂的堆结构。 Smoothsort 使用更复杂的堆来获得 O(n) 最佳情况行为,同时保持 O(1) 空间使用和 O(n log n) 最坏情况行为。 Poplar sort 类似于平滑排序,但空间使用量为 O(log n),性能稍好。人们甚至可以想到像insertion sort and selection sort as heap sort variants 这样的经典排序算法。

一旦你更好地掌握了堆排序,你可能想研究introsort 算法,它结合了快速排序、堆排序和插入排序来产生一种结合了快速排序优势的极快排序算法(快速排序在平均)、堆排序(极好的最坏情况行为)和插入排序(小数组的快速排序)。 Introsort 是 C++ 的 std::sort 函数的许多实现中使用的,一旦你有一个工作堆排序,你自己实现起来并不难。

希望这会有所帮助!

【讨论】:

  • 一年,在看奥运会的时候,我编写了一个排序算法,首先声明每个人都是有资格的参赛者,他们被认为比其他人更优秀,然后迭代地将那些有资格的人配对知道上级的人最少,输的暂时没有资格,并把参赛者的“上级”计入获胜者。一旦只剩下一个参赛者,那就是第一名;从集合中删除它,恢复所有人,然后恢复。第一遍进行 N-1 次比较;第二个需要 lgN。以后的通行证会有所不同。
  • 计数项目值比较,我发现排序工作得很好,但我没有分析它与堆排序相比的确切行为,堆排序在概念上似乎最接近。然而,堆排序不会像我的基于锦标赛的排序那样改变后轮行为。
  • 附带说明,我有平滑排序和白杨排序的实现,但我的benchmarks 倾向于表明没有明确的赢家。 Smoothsort 至少比 poplar 排序好。
【解决方案3】:

我会看看我如何回答这个问题,因为我对堆排序和堆是什么的解释会有点......

...呃,可怕

无论如何,首先,我们最好检查一下 Heap 是什么:

取自Wikipedia,堆是:

在计算机科学中,堆是一种特殊的基于树的数据结构,它满足堆属性:如果 B 是 A 的子节点,则 key(A) ≥ key(B)。这意味着具有最大键的元素始终位于根节点中,因此这种堆有时称为最大堆。 (或者,如果反向比较,最小的元素总是在根节点,这会导致最小堆。)

基本上,堆是一棵二叉树,任何节点的所有子节点都小于该节点。

现在,堆排序是一种 O(n lg(n)) 排序算法。您可以在herehere 阅读一些相关信息。它几乎可以通过将您要排序的任何元素的所有元素放入堆中,然后构建从最大元素到最小元素的排序数组。您将继续重组堆,并且由于最大的元素始终位于堆的顶部(根),因此您可以继续获取该元素并将其放在排序数组的后面。 (也就是说,您将反向构建排序数组)

为什么这个算法是 O(n lg(n))?因为堆上的所有操作都是 O(lg(n)),因此,您将执行 n 个操作,导致总运行时间为 O( n lg(n)).

我希望我可怕的咆哮对你有所帮助!有点啰嗦;对不起……

【讨论】:

    【解决方案4】:

    假设您有一个特殊的数据结构(称为堆),它允许您存储数字列表并让您在O(lg n) 时间内检索和删除最小的数字。

    你明白这如何导致一个非常简单的排序算法吗?

    困难的部分(实际上并不难)是实现堆。

    【讨论】:

      【解决方案5】:

      也许交互式跟踪将帮助您更好地理解算法。这是demo

      【讨论】:

        【解决方案6】:

        我记得我的算法分析教授告诉我们堆排序算法的工作原理就像一堆砾石:

        假设您有一个装满砾石的袋子,然后将其倒在地板上:较大的石头可能会滚到底部,而较小的石头(或沙子)会留在顶部。

        您现在取出堆的最顶部并将其保存在堆的最小值处。再把剩下的堆放在你的袋子里,然后重复。 (或者你可以采取相反的方法,抓住你看到在地板上滚动的最大的石头,这个例子仍然有效)

        这或多或少是我知道的解释堆排序如何工作的简单方法。

        【讨论】:

        • 一个小问题 - 在堆排序中,您不会在每一步都重建堆。如果你这样做,那么你最终会得到选择排序。
        • 是的,我想这很难用一个简单的砾石袋来解释:P
        • 但是你确实重新平衡了堆,不是吗?
        • 在这个例子中,当你把它放回你的袋子里时,堆会自动重新平衡,然后你每次都会把它倒在地板上。无论如何,这只是初学者的示例,因此可能会跳过一些要点以提供可理解的总体图片
        【解决方案7】:

        堆排序包括最简单的逻辑,时间复杂度为 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);
        
          }
        }
        

        【讨论】:

          猜你喜欢
          • 2018-05-26
          • 1970-01-01
          • 2021-10-17
          • 2021-10-10
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-11-01
          相关资源
          最近更新 更多