堆排序

5 阅读2分钟

原理

以从小到大排序为示例,我们借助大根堆。

  1. 从第一个非叶子节点开始,把二叉树调整成一个大根堆, 即从第(n-1) / 2号元素开始到堆顶元素(0号位元素),进行下沉操作。
  2. 把堆顶元素和末尾元素进行交换, 此时我们将值最大的元素放到了最后。 所以当前状态打破了大根堆节点之间的大小关系,因此需要从0号位元素继续开始堆的下沉调整,下沉调整的时候我们就不需要考虑最后一个元素了,因为此时最后一个元素是最大值。如此循环,每次堆顶都是最大的元素。

时间空间复杂度

排序算法平均时间复杂度最好时间复杂度最坏时间复杂度空间复杂度稳定性
堆排序O(nlog2nn\log_{2}{n})O(nlog2nn\log_{2}{n})O(nlog2nn\log_{2}{n})O(1)不稳定

代码

#include <iostream>
#include <stdlib.h>
#include <time.h>

using namespace std;

// 堆的下沉调整
void siftDown(int arr[], int i, int size) {

  int val = arr[i]; // val 为要调整的值

  while (i < size / 2)  // 注意此处的比较
  {
    int child = 2 * i + 1; // 左孩子
    if (child + 1 < size && arr[child + 1] > arr[child]) {  // 右孩子存在,并且右孩子大于左孩子
      child = child + 1;  // 记录数值大的孩子的位置
    }

    if (arr[child] > val) {
      arr[i] = arr[child];
      i = child; 
    } else {
      break;
    }
  }

  arr[i] = val;
  
}

// 堆排序
void HeapSort(int arr[], int size) {

    int n = size - 1;  // 末尾元素的下标

    // 从第一个非叶子节点,调整为大根堆
    for (int i = (n - 1) / 2; i >= 0; i--) {
      siftDown(arr, i, size);
    }

    // 把堆顶元素和末尾元素进行交换, 从堆顶开始进行下沉操作
    for (int i = n; i > 0; i--) {
      int tmp = arr[0];
      arr[0] = arr[i];
      arr[i] = tmp;

      siftDown(arr, 0, i);  // 第三个参数表示参与调整的元素个数
    }

}

int main() {

    int arr[10];
    srand(time(nullptr));

    for (int i = 0; i < 10; i++) {
        arr[i] = rand() % 100 + 1;
    }

    for (int v : arr) {
        cout << v << " ";
    }

    cout << endl;

    HeapSort(arr, 10);

    for (int v : arr) {
        cout << v << " ";
    }

    cout << endl;

    return 0;
}

测试

➜  build git:(main) ✗ ./HeapSort     
91 93 13 98 25 46 49 90 82 65 
13 25 46 49 65 82 90 91 93 98