Skip to content
文档章节

堆排序

堆是什么

堆是一种二叉树树形结构,跟一把的数的却别是不需要额外存储保存指针关系。

堆分为大顶堆和小顶堆

大顶堆:父节点值大于子节点的值 小顶堆:父节点值小于子节点值

节点位置

通常堆以数组形式存储堆中的数据,假设一个节点的位置索引是 i , 数组起始位置为 0

  1. 左子节点所在位置 (2i+1)
  2. 右子节点所在位置 (2i+2)
  3. 父节点所在位置 (i1)/2

堆的操作

在堆中最大值或最小值(优先队列)总是位于根节点。最大堆的操作有如下几种操作:

  1. 最大堆调整(Max Heapify):将堆的末端子节点调整,使得子节点永远小于父节点
  2. 创建最大堆(Build Max Heap):将堆中的数据重新排序
  3. 堆排序(Heap Sort):移除根节点,并做最大堆调整递归运算。

堆的应用

1. 最大的 K 个数和最小的 K 个数

最大的 K 个数: 大顶堆获取数据 最小的 K 个数:小顶堆获取数据

2. 排序算法

升序:小顶堆排序 倒叙:大顶堆排序

TIP

注意这个跟最大的 k 和最小的 k 个数使用相反,因为排序算法会交换顶点和末尾节点(不一定是数组最后一个)。

建堆过程

以大顶堆为例

  1. 从数组中最后一个非叶子节点开始向上调整。最后一个非叶子节点位置 i=(len11)/2,左子节点所在位置 (2i+1),右子节点所在位置 (2i+2)
  2. 假设处理某个非叶子子节点,其后的非叶子子节点已排序好了。比较当前节点与其子节点大小关系。
  3. 如果当前节点比所有子节点大,则处理下一个非叶子子节点
  4. 如果当前节点小于子节点数值,则取子节点中最大值与当前节点交换, 记录最大子节点位置为 childIndex。向下处理子节点是否满足堆结构,当前节点 i=childIndex,处理子节点。

画个图理解下,以 [3,5,3,0,8,6,1,5,8] 为例:

建堆过程

堆排序过程

  1. 建堆
  2. 取出堆顶节点,将待处理数组最后一个节点和堆顶节点交换,调整堆结构
  3. 待处理节点数目-1,循环执行 步骤 2 和 3 直到处理完毕

JS 算法

js
Array.prototype.heapSort = function () {
  let arr = this.slice(0);

  let len = arr.length;

  function swap(i, j) {
    let temp = arr[i];
    arr[i] = arr[j];
    arr[j] = temp;
  }

  // 类似 shiftDown
  function maxHeapAdjust(start, end) {
    let parent = start;
    // 左节点
    let child = start * 2 + 1;

    // 子节点在下表范围类才比较
    while (child <= end) {
      // 先比较两个子节点中最大的
      if (child + 1 <= end && arr[child] < arr[child + 1]) child++;

      // 如果父节点比子节点大,则跳出循环,不再处理
      if (arr[parent] > arr[child]) return;
      // 否则,交换父子节点内容,子节点再和孙子节点比较
      swap(parent, child);
      parent = child;
      child = child * 2 + 1;
    }
  }

  // 初始化堆, 类似 buildHeap
  for (var i = Math.floor(arr.length / 2) - 1; i >= 0; i--) {
    maxHeapAdjust(i, arr.length - 1);
  }

  // 排序
  for (var i = len - 1; i > 0; i--) {
    swap(0, i);
    maxHeapAdjust(0, i - 1);
  }

  return arr;
};

var a = [3, 5, 3, 0, 8, 6, 1, 5, 8, 6, 2, 4, 9, 4, 7, 0, 1, 8, 9, 7, 3, 1, 2, 5, 9, 7, 4, 0, 2, 6];

console.log(a.heapSort());

python 实现

py
# 大顶堆
class MaxHeap(object):
  def __init__(self, nums):
    self.nums = nums
    self.buildMaxHeap()

  def buildMaxHeap(self):
    size = len(self.nums)

    # 建堆
    for i in range(size // 2 - 1, -1, -1):
      self.heapify(size, i)

  # 调整节点 i 是否满足对结构
  def heapify(self, n, i):
    largest = i
    left = 2 * i + 1
    right = 2 * i + 2

    if left < n and self.nums[largest] < self.nums[left]:
      largest = left
    if right < n and self.nums[largest] < self.nums[right]:
      largest = right

    if largest != i:
      self.nums[i], self.nums[largest] = self.nums[largest], self.nums[i]

      # 上面节点下放,继续验证是否符合堆结构,调整结构
      self.heapify(n, largest)

  def sort(self):
    size = len(self.nums)
    # 排序
    for i in range(size - 1, 0, -1):
      self.nums[i], self.nums[0] = self.nums[0], self.nums[i]
      self.heapify(i, 0)


  def pop(self):
    size = len(self.nums)
    if size == 0:
      return None

    self.nums[0], self.nums[size -1] = self.nums[size - 1], self.nums[0]
    val = self.nums.pop()

    size -= 1

    self.heapify(size, 0)

    return val

  def add(self, num):
    self.nums.append(num)
    size = len(self.nums)
    self.shiftUp(size -1)

  def shiftUp(self, i):
    while i > 0:
      parent = (i -1) // 2

      if self.nums[parent] < self.nums[i]:
        self.nums[parent], self.nums[i] = self.nums[i], self.nums[parent]
        i = parent
      else:
        break


if __name__ == "__main__":
  nums = [5,6,2,3,1,7,9,8,10,1,2]
  maxHeap = MaxHeap(nums)
  # maxHeap.sort()
  print(maxHeap.nums)

  # for i in range(len(maxHeap.nums)):
  #   val = maxHeap.pop()
  #   print(val, maxHeap.nums)


  # val = maxHeap.pop()

  # val = maxHeap.pop()
  # print(val, maxHeap.nums)

  maxHeap.add(22)
  print(maxHeap.nums)
  maxHeap.add(23)
  print(maxHeap.nums)
  maxHeap.add(12)
  print(maxHeap.nums)
  maxHeap.add(13)
  print(maxHeap.nums)