C语言-数据结构 堆排序(大根堆)

        在堆排序中核心是理解堆排序的调整过程,就是对局部的三个结点进行比较,一个根节点与两个孩子节点进行比较,如果某个孩子更大,把孩子换上去,双亲结点换下来。从最后一个非叶子的双亲结点进行调整(就是有孩子的双亲中编号最大的)从它开始进行递减调整到根节点,每次把局部最大的孩子结点调到双亲位置,如果孩子没有比双亲大就不管,一直调整到根节点位置,此时根节点就是最大的,然后我们把它与最后一个叶子结点进行交换,最后一个位置就是最大的了,交换完树就不满足大根堆了,我们继续调整前面的结点重新构造大根堆,同样从底部最后一个非叶子的双亲结点开始调整,刚才换下来的这个节点就不需要再进行调整了,包括已经换好的节点也不需要参与调整。

注意1:我们一旦从大根堆找到最大值,那么位置也就最终确定了,这个最大值就退出大根堆的调整,每次调整的结点不包含已经调整好的结点

注意2:每一轮都会从大根堆中找到一个最大值

注意3:我们的数组存放排序数据的下标从1-length,第一个位置0用来临时存储局部双亲节点的值用于与比自己大的孩子交换。在前面介绍的希尔排序中也用0这个位置存储临时的值,其他正常参与排序的数据存在1-length位置。当然你可以自己申请一个用临时变量存储来进行交换操作,数组中 0 位置正常存放排序的数据

        为了实现的方便我们用数组实现,也就是二叉树的顺序存储,同时父亲下标为i时,左孩子下标为2*i,右孩子下标2*i+1,如果用二叉链表会比较麻烦,因为要找父亲结点用于交换,单向二叉链表完成不了,需要再加入一个父亲指针域。

下面为具体实现代码:

#include <stdio.h>

// 调整堆的函数,使以 k 为根的子树满足最大堆的性质
void HeadAdjust(int a[], int k, int len) {
    // 临时保存当前节点的值
    a[0] = a[k];
    // 从 k 节点的左子节点开始向下调整堆
    for (int i = 2 * k; i <= len; i *= 2) {
        // 如果右子节点存在且比左子节点大,则选择右子节点
        if (i < len && a[i] < a[i + 1]) {
            i++;
        }
        // 如果当前节点已经比子节点大,不需要继续调整
        if (a[0] >= a[i]) {
            break;
        }
        else {
            // 将子节点的值上移
            a[k] = a[i];
            k = i; // 继续向下调整
        }
    }
    // 将保存的节点值放到最终位置
    a[k] = a[0];
}

// 构建最大堆的函数
void BuildMaxHeap(int a[], int len) {
    // 从最后一个非叶子节点开始调整堆
    for (int i = len / 2; i > 0; i--) {
        HeadAdjust(a, i, len);
    }
}

// 交换两个整数的函数
void Swap(int* a, int* b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

// 堆排序的函数
void HeapSort(int a[], int len) {
    // 首先构建最大堆
    BuildMaxHeap(a, len);
    // 从最后一个元素开始,将最大元素交换到末尾,并重新调整堆
    for (int i = len; i > 1; i--) {
        Swap(&a[i], &a[1]); // 交换根节点与当前堆的最后一个节点
        // 重新调整堆,注意调整的长度是 i-1,因为已排序的部分在末尾
        HeadAdjust(a, 1, i - 1);
    }
}

int main() {
    // 初始化数组,注意第一个元素是0,用于保持1-based索引
    int a[8] = { 0, 5, 3, 7, 8, 9, 1, 6 };
    // 对数组进行堆排序
    HeapSort(a, 7);
    // 打印排序后的数组,注意每个元素之间用箭头分隔
    for (int i = 1; i <= 7; i++) {
        printf("%d", a[i]);
        if (i < 7) {
            printf("->");
        }
    }
    printf("\n"); // 打印换行符
    return 0;
}

运行结果:

407bf1a85c494f4aa69a5199b498fb00.png

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值