排序算法-堆排序

1、堆

堆在形式上是一棵完全二叉树(假设树的高度是h,所有的叶子结点都是在第h层或h-1层)

堆分为两类,大根堆和小根堆:

  • 大根堆:每个结点的值都大于等于其孩子结点的值;
  • 小根堆:每个结点的值都小于等于其孩子结点的值;

堆在形式上是一棵完全二叉树,用数组存储它,不会浪费空间;

2、堆排序的过程(大根堆)

堆排序中最关键的操作是将序列调整为堆。

(1)从无序序列所确定的完全二叉树的第一个非叶子结点开始,从右到左,从下到上,对每个结点进行调整,最终得到一个大根堆。

对结点的调整方法:

        1)将当前结点(假设为a)的值与它的孩子结点进行比较,如果存在大于a的孩子结点,则从中选出最大的一个与a交换。

        2)当a来到下一层的时候重复 1)过程,直到a的孩子结点值都小于a为止。

(2)将根结点(假设为c)与无序序列中最后一个元素(假设为d)交换,c进入有序序列,到达最终位置。此时只有结点d可能不满足堆,对其进行调整。

(3)重复(2),直到无序序列只剩下1个时,排序结束。

3、算法实现

def sift(arr, i, n):  
    j = 2*i + 1
    temp = arr[i]
    while j < n:
        if j + 1 < n and arr[j] < arr[j+1]:
            j += 1
        if temp >= arr[j]:
            break
        arr[i] = arr[j]
        i = j
        j = 2*i+1    
    arr[i] = temp
def heap_sort(arr):
    if not arr or len(arr) <= 1:
        return
    n = len(arr)
    for i in range(n//2-1, -1, -1):  # 建堆
        sift(arr, i, n)
    for i in range(n, 1, -1):
        arr[0], arr[i-1] = arr[i-1], arr[0]  # 堆顶和最后一个无序元素交换位置
        sift(arr, 0, i-1)     # 调整堆

if __name__ == '__main__':
    a = [10, 1, 5, 2, 4, 3, 2, 1]
#     a = [5, 8, 7, 6, 3, 2, 1]
    heap_sort(a)
    print(a)

4、复杂度分析

(1)时间复杂度分析

时间复杂度为O(nlog_2n)

对于sift() 函数,走了当前结点到叶子结点的路径,完全二叉树的高度为log_2n+1,即对每个结点调整的时间复杂度为O(log_2n)。对于heap_sort()函数,第1个循环的基本操作次数约为O(log_2n)\times n/2,第2个循环的基本操作次数约为O(log_2n)\times (n-1),所以整个算法的基本操作次数为O(log_2n)\times n/2 + O(log_2n)\times (n-1),化简后得其时间复杂度为nO(log_2n)

堆排序在最坏情况下,时间复杂度也为nO(log_2n),这是相对于快速排序的最大优点。

(2)空间复杂度分析

空间复杂度为O(1)。

额外空间有一个temp,所以空间复杂度为O(1)。

五、适用场景

堆排序适用于序列数比较多的场景,比如从1万个序列中选出前10个。

不适合用于序列小‌的场景。原因主要在于构建初始堆时,对于每个元素都需要进行一定数量的比较操作,以确保满足堆的性质。在数据量较小的情况下,这种比较与交换的操作成本相对较高,导致堆排序在处理少量数据时相比其他排序算法可能不是最优选择。

PS:堆排序是一种不稳定的排序算法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值