详解排序之堆排序

数据结构堆

堆是什么

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

  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)
}
相关推荐
绝顶少年3 分钟前
Spring Boot 注解:深度解析与应用场景
java·spring boot·后端
CYRUS STUDIO6 分钟前
Unidbg Trace 反 OLLVM 控制流平坦化(fla)
android·汇编·算法·网络安全·逆向·ollvm
ゞ 正在缓冲99%…14 分钟前
leetcode22.括号生成
java·算法·leetcode·回溯
孪生质数-17 分钟前
SQL server 2022和SSMS的使用案例1
网络·数据库·后端·科技·架构
uhakadotcom21 分钟前
AWS Lightsail 简介与实践
后端·面试·github
小卡皮巴拉25 分钟前
【力扣刷题实战】矩阵区域和
开发语言·c++·算法·leetcode·前缀和·矩阵
WG_1733 分钟前
第五章.图论
算法·图论
神里流~霜灭44 分钟前
蓝桥备赛指南(12)· 省赛(构造or枚举)
c语言·数据结构·c++·算法·枚举·蓝桥·构造
小杨爱学习zb1 小时前
学习总结 网格划分+瞬态求解设置
笔记·学习·算法
双叶8361 小时前
(C语言)单链表(1.0)(单链表教程)(数据结构,指针)
c语言·开发语言·数据结构·算法·游戏