《数据结构》堆排序算法

一.堆是一种完全二叉树数据结构,分为两种类型:

  1. 最大堆:每个节点的值(只有非叶子节点才有子节点)都不小于其子节点的值。根节点是最大值。
  2. 最小堆:每个节点的值(只有非叶子节点才有子节点)都不大于其子节点的值。根节点是最小值。

二.堆排序过程图示: 

升序为例(建大根堆:每一个父节点(parent)都必须大于其儿子节点(child))

我们从最后一个非叶子节点开始调整。

建堆过程解释:

1.最后一个非叶子节点为13,13的儿子节点65,13<65,交换这两个节点。

2.其次非叶子节点为38,38的儿子左节点97比其儿子右节点76大,38<97,交换这两个节点。

3.其次为非叶子节点49,49的儿子左节点97比其儿子右节点65大,49<97,交换这两个节点。

4.非叶子节点49,49的儿子左节点38比其儿子右节点76小,49<76,交换这两个节点。

开始排序阶段过程解释:

1.交换堆顶和堆尾元素。

2.恢复为大堆,不包括97。

3.循环直至所有数据排序成功。

三.简易代码描述:

void Swap(int* a, int* b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

void AdjustDown(int* a, int n, int parent) {
    int child = 2 * parent + 1;  // 左子节点索引
    if (child >= n) return;      // 如果没有子节点,直接返回

    // 找到左右子节点中较大的那个
    if (child + 1 < n && a[child] < a[child + 1]) {
        child++;  // 右子节点存在且较大
    }

    // 如果父节点小于较大的子节点,则交换并继续调整
    if (a[parent] < a[child]) {
        Swap(&a[parent], &a[child]);
        AdjustDown(a, n, child);  // 递归调整子树
    }
}

void HeapSort(int* a, int n) {
    // 构建大根堆
    for (int i = (n - 1 - 1) / 2; i >= 0; i--) {
        AdjustDown(a, n, i);
    }

    // 进行排序
    for (int end = n - 1; end > 0; end--) {
        Swap(&a[0], &a[end]);  // 将最大值移到末尾
        AdjustDown(a, end, 0); // 调整剩余部分
    }
}

四.代码解释:

  • Swap: 交换两个元素的函数。
  • AdjustDown: 从给定的父节点开始,调整堆,使得以该节点为根的子树满足堆性质。
  • HeapSort: 首先构建大根堆,然后逐步将堆顶元素(最大值)放到数组末尾,并调整剩余部分。

五.部分代码解释:

代码片段一:

for (int i = (n - 1 - 1) / 2; i >= 0; i--) {
        AdjustDown(a, n, i);
    }
1.堆的构建:

在堆排序中,我们首先需要构建一个大根堆(或最小堆),以便能够逐步提取最大值(或最小值)。这个构建过程的关键是调整每一个非叶子节点,使得整个树满足堆的性质。

2.计算非叶子节点的起始位置

(n - 1 - 1) / 2 这个公式用来计算最后一个非叶子节点的索引。具体解释如下:

假设有一个包含 7 个元素的数组 [3, 5, 1, 8, 9, 6, 2],我们来找出最后一个非叶子节点的索引:

  1. 数组大小 n = 7
  2. 最后一个元素的索引 n - 1 = 6
  3. 倒数第二个元素的索引 n - 1 - 1 = 5
  4. 最后一个非叶子节点的索引 计算为 (n - 1 - 1) / 2 = 5 / 2 = 2

所以,最后一个非叶子节点的索引是 2

图示说明

 

3.调整非叶子节点

从最后一个非叶子节点开始,向前遍历到根节点,并对每个非叶子节点调用 AdjustDown 函数来调整堆。AdjustDown 函数的作用是确保以当前节点为根的子树满足堆的性质,即最大堆中的每个父节点都大于其子节点。

这段代码的目的是从最后一个非叶子节点开始,逐步向前调整堆,以构建一个有效的大根堆。这个过程是堆排序的关键步骤之一。

代码片段二:

for (int end = n - 1; end > 0; end--) {
    Swap(&a[0], &a[end]);  // 将最大值移到末尾
    AdjustDown(a, end, 0); // 调整剩余部分
}

1.for (int end = n - 1; end > 0; end--): 这是一个倒序循环,从数组的最后一个元素开始,逐步向前移动,直到第一个元素。这是为了将堆顶的最大元素(即数组的第一个元素)逐渐移动到当前“未排序”的数组末尾位置。

2.Swap(&a[0], &a[end]): 交换堆顶元素(数组的第一个元素,最大值)和当前“未排序”部分的最后一个元素。这将最大值移到已排序部分的末尾。

3.AdjustDown(a, end, 0): 调整堆的剩余部分。交换后,堆的性质可能被破坏,需要通过 AdjustDown 函数来重新调整剩余部分以维持最大堆的性质。

六.堆排序的时间复杂度:

  • 构建初始堆:需要对所有非叶子节点进行堆化操作,总体时间复杂度为 (O(n))。
  • 排序过程:在每次交换堆顶元素(最大值)和末尾元素后,调整堆的时间复杂度为 (O(\log n)),这个操作重复 (n-1) 次,总时间复杂度为 (O(n \log n))。

因此,堆排序的总时间复杂度是 (O(n \log n)),无论是最坏情况、平均情况还是最佳情况。

  • 10
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

data_structure_wr

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值