c++ 堆和堆排序

什么是堆(Heap)?

堆(Heap)是一种基于完全二叉树的数据结构,用于快速查找最大值或最小值。它满足以下性质:

  • 完全二叉树:每一层从左到右填满,除了最后一层可以不完全填满,但节点必须靠左排列
  • 堆序性质:堆分为两种常见类型:
    • 大顶堆(Max-Heap):每个节点的值 ≥ 其子节点的值。根节点是最大值。
    • 小顶堆(Min-Heap):每个节点的值 ≤ 其子节点的值。根节点是最小值。

示例

  • 大顶堆
      10
     /  \
    5    3
   / \
  2   1
  • 小顶堆
      1
     / \
    3   2
   / \
  5   4

堆的存储方式

数组存储:堆通常用数组表示,因为它是完全二叉树,利用数组的结构可以高效定位父节点和子节点。

  • 父节点索引parent(i) = (i - 1) / 2
  • 左子节点索引left(i) = 2 * i + 1
  • 右子节点索引right(i) = 2 * i + 2

数组表示例:

大顶堆 [10, 5, 3, 2, 1] 对应的结构为:

        10
       /  \
      5    3
     / \
    2   1

堆的基本操作

插入元素

  • 将新元素插入到数组末尾,然后向上调整堆(上滤),确保堆序性质。

算法:

  1. 将元素插入数组末尾。
  2. 通过“上滤”操作,将其逐层与父节点比较,必要时交换。

时间复杂度:O(log n)

删除堆顶元素

  • 删除堆顶元素后,将最后一个元素移到堆顶,然后向下调整(下滤)。

算法:

  1. 删除堆顶元素,将最后一个元素放到堆顶。
  2. 通过“下滤”操作,将其逐层与子节点比较,必要时交换。

时间复杂度:O(log n)

构建堆

  • 从无序数组构建堆,可以通过heapify(堆化)操作实现。
    从最后一个非叶子节点开始,逐步向上调整。

时间复杂度:O(n)

堆实现(c++)

#include <iostream>
#include <vector>
using namespace std;

// 调整为大顶堆
void heapify(vector<int>& arr, int n, int i) {
    int largest = i;         // 根节点
    int left = 2 * i + 1;    // 左子节点
    int right = 2 * i + 2;   // 右子节点

    // 如果左子节点大于根节点
    if (left < n && arr[left] > arr[largest])
        largest = left;

    // 如果右子节点大于当前最大值
    if (right < n && arr[right] > arr[largest])
        largest = right;

    // 如果最大值不是根节点,交换并递归调整
    if (largest != i) {
        swap(arr[i], arr[largest]);
        heapify(arr, n, largest);  // 递归调整子树
    }
}

// 构建堆
void buildHeap(vector<int>& arr) {
    int n = arr.size();
    for (int i = n / 2 - 1; i >= 0; i--) {
        heapify(arr, n, i);
    }
}

// 打印数组
void printArray(const vector<int>& arr) {
    for (int i : arr) {
        cout << i << " ";
    }
    cout << endl;
}

int main() {
    vector<int> arr = {4, 10, 3, 5, 1};
    cout << "原数组: ";
    printArray(arr);

    buildHeap(arr);
    cout << "构建大顶堆: ";
    printArray(arr);

    return 0;
}

堆排序(Heap Sort)

堆排序是基于大顶堆的排序算法。其过程如下:

  1. 将数组构建为大顶堆
  2. 堆顶元素(最大值)与末尾元素交换,并缩小堆的范围。
  3. 对剩余部分重新进行堆调整,直到所有元素有序。

堆排序代码:

#include <iostream>
#include <vector>
using namespace std;

// 调整为大顶堆
void heapify(vector<int>& arr, int n, int i) {
    int largest = i;         // 初始化最大值为根节点
    int left = 2 * i + 1;    // 左子节点
    int right = 2 * i + 2;   // 右子节点

    // 如果左子节点存在且大于根节点
    if (left < n && arr[left] > arr[largest])
        largest = left;

    // 如果右子节点存在且大于当前最大值
    if (right < n && arr[right] > arr[largest])
        largest = right;

    // 如果最大值不是根节点,交换并递归调整
    if (largest != i) {
        swap(arr[i], arr[largest]);
        heapify(arr, n, largest);  // 递归调整子树
    }
}

// 堆排序
void heapSort(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 printArray(const vector<int>& arr) {
    for (int num : arr) {
        cout << num << " ";
    }
    cout << endl;
}

int main() {
    vector<int> arr = {12, 11, 13, 5, 6, 7};
    cout << "排序前的数组: ";
    printArray(arr);

    heapSort(arr);

    cout << "排序后的数组: ";
    printArray(arr);

    return 0;
}

代码分析

  • heapify 函数
    这是一个递归函数,用于调整部分数组为大顶堆。在构建和排序过程中都需要用它来维护堆的性质。
  • heapSort 函数
    • 第一步:从最后一个非叶子节点开始,依次构建大顶堆。
    • 第二步:逐次将堆顶(最大值)与末尾元素交换,并对剩余部分进行堆调整。
  • printArray 函数
    打印数组内容,帮助查看排序结果。

时间复杂度分析

  • 构建堆:O(n)
  • 调整堆:O(log n)(每次交换后调整堆)
  • 堆排序整体时间复杂度:O(n log n)

空间复杂度

  • 原地排序:不需要额外空间,空间复杂度为 O(1)

堆排序总结

  • 堆排序是一种不稳定的排序算法,因为在排序过程中相同元素的顺序可能会改变。
  • 适合用于需要原地排序且不要求稳定性的场景。
  • 堆排序的时间复杂度为 O(n log n),在大数据排序中表现稳定。

堆的应用

  • 优先队列:使用堆实现,可以快速获取最大值/最小值。
  • 堆排序:利用大顶堆完成排序,时间复杂度 O(n log n)。
  • 求 Top K 元素:利用小顶堆,可以在 O(n log k) 时间内找到数组的前 K 个最大元素。
  • 合并多个有序数组:使用小顶堆合并多个有序数组,时间复杂度 O(n log k)。

时间复杂度分析

操作时间复杂度
插入元素O(log n)
删除堆顶元素O(log n)
构建堆O(n)
堆排序O(n log n)

总结

  • 是一种高效的二叉树数据结构,适用于需要快速获取最大/最小值的场景。
  • 堆排序是一种原地排序算法,空间复杂度 O(1),但它是不稳定排序
  • 应用场景广泛,如优先队列、Top K 问题多路归并等。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值