堆排序,了解基础知识

问题描述 投票:0回答:2

作为免责声明,我是本网站的新手,因此,我不太清楚如何提问。请不要太苛刻,因为我实际上只是想了解其中一些概念的工作原理。如果我在一开始就缺少理解,请告诉我,这样我可以从那里开始,而不会浪费您的时间。这里什么也没有。因为我认为我的理解可能有缺陷,所以我对堆在不同区域的行为提出了一些问题,然后试图回答它们。

首先,我想帮助您了解添加到空堆中的一组随机数字的外观。例如,假设我有9、4、5、3、2、7、8、7。将其添加到堆中后,堆的外观如何?我可以直观地理解这一点(我认为)9是根,4是第一个左孩子,依此类推,依此类推,但是由于这不是一棵树,而是一个堆,它将通过切换来对数字进行排序它们(请参阅“如果我的理解是正确的”段落),以便它们以最小或最大顺序排序?

现在可以说我们从堆中删除了9(我相信9将成为根),我们将如何应对这一变化,然后将什么放入根中?我想在这里如果9是根,我们将取下一个最大的数字并将其复制到9的插槽中,而如果这是一个最小堆,而我们只是在底部删除一个节点,则将其删除。问题。

沿着相似的行,公式将如何获得数组中堆项的父级?-我想我明白,如果父母在i处,则左孩子将在i * 2处,而右孩子将在i * 2 + 1处。因此,要找到父代,我们必须将i / 2除以找到父代。例如,如果我们在i = 7处的父级将是i = 3,因为3.5将被截断,并且如果我们在i = 6点处的父级也将是i = 3。从这个例子中,i = 7的孩子将是i = 3的右孩子,而i = 6将是i = 3的左孩子。

如果我的理解是正确的,那么要在将新术语添加到词根之后重新进行修饰,我会将孩子与父对象进行比较;如果孩子较大,请切换术语。但是我需要比较两个孩子(如果有两个),以确定哪个更大,以决定哪个需要交换。这将用于最大堆,而将另一个方向用于最小堆。

最后,如果我在哪里添加根元素,它将如何重新堆砌?

heap heapsort min-heap max-heap
2个回答
0
投票

删除9后,没有任何内容成为根。堆排序算法转到左子级进行排序(您说过4)。然后右子级(或5),依此类推。如果要检查的数字是根((我们有不同的实现)],则4成为根。 root,然后是5,等等。如果您感到困惑,请查看用JavaScript编写的heapsort的定义:

var heapSort = function(array) {
  var swap = function(array, firstIndex, secondIndex) {
    var temp = array[firstIndex];
    array[firstIndex] = array[secondIndex];
    array[secondIndex] = temp;
  };
  var maxHeap = function(array, i) {
    var l = 2 * i;
    var r = l + 1;
    var largest;
    if (l < array.heapSize && array[l] > array[i]) {
      largest = l;
    } else {
      largest = i;
    }
    if (r < array.heapSize && array[r] > array[largest]) {
      largest = r;
    }
    if (largest !== i) {
      swap(array, i, largest);
      maxHeap(array, largest);
    }
  };
  var buildHeap = function(array) {
    array.heapSize = array.length;
    for (var i = Math.floor(array.length / 2); i >= 0; i--) {
      maxHeap(array, i);
    }
  };
  buildHeap(array);
  for (var i = array.length-1; i >= 1; i--) {
    swap(array, 0, i);
    array.heapSize--;
    maxHeap(array, 0);
  }
  array.heapMaximum = function(){
      return this[0];
  };
  array.heapExtractMax = function(){
      if(this.heapSize < 1){
          throw new RangeError("heap underflow");
      }
      var max = this[0];
      this[0] = this[this.heapSize - 1];
      this.heapSize--;
      maxHeap(this, 1);
      return max;
  };
  array.heapIncreaseKey = function(i, key){
      if(key < this[i]){
          throw new SyntaxError("new key is smaller than current key");
      }
      this[i] = key;
      while(i > 1 && this[Math.floor(i / 2)] < this[i]){
          swap(this, i, Math.floor(i / 2));
          i = Math.floor(i / 2);
      }
  };
  array.maxHeapInsert = function(key){
      this.heapSize--;
      this[this.heapSize] = -Infinity;
      this.heapIncreaseKey(this.heapSize, key);
  };
};
var a = [Math.floor(Math.random() * 100), Math.floor(Math.random() * 100), Math.floor(Math.random() * 100), Math.floor(Math.random() * 100), Math.floor(Math.random() * 100)];
heapSort(a);
document.writeln(a);
*{
  font-family:monospace;
}

我实际上不知道它会如何重新适应,但是您可以看到摘要以找出答案。


0
投票

首先是关于堆外观的第一个问题。它将采用完整的二叉树的结构。我们可以沿着列表浏览,并在看到树时对其进行更新,但这会破坏运行时间,因此有一种更聪明的方法可以执行此操作。我们首先要线性地遍历数组并将其添加到最左边的插槽中,数组的第一个条目就是根。然后,有了数组后,我们便希望从头开始修复堆。这涉及查看堆的最高深度,并通过进行交换来固定它,以便最小的是父级。然后在树的深处向上移动一个,如果其中一个孩子小于新父母,则进行交换。如果是这样,则进行交换,但是我们可能破坏了min属性,因此我们必须递归向下移动堆以修复该属性。一旦我们递归地移到顶部并将堆固定在顶部,那么我们将使最小堆成为所需。请注意,通过一些漂亮的代数,我们可以证明这将在O(n)时间内运行。

关于删除9的第二个问题是不正确的(因为它不再是根),所以我们集中在删除根节点上。从树或数组的第一项中删除根节点后,我们需要在树结构中放置一些东西,然后将树的最左边节点或数组中的最后一个元素放置在数组中,可能在想,这可能破坏了最小财产,而您是对的。因此,一旦最左侧移动到根节点,我们必须检查其子节点,如果它的子节点都小于两个节点,那么我们就很好了。否则,我们需要交换较小的子集,并对下一组子集重复此操作,直到它小于两个子集。

在数组中,我们使用2i和2i + 1作为索引是正确的,因此仅将2除以是不够的。我们注意到2i是偶数,而2i + 1是奇数,因此我们应该关注所查看的索引是偶数还是奇数。但是,正确的是,截断将为父级给出正确的答案,而小数点将决定左,右子级。

为了解决您的最终担忧,我们应该注意,当您将某些东西添加到堆中时,它是完整的二叉树,应添加到最左侧的插槽而不是根。当您在最左边添加一个对象(用于最小堆)时,我们需要检查它是否小于其父对象,并将其移到根目录。

此外,当需要运行prim的算法或Dijkstra的最短路径算法时,使用O(n)构建堆是高效的。

希望这会有所帮助-杰森

© www.soinside.com 2019 - 2024. All rights reserved.