【堆结构】+【堆排序】小白学习教程,详细版含代码

第一次写文章,有讲不清楚的地方请多包涵~


前言

最近在找算法工作,面试会手撕代码,以前学的基础好多都忘了,复习一下。动图做完了才发现好糊QAQ,下次想办法解决一下。


一、堆 (Heap) 的概念

其实堆(Heap)就是树,一种特殊的完全二叉树,不知道完全二叉树的童鞋有兴趣可以去了解一下,不了解也没关系,对学习堆没有影响。

1. 堆的结构

我们先看一下堆结构具体长什么样(以大顶堆为例):
大顶堆结构图
首先,堆的节点是有序的
a. 对于大顶堆(如上图),每个父节点的值都大于等于子节点的值。比如“16”就大于它的两个子节点:“14”和“10”,而“14”又大于它的两个子节点“8”和“7”,其他父节点也是一样的。
b. 对于小顶堆(如下图),每个父节点的值都小于等于子节点的值。
在这里插入图片描述

2. 堆的存储结构

堆通常使用数组来进行顺序存储,其中根节点存储在数组的第一个位置(索引为 0),并且根据完全二叉树的特性,可以通过索引计算出每个节点的父节点和子节点的位置关系。也就是在数组中,从上至下每一层都从左至右依次顺序存入。
在这里插入图片描述

3. 下标计算

  • 下标为 i 的节点的父节点下标:( i - 1) / 2【取整】
  • 下标为 i 的节点的左孩子节点下标:i * 2 + 1
  • 下标为 i 的节点的右孩子节点下标:i * 2 + 2

其实到这里堆就讲完了,什么!这么快,是的,你已经完全掌握堆了。接下来就是这个堆出现了一些情况的解决方法而已。

二、维护堆

维护堆就是当堆中某个节点出现了不符合大顶堆或小顶堆规则的情况。 比如下图,
在这里插入图片描述
整体来看,该堆是大顶堆,但是“4”这个节点却小于它的子节点(小于其任何一个子节点都不行),那么我们就要对这个堆进行维护,使它变成正确的大顶堆形式。

维护思路:

  • 输入存储堆的数组 arr[],长度为n, 标记当前维护节点为 i,同时令largest = i;
  • 分别比较 i 的左孩子节点 lson 、右孩子节点 rson 的值与 i 的值,如果孩子节点大于父节点,则将largest更新为孩子节点的下标;
  • 如果 largest 不等于 i(也就是出现了孩子节点大于父节点的情况),则交换 arr[largest] 和 arr[i] 的值,并递归调用 heapify() 函数来确保交换后的子树也满足堆的性质。
    搞了个动画来康康,方便理解这个过程。
    在这里插入图片描述

维护堆代码如下

def heapify(arr, n, i):
    # 初始化最大值为当前节点索引
    largest = i
    # 计算左子节点和右子节点的索引
    lson = i * 2 + 1
    rson = i * 2 + 2

    # 如果左子节点存在且大于当前节点,则更新最大值索引
    if lson < n and arr[largest] < arr[lson]:
        largest = lson
    # 如果右子节点存在且大于当前节点,则更新最大值索引
    if rson < n and arr[largest] < arr[rson]:
        largest = rson
    # 如果最大值索引不等于当前节点索引,表示需要进行交换操作
    if largest != i:
        # 交换当前节点和最大值节点的值
        arr[largest], arr[i] = arr[i], arr[largest]
        # 对交换后的最大值节点递归执行维护操作
        heapify(arr, n, largest)

三、堆排序

  1. 建堆
    其实就是给一个乱序的数组,把这个数组看成存储的待维护的堆,从堆中最后一个父节点开始向上维护
def heap_sort(arr, n):
    # 建堆
    for i in range(n//2 - 1, -1, -1):
        heapify(arr, n, i)
  1. 堆排序
    排序的思路就是,把堆顶元素(最大值)与最后一个元素交换,然后把交换后的最后一个元素(即最大值)取出,剩下元素构成一个新堆,再从堆顶开始维护。重复这个过程,就能从大到小将堆中元素排序。

堆排序代码如下

def heap_sort(arr, n):
    # 建堆
    for i in range(n//2 - 1, -1, -1):
        heapify(arr, n, i)
    # 排序
    for i in range(n - 1, 0, -1):
        # 交换堆顶元素和最后一个元素
        arr[i], arr[0] = arr[0], arr[i]
        # 从堆顶开始维护,此时arr[]长度比之前-1
        heapify(arr, i, 0)

总的代码如下:

def heapify(arr, n, i):
    # 初始化最大值为当前节点索引
    largest = i
    # 计算左子节点和右子节点的索引
    lson = i * 2 + 1
    rson = i * 2 + 2

    # 如果左子节点存在且大于当前节点,则更新最大值索引
    if lson < n and arr[largest] < arr[lson]:
        largest = lson
    # 如果右子节点存在且大于当前节点,则更新最大值索引
    if rson < n and arr[largest] < arr[rson]:
        largest = rson
    # 如果最大值索引不等于当前节点索引,表示需要进行交换操作
    if largest != i:
        # 交换当前节点和最大值节点的值
        arr[largest], arr[i] = arr[i], arr[largest]
        # 对交换后的最大值节点递归执行维护操作
        heapify(arr, n, largest)

def heap_sort(arr, n):
    # 建堆
    for i in range(n//2 - 1, -1, -1):
        heapify(arr, n, i)
    # 排序
    for i in range(n - 1, 0, -1):
        arr[i], arr[0] = arr[0], arr[i]
        heapify(arr, i, 0)
# 初始化堆元素
arr = [2, 3, 8, 1, 4, 9, 10, 7, 16, 14]
n = 10
# 堆排序
heap_sort(arr, n)
# 打印
print(arr)

总结

堆其实就是一种特殊的树,最常见的类型为大顶堆和小顶堆,堆中出现不合规的点需要对该点进行堆维护即可恢复成大顶堆或小顶堆的样式。堆排序包含建堆、排序,建堆从最后一个父节点向上维护;排序将堆顶元素与最后元素交换,再从堆顶开始向下维护。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

奇迹行者还在打野

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

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

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

打赏作者

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

抵扣说明:

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

余额充值