912. 排序数组(堆排序)

912. 排序数组 #

链接 #

912. 排序数组

题目 #

给你一个整数数组 nums,请你将该数组升序排列。

示例 1:

输入:nums = [5,2,3,1]
输出:[1,2,3,5]

示例 2:

输入:nums = [5,1,1,2,0,0]
输出:[0,0,1,1,2,5]

提示:

  • 1 <= nums.length <= 5 * 104
  • -5 * 104 <= nums[i] <= 5 * 104

解答 #

堆排序

#include <vector>
using namespace std;

class Solution {
public:
  vector<int> sortArray(vector<int> &nums) {
    // 调用堆排序函数
    heap_sort(nums);
    return nums;
  }

private:
  void heap_sort(vector<int> &arr) {
    int n = arr.size();
    // 构建最大堆
    for (int i = n / 2 - 1; i >= 0; i--) { // 从最后一个非叶子节点开始
      heapify(arr, n, i);
    }
    // 一个个交换元素
    for (int i = n - 1; i > 0; i--) {
      swap(arr[0], arr[i]); // 将当前最大的放到数组末尾
      heapify(arr, i, 0); // 重新对剩下的元素进行堆调整
    }
  }

  void heapify(vector<int> &arr, int n, int i) { // n 表示数组长度,i 表示当前节点下标
    // 第一部分,找到最大值
    int largest = i; // 初始化最大为根
    int left = 2 * i + 1; // 左 = 2*i + 1
    int right = 2 * i + 2; // 右 = 2*i + 2

    // 如果左子节点大于根节点
    if (left < n && arr[left] > arr[largest]) { // left < n 合法性检查
      largest = left;
    }

    // 如果右子节点比最大还大
    if (right < n && arr[right] > arr[largest]) {
      largest = right;
    }

    // 第二部分,将最大值交换到根
    // 如果最大不是根
    if (largest != i) {
      swap(arr[i], arr[largest]);

      // 递归地定义子堆
      heapify(arr, n, largest);
    }
  }
};
/**
 * @param {number[]} nums
 * @return {number[]}
 */
var sortArray = function(nums) {
    const heapify = (nums, n, i) => {
        let left = 2 * i + 1
        let right = 2 * i + 2
        let largest = i

        if (left < n && nums[left] > nums[largest]) {
            largest = left
        }

        if (right < n && nums[right] > nums[largest]) {
            largest = right
        }

        if(largest != i) {
            [nums[largest], nums[i]] = [nums[i], nums[largest]]
            heapify(nums, n, largest)
        }
    }

    const n = nums.length

    for (let i = Math.floor(n / 2) - 1; i >= 0; i--) {
        heapify(nums, n, i)
    }

    for (let i = n - 1; i >= 0; i--) {
        [nums[i], nums[0]] = [nums[0], nums[i]]
        heapify(nums, i, 0)
    }

    return nums
};

可视化:Binary Max Heap | Build-Max-Heap - YouTube

考点:heapify 的时空复杂度分析 #

堆排序算法中的核心函数 heapify,用于确保从给定的节点 i 开始,下面的子树遵循最大堆的性质。最大堆的性质是父节点的值大于或等于其子节点的值。该函数通过递归调用自身来保证这一点,如果在当前节点、其左子节点或其右子节点中发现更大的值,就会进行交换,并在交换后对影响到的子树再次进行 heapify 调用。

时间复杂度 #

分析 heapify 函数的时间复杂度时,问题实际等价于递归调用的深度与 N 的关系。对于一个完全二叉树,最坏的情况是需要递归地调整到叶节点,这个深度大约是树的高度 (h),其中 (h = \log_2(n))(因为每一层节点数量翻倍,所以树的高度与节点数量的对数成正比)。

  • 最坏情况时间复杂度:在最坏的情况下(即每次调用 heapify 时都需要交换到叶子节点),时间复杂度为 (O(\log n))。

  • 平均情况时间复杂度:考虑到不是每次都需要交换到最底层,平均情况下的时间复杂度仍然可以认为是 (O(\log n)),但实际上的常数因子可能会小于最坏情况。

空间复杂度 #

  • 递归堆栈空间复杂度:由于 heapify 函数是递归调用的,其空间复杂度主要由递归堆栈的深度决定,即 (O(\log n))。这是因为在最坏的情况下,递归的深度可以达到树的高度,即 (h = \log_2(n))。