堆排序,了解基础知识

时间:2014-11-21 18:49:18

标签: heap heapsort min-heap max-heap

作为免责声明,我是本网站的新手,因此,不太清楚如何提问。请不要过于苛刻,因为我真的想要了解其中一些概念是如何运作的。如果我在开始时缺少理解,请告诉我,所以我可以从那里开始,而不是浪费你的时间与其余的。这没什么。因为我认为我的理解可能存在缺陷,所以我提出了一些关于堆如何在不同领域采取行动的问题,然后试图回答这些问题。

首先,我想了解如何添加到空堆中的随机数字集。比方说,我有9个,4个,5个,3个,2个,7个,8个,7个。将它添​​加到堆后,堆会是什么样的?我可以在视觉上理解这一点(我认为)9是根,4是第一个左子,依此类推,但由于这不是特定的树,而且是一堆,它会按数字排序吗?切换它们(参见第34段;如果我的理解是正确的"),以便它们按最小或最大顺序排序?

现在我们假设我们从堆中删除了9(我相信9将是根),我们将如何响应此更改然后将什么放入根目录?我想在这里,如果9是根,我们将采用下一个最大数字并将其复制到九个插槽中,而如果这是一个最小堆,我们只是删除底部的节点,它将被删除否问题

沿着类似的方向,获取数组中堆项的父项的公式是什么? - 我想我理解这一点,如果父母在我身边,左边的孩子将在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的左孩子。

如果我对此的理解是正确的,那么在将一个新术语添加到根之后重新定义我会将孩子与父母进行比较,如果孩子更大,则切换术语。但我需要比较两个孩子(如果有两个),看哪哪个更大,以决定哪一个需要交换。这将是一个最大堆,并将转向最小堆的另一个方向。

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

2 个答案:

答案 0 :(得分:0)

删除9后,没有任何东西成为根。 heapsort算法转到左边的孩子进行排序(你说4)然后是正确的孩子(或5)等。如果正在检查的数字是根(我们有不同的实现)那么4成为根,然后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;
}

我实际上不知道它会如何重新恢复,但是你可以看到要解决的代码片段。

答案 1 :(得分:0)

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

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

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

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

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

希望这会有所帮助-杰森