堆排序原理及实现

本文介绍了堆排序的原理和步骤。首先,通过构建大顶堆使待排序序列找到最大值,然后交换堆顶和末尾元素并重新调整堆。这一过程重复n-1次完成排序。堆排序的关键包括正确构建堆以及如何在交换后维护堆的性质。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

原理

以升序排序为例(升序采用大顶堆,降序采用小顶堆)。可以分成2步:

(1)将待排序的序列构造成一个大顶堆,这时堆顶就是序列的最大值。

(2)将堆顶元素和末尾元素进行交换,这时最大值就放到了末尾。然后将剩下n-1个元素重新构造成一个堆,这样堆顶就是n个元素的次大值。这样反复执行n-1轮后,序列就排好序了。

 

第1步:将待排序的序列构造成一个堆

思想:从最后一个非叶子节点开始,对于每个非叶子节点,以这个节点为根,自上而下不断调整,使得以它为根的子树成为一个堆。

【那么非叶子节点的范围是多少呢】

答:假设数组有n个元素,则下标为 0 到 n/2-1 的元素是非叶子节点,下标 n/2 到 n-1 的元素是叶子节点。

证明:

方法一:根据完全二叉树的特点,下标为 i 的节点的父节点是 (i-1)/2,那么最后一个节点 n-1 的父节点是 (n-2)/2 = n/2-1,这就是最后一个非叶子节点。

方法二:反证法。假设n/2不是叶子节点,那么它的左节点下标是 2*(n/2)+1 = n+2,超过了数组最大下标n-1,显然不符合逻辑。

 

第2步

思想:将堆顶元素和末尾元素进行交换,这时最大值就放到了末尾。然后将剩下n-1个元素重新构造成一个堆,这样堆顶就是n个元素的次大值。这样反复执行n-1轮后,序列就排好序了。

【怎么将剩下元素重新构造成一个堆】

剩下元素其实已经是“半个堆”了,只需要把堆顶不断向下交换就行了。每次通过比较当前节点和两个子节点的关系来进行交换。对于大顶堆来说,如果父节点比子节点小,就和较大的子节点进行交换。当父节点比两个子节点都大时就不需要交换了。

 

代码实现:

// 以index为堆顶,将堆顶元素向下沉,使得[index,max_index)的所有元素重新构造成堆
void heapify(int* nums, int index, int max_index){
    while(true){
        int maxValueIndex = index;  // 当前节点和左右子节点这三者中最大者的下标
        // 以大顶堆为例,若父节点比子节点小,就与较大的子节点交换
        
        // 若左节点比父节点(大小顶堆用>号)
        if(2*index+1 < max_index && nums[index] < nums[2*index+1])     
            maxValueIndex = 2*index+1;
        
        // 若右节点比左节点和父节点都大(小顶堆用>号)
        if(2*index+2 < max_index && nums[maxValueIndex] < nums[2*index+2]) 
            maxValueIndex  = 2*index+2;

        if(maxValueIndex == index) // 说明当前节点比两个子节点都大,无需再往下迭代了
            break;
        swap(nums[index], nums[maxValueIndex]);
        index = maxValueIndex;
    }
}

void HeapSort(int* nums, int n){
    // 1.将原始数组构造成堆
    for(int i=n/2-1; i>=0; i--)     // 对每个非叶子节点
        heapify(nums, i, n);        // 调整结构使得以它为根的子树成为堆

    // 2.将堆顶移到末尾,并对剩下元素重新构造堆
    for(int j=n-1; j>0; j--){
        swap(nums[0], nums[j]);  // 将堆顶元与末尾元素进行交换
        heapify(nums, 0, j);     // 剩下元素重新构造堆
    }
}

 

参考:

图解排序算法(三)之堆排序

五分钟学算法:什么是堆?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值