堆结构和堆排序

heap structure and heap sort

Posted by Marlin on July 26, 2025

堆结构

最大堆: 父节点的值大于等于其子节点的值 最小堆: 父节点的值小于等于其子节点的值 本文以最大堆为例。

堆中最重要的操作为向上调整向下调整

  • 向上调整(Heapify Up):将一个节点向上移动,直至其父节点的值小于等于其子节点的值。 0-based 数组下标时,i 的父节点为 (i-1)/2。

  • 向下调整(Heapify Down):将一个节点向下移动,直至其子节点的值都小于等于其父节点的值。 0-based 数组下标时,i 的左子节点为 2i+1,右子节点为 2i+2。

1. 插入(Insert)

步骤: 1. 将新元素添加到数组末尾(完全二叉树的最后一个位置)。
2. 向上调整(Heapify Up):将新元素与父节点比较,若不满足堆属性则交换,重复此过程直至根节点或满足属性。

  • 时间复杂度:$O(log n)$(树的高度为 log n)。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    
    void heap_insert(int arr[], int n, int key){
      int i = n;
      arr[i] = key;
      int fa = (i - 1) / 2;
    
      while (arr[i] > arr[fa]) {
          swap(arr[i], arr[fa]);
          i = fa;
          fa = (i - 1) / 2;
      }
    }
    

2. 修改堆顶元素

步骤: 1. 将新元素代替为堆顶元素。
2. 向下调整(Heapify Down):将堆顶元素与其子节点比较,若不满足堆属性则交换,重复此过程直至叶节点或满足属性。

  • 时间复杂度:$O(log n)$(树的高度为 log n)。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    
    void heap_modify(int arr[], int n, int i) {
      while (1) {
          int max = i;
          // 每次循环重新计算子节点坐标
          int lchil = 2 * i + 1;
          int rchil = 2 * i + 2;
            
          if (lchil < n && arr[lchil] > arr[max]) {
              max = lchil;
          }
          if (rchil < n && arr[rchil] > arr[max]) {
              max = rchil;
          }
          if (max == i) {
              break;
          }
          swap(arr[i], arr[max]);
          i = max; // 更新当前位置
      }
    }
    

    堆排序

步骤: 1. 构造最大堆 $O(n log n)$。
2. 交换堆顶元素与当前堆的最后一个元素(将最大值归位)。
3. 重复步骤 2,直至堆大小为 1。

  • 时间复杂度:$O(n log n)$。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    
    void heap_sort(int arr[], int n){
      // 自顶向下(逐个插入):O(nlogn)
      for(int i = 0; i < n; i++){
          heap_insert(arr, n, arr[i]); // 向上调整 构造最大堆
      }
      int size = n;
      while(size > 1){
          swap(arr[0], arr[size - 1]); // 交换堆顶元素与最后一个元素
          size--; // 堆的大小减一
          heap_modify(arr, size, 0); // 向下调整
      }
    }
    

    优化: 自顶向下 $O(n log n)$ -> 自底向上建堆 $O(n)$ 堆排序 $O(n log n)$ 步骤

    1. 构造最大堆 $O(n)$。
    2. 交换堆顶元素与当前堆的最后一个元素(将最大值归位)。
    3. 重复步骤 2,直至堆大小为 1。
  • 时间复杂度:$O(n log n)$。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    
    void heap_sort(int arr[], int n) {
      // 自底向上(Floyd算法):O(n)
      for (int i = (n - 1 - 1) / 2; i >= 0; i--) {
          // i从最后一个非叶子节点(最后一个节点的父节点)开始向上建堆
          heap_modify(arr, n, i); // 向下调整 构建最大堆
      }
      int size = n;
      while (size > 1) {
          swap(arr[0], arr[size - 1]); // 交换堆顶元素与最后一个元素
          size--;                      // 堆的大小减一
          heap_modify(arr, size, 0);   // 向下调整
      }
    }