排序算法专题-堆排序

  堆排序,顾名思义,就是一种基于堆这种数据结构来实现排序的一种算法,那么何谓堆呢?简单点说,堆是一个近似完全二叉树的结构,同时满足即子结点的键值或索引总是小于(或者大于)它的父节点,由此而生,堆分为两种,分别是小顶堆和大顶堆。

  • 大顶堆:每个节点的值都大于或等于其子节点的值,在堆排序算法中用于升序排列
  • 小顶堆:每个节点的值都小于或等于其子节点的值,在堆排序算法中用于降序排列
      从定义和特征上来看,堆排序的主要步骤就是构建一个堆,然后每次从堆里面取出最大(最小)的数来交换堆最后一个元素,知道堆的尺寸为1,就完成排序了。堆排序的步骤如下:
  • 1:创建一个堆 H[0……n-1];
    2:把堆首(最大值)和堆尾互换;
    3:把堆的尺寸缩小 1,并调用 shift_down(0),目的是把新的数组顶端数据调整到相应位置;
    4:重复步骤 2,直到堆的尺寸为 1。
    这里附上两个有图解的链接
    1:堆排序
    2:【算法】排序算法之堆排序

  • 堆的特点:如果一个结点位置为k,则其父节点位置为k/2,其两个子节点的位置分别为2k和2k+1。因此,从a[k]向上一层,令k=k/2,向下一层令k=2k或2k+1,我们可以基于这个特点来实现一个堆。这样有点抽象,我们再具体一点,我们迭代输入是原数组和k,那么我们比较的就是k、k=2k、2k+1,这三个位置的值,假如这三个位置k处为最大,表示这里已经是一个大顶堆,无需操作,如果2k+1处最大,表示这里不是一个大顶堆,需要进行交换,接下来就要把2k+1对应的值交换到k处,同时2k+1处不一定是大顶堆,需要进行迭代,那么为什么2k不用迭代呢?因为我们会用一个循环来控制迭代,0、1、2…k…len(arr)//2,采用倒序遍历,因此2k处必然已经是大顶堆,2k+1处由于已经发生了值的变动,不能保证仍然是大顶堆,所以需要进行迭代。生成堆的代码如下:
def buildMaxHeap(nums):
    """
    根据数组,在原数组基础上将其变成堆结构
    >>>nums = [3,38, 5, 44, 15, 36]      
    >>>buildMaxHeap(nums)
    >>>[44, 38, 36, 3, 15, 5]
    """
    for i in range(len(nums)//2, -1, -1):
        heapify(nums, i)
        
def heapify(nums, i):
    left = 2*i + 1
    right = 2*i + 2
    largest = i
    n = len(nums)
    if left < len(nums) and nums[left] > nums[largest]:
        largest = left
    if right < len(nums) and nums[right] > nums[largest]:
        largest = right
    if largest != i:
        nums[i], nums[largest] = nums[largest], nums[i]
        heapify(nums, largest)
nums = [3,38, 5, 44, 15, 36]      
buildMaxHeap(nums)
nums
  • 注:这里跟上面讲的有点不一样,左右节点分别是left = 2*i + 1, right = 2*i + 2,这是因为数组的索引是从0开始的。

  理解的堆之后,堆排序就不成问题了,每次将堆的第一个元素(最大值)和最后一个元素交换,然后维护[0, n-k]长度的堆,其中k为迭代次数。堆排序的所有代码如下:

def buildMaxHeap(nums):
    for i in range(len(nums)//2, -1, -1):
        heapify(nums, i)
        
def heapify(nums, i):
    left = 2*i + 1
    right = 2*i + 2
    largest = i
    n = len(nums)
    if left < currentLen and nums[left] > nums[largest]:
        largest = left
    if right < currentLen and nums[right] > nums[largest]:
        largest = right
    if largest != i:
        nums[i], nums[largest] = nums[largest], nums[i]
        heapify(nums, largest)
            
def heapSort(nums):
    """
    堆排序
    >>>nums = [3,38, 5, 44, 15, 36]
    >>>heapSort(nums)
    >>>[3, 5, 15, 36, 38, 44]
    """
    global currentLen
    currentLen = len(nums)
    buildMaxHeap(nums)
    for i in range(len(nums)-1, 0, -1):
        nums[0], nums[i] = nums[i], nums[0]
        currentLen -= 1
        heapify(nums, 0)
    return nums
    
nums = [3,38, 5, 44, 15, 36]
heapSort(nums)
  • 这里要注意由于每次迭代需要维护堆的长度发了变化,因此用一个全局变量currentLen来引导heapify(nums, 0)的正确维护。
  • 算法解析:堆排序全程在原数组上进行操作,因此空间复杂度为O(1)。时间复杂度要分为两个部分来看,首先buildMaxHeap()方法的时间复杂度很好计算为n/2*log n,也就是O(n log n),再看heapSort方法,从for循环开始,很显然时间复杂度为(n-1)*|log n|(|a|表示一个比a小的数),也就是O(n log n),总的来说该算法的时间复杂度为2*O(n log n),也就还是O(n log n)。
  • 稳定性分析:不看堆的生成,我们只看循环体内的交换,他是每次最大值和最末尾一个元素的交换,加入两个相同元素有同一个父节点,那么他们在堆中连号,当遍历到这两个元素时,处在较后面的元素必然会交换到0索引位置,所以必然会发生相同元素相对位置的改变,因此是不稳定排序算法。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

lemon_tttea

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

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

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

打赏作者

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

抵扣说明:

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

余额充值