详解排序之堆排序

数据结构堆

堆是什么

堆是一种数据结构,它需要同时满足两个条件:

  1. 是一个完全二叉树

完全二叉树首先是一种二叉树结构,每一个节点最多有两个子节点:左子节点和右子节点

每一层节点,从左到右,依次排列

只有当前层节点都添加满了之后,才能继续添加下一层节点

  1. 所有父节点的值 >= 子节点的值,一般称之为大顶堆;或所有父节点的值 <= 子节点的值,一般称之为小顶堆

怎么样来表示堆

我们从根节点开始,依次给每一个节点添加编号0,1,2,3,4,此时可以用一个一维数组来表示堆

使用一维数组来表示堆后,还可以通过数组下标,来快速定位指定节点的父节点,左子节点和右子节点

假设指定节点的下标为i,则可以通过以下公式快速定位:

  • 父节点下标 = (i - 1)/ 2
  • 左子节点下标 = 2 * i + 1
  • 右子节点下标 = 2 * i + 2

假设当前节点的下标为1

父节点下标 = (1 - 1) / 2 = 0

左子节点下标 = 2 * 1 + 1 = 3

右子节点下标 = 2 * 1 + 2 = 4

如何用一个未排序数组构建一个堆

主要分为两个步骤:

  1. 挑选节点
  2. 进行heapify操作

当我们拿到一个未排序的数组时,可以通过这个数组来构建一个完全二叉树

此时满足了堆的第一个条件,但不满足第二个条件

所有父节点的值 >= 子节点的值,一般称之为大顶堆;或所有父节点的值 <= 子节点的值,一般称之为小顶堆

为了满足第二个条件,我们需要对不满足条件的节点的值进行交换操作,这个操作一般称之为heapify

heapify代码:

go 复制代码
package test

import (
   "fmt"
   "testing"
)

// arr: 数组
// i: 需要进行heapify操作的节点
// arrLen: 数组长度
func heapify(arr []int, i int, arrLen int) {
   // 假设最大值下标为i
   max := i
   // i节点的左子节点和右子节点
   left := 2*i + 1
   right := 2*i + 2
   // 左子节点下标未越界 && 左子节点值 > 最大值
   if left < arrLen && arr[left] > arr[max] {
      // 最大值下标变为左子节点下标
      max = left
   }
   // 右子节点下标未越界 && 右子节点值 > 最大值
   if right < arrLen && arr[right] > arr[max] {
      // 最大值下标变为右子节点下标
      max = right
   }
   // 最大值下标不为i
   if max != i {
      // 交换节点的值
      swap(arr, max, i)
      // 因为max节点的值已改变,递归对下标max进行heapify,保证堆结构
      heapify(arr, max, arrLen)
   }
}

func swap(arr []int, i, j int) {
   temp := arr[i]
   arr[i] = arr[j]
   arr[j] = temp
}

// 测试代码
func TestHeapify(t *testing.T) {
   arr := []int{5, 7, 8, 9, 4}
   heapify(arr, 1, 5)
   fmt.Println(arr)
}

heapify可以保证一个节点满足堆结构,我们只要循环对目标节点进行heapify操作,就可以使完全二叉树成为堆结构,所以我们的下一步是解决如何挑选节点的问题

从哪一个节点开始进行heapify操作:从最后一个节点的父节点开始,依次向前,循环进行heapify操作

构建堆代码:

go 复制代码
func buildHeap(arr []int) {
   // 获取数组长度
   arrLen := len(arr)
   // 最后一个元素下标
   last := arrLen - 1
   // 从最后一个节点的父节点开始,依次向前,循环进行heapify操作
   for i := (last - 1) / 2; i >= 0; i-- {
      heapify(arr, i, arrLen)
   }
}

堆排序

如何使用数据结构堆来进行排序

上面我们学习了如何用一个未排序数组来构建一个堆,当我们构建一个大顶堆或小顶堆后,我们可以得到一个数组内最大的值或最小的值

大顶堆的根节点就是数组内最大的值,此时我们交换根节点和最后一个节点,就可以把最大值放到最后一个节点中,也就是数组的最后一个元素

绿色元素代表已排序的元素,此时我们无需关心这些已排序的元素,只用看还未排序的元素即可

此时,由于我们交换了根节点和最后一个节点的值,为了满足堆结构,还需要对交换后的根节点进行heapify操作

进行完heapify操作后,我们又可以循环进行以上操作,交换,heapify操作。。。

完整堆排序

完整堆排序步骤:

  1. 构建堆
  2. 交换根节点和未排序的最后一个节点
  3. 对根节点进行heapify
  4. 数组长度-1,循环进行步骤2,步骤3

完整堆排序代码:

go 复制代码
package test

import (
   "fmt"
   "testing"
)

// 堆排序
func heapSort(arr []int) {
   // 构建堆
   buildHeap(arr)
   // 循环减少数组长度
   for i := len(arr) - 1; i > 0; i-- {
      // 交换根节点和未排序的最后一个节点
      swap(arr, 0, i)
      // 对根节点进行heapify
      heapify(arr, 0, i)
   }
}

func buildHeap(arr []int) {
   // 获取数组长度
   arrLen := len(arr)
   // 最后一个元素下标
   last := arrLen - 1
   // 从最后一个节点的父节点开始,依次向前,循环进行heapify操作
   for i := (last - 1) / 2; i >= 0; i-- {
      heapify(arr, i, arrLen)
   }
}

// arr: 数组
// i: 需要进行heapify操作的节点
// arrLen: 数组长度
func heapify(arr []int, i int, arrLen int) {
   // 假设最大值下标为i
   max := i
   // i节点的左子节点和右子节点
   left := 2*i + 1
   right := 2*i + 2
   // 左子节点下标未越界 && 左子节点值 > 最大值
   if left < arrLen && arr[left] > arr[max] {
      // 最大值下标变为左子节点下标
      max = left
   }
   // 右子节点下标未越界 && 右子节点值 > 最大值
   if right < arrLen && arr[right] > arr[max] {
      // 最大值下标变为右子节点下标
      max = right
   }
   // 最大值下标不为i
   if max != i {
      // 交换节点的值
      swap(arr, max, i)
      // 因为max节点的值已改变,递归对下标max进行heapify,保证堆结构
      heapify(arr, max, arrLen)
   }
}

func swap(arr []int, i, j int) {
   temp := arr[i]
   arr[i] = arr[j]
   arr[j] = temp
}

// 测试代码
func TestHeapify(t *testing.T) {
   arr := []int{5, 7, 8, 9, 4}
   heapSort(arr)
   fmt.Println(arr)
}
相关推荐
.生产的驴13 分钟前
SpringBoot 封装统一API返回格式对象 标准化开发 请求封装 统一格式处理
java·数据库·spring boot·后端·spring·eclipse·maven
景天科技苑21 分钟前
【Rust】Rust中的枚举与模式匹配,原理解析与应用实战
开发语言·后端·rust·match·enum·枚举与模式匹配·rust枚举与模式匹配
知来者逆22 分钟前
计算机视觉——速度与精度的完美结合的实时目标检测算法RF-DETR详解
图像处理·人工智能·深度学习·算法·目标检测·计算机视觉·rf-detr
阿让啊26 分钟前
C语言中操作字节的某一位
c语言·开发语言·数据结构·单片机·算法
এ᭄画画的北北27 分钟前
力扣-160.相交链表
算法·leetcode·链表
追逐时光者1 小时前
MongoDB从入门到实战之Docker快速安装MongoDB
后端·mongodb
方圆想当图灵1 小时前
深入理解 AOP:使用 AspectJ 实现对 Maven 依赖中 Jar 包类的织入
后端·maven
豌豆花下猫1 小时前
Python 潮流周刊#99:如何在生产环境中运行 Python?(摘要)
后端·python·ai
爱研究的小陈1 小时前
Day 3:数学基础回顾——线性代数与概率论在AI中的核心作用
算法
渭雨轻尘_学习计算机ing1 小时前
二叉树的最大宽度计算
算法·面试