排序算法(3) 堆排序

堆排序

1. 算法原理

堆排序的原理与部分没听说过该排序方法的同学猜想的不同,它并不是将堆顶元素逐一弹出后压入数组中来得到一个有序的数组。这种方法在初始建堆时需要使用一个数组空间,在得到有序的数组时需要使用另一个数组空间。而堆排序则是在初始数组的基础上直接进行排序,相较于上一种方法节省了一倍的空间

堆排序的做法时:
从小到大排序数组为例,首先使用初始数组建立一个大顶堆,记作a[1…n],然后将堆顶元素a[1]与堆尾元素a[n]进行交换,将此次操作看作是一次堆顶元素弹出操作,据此调整堆数组范围和元素位置,记新堆为a[1…n - 1]。不断重复以上操作,直到堆中只剩一个元素,最终的数组便排好了序。

2. 算法模拟

对于数组:3 5 8 4 3 2 10 1 9,进行从小到大排序

在这里插入图片描述

由图可知,堆排序仅仅使用了建堆时的数组空间,最终排序结果仍存储在该数组中。

3. 线性建堆法

知道了堆排序的算法原理,但如何将一个待排序数组建成一个堆呢?

在这里简单介绍一下线性建堆法

线性建堆法的算法原理是:将当前数组看成一个完全二叉树,再将该完全二叉树从后到前一次性扫描所有拥有子节点的节点,最终将数组调整成堆。

[点击并拖拽以移动]

4. 算法分析

(1)排序稳定性:

对于数组a[1…n],假设第3~第n号元素已排序好,且a[1] = a[2],则对于第n - 1次循环,a[1]将与a[2]互换,且在第n次循环时循环结束,此时a[1]与a[2]不满足排序前的相对位置,即最终排序好的数组不满足排序稳定性,因此堆排序是不稳定的。

(2)时间复杂度:

建堆时,需要深搜遍历完全二叉树的每一个节点,并按照堆性质进行调整,即需要遍历整个数组,时间复杂度为O(n)

弹出后调整堆时,只需要对一个节点按照堆性质进行调整,即调整堆层数次数,时间复杂度为O(logn)

每次弹出后都需要进行调整,共弹出n次、调整n次。

因此,堆排序算法的时间复杂度为: O ( n log ⁡ n ) O(n\log n) O(nlogn)

5. 代码演示


// 线性建堆
int* buildheap(int n, int* a) // n为数组元素个数,a为数组,函数返回建成堆的数组
{
    for(int i = n / 2; i >= 1; --i)
    {
        int j = i;
        while(j <= n / 2) // 限定节点有子节点,不断dfs
        {
            if(a[j] < a[2 * j] || (a[j] < a[2 * j + 1] && 2 * j + 1 <= n)) // 节点向下调整
            {
                int ind = 0;
                if(a[2 * j] < a[2 * j + 1] && 2 * j + 1 <= n)
                {
                    ind = 2 * j + 1;
                }
                else
                {
                    ind = 2 * j;
                }
                swap(a[j], a[ind]);
                j = ind;
            }
            else // 节点无需向下调整,break
                break;
        }
    }
    return a;
}

// 堆排序
void heap_sort(int n, int* a) // n为数组元素个数,a为数组,函数返回排好序的数组
{
    a = buildheap(n, a);
    int num = n; // num用来控制当前堆中元素个数
    for(int i = n; i > 1; --i) // 只需执行n - 1次循环
    {
        swap(a[i], a[1]);
        num -= 1;
        if(num == 1) // 当本次循环堆中只有一个元素时,直接结束即可
            break;
        int j = 1;
        while(j <= num / 2)
        {
            if(a[j] < a[2 * j] || (a[j] < a[2 * j + 1] && 2 * j + 1 <= num))
            {
                int ind = 0;
                if(a[2 * j] < a[2 * j + 1] && 2 * j + 1 <= num)
                {
                    ind = 2 * j + 1;
                }
                else
                {
                    ind = 2 * j;
                }
                swap(a[j], a[ind]);
                j = ind;
            }
            else
                break;
        }
    }
    return;
}


  • 20
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值