堆排序是指利用堆这种数据结构所设计的一种排序算法。

类型:选择排序
时间复杂度(最坏):O(nlogn)
时间复杂度(最好):O(nlogn)
时间复杂度(平均):O(nlogn)
空间复杂度:O(1)
稳定性:不稳定(堆的根元素与最后一个叶节点交换值时会导致不稳定)

堆是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。且完全二叉树可以基于数组存储(父子节点的关系可以用数组下标表示),加持上堆的特性,故可以做堆排序。

满二叉树:除最后一层无任何子节点外,每一层上的所有结点都有两个子结点二叉树;即每一层节点数都达到最大值;即深度为 k 的二叉树节点个数为 2^k-1,则为满二叉树。

完全二叉树:若设二叉树的深度为 k,除第 k 层外,其它各层 (1~k-1) 的结点数都达到最大个数,且第 k 层所有的结点都连续集中在最左边,这就是完全二叉树。

堆:基于完全二叉树,分为大顶堆/小顶堆。各节点的数值皆大于其左右子节点的数值(大顶堆),或各节点的数值皆小于其左右子节点的数值(小顶堆)。

堆的特性

大顶堆,节点的数值皆大于子节点的数值,下文都以大顶堆为实例讲解

因为是基于完全二叉树的,所以堆还有一些相应的特性:

1、位序为 n 的节点,其父节点位序为 (int) n/2
2、位序为 n 的节点,其左子节点位序为 2n,右子节点位序为 2n+1(这是按照位序从1开始计算,但对编程语言不太友好,如果位序从0开始计算,则左子节点位序为 2n+1, 右子节点位序为 2n+2)

按照数组下标的方式去编号的话如下:

               0/   \1     2/ \  /  \3    4 5   6.../floor(n/2)  .../  n/ \
2n+1  2n+2

可以发现,所有非叶子节点的序号都会落在 0 ~ (int) N/2-1 区间中,N为节点总数,例如:
1、有4个节点,节点编号为0~3,非叶子节点编号区间值为 0~1,即编号为 0,1 的节点为非叶子节点。
2、有5个节点,节点编号为0~4,非叶子节点编号区间值为 0~1,即编号为 0,1 的节点为非叶子节点。
3、有6个节点,节点编号为0~5,非叶子节点编号区间值为 0~2,即编号为 0,1,2 的节点为非叶子节点。
4、有7个节点,节点编号为0~6,非叶子节点编号区间值为 0~2,即编号为 0,1,2 的节点为非叶子节点。

可以很方便的获取完全二叉树的非叶子节点的序号集合,从 k-1 层开始,依次将各非叶子节点及其左右子节点视为一个完全二叉树,将其调整至大顶堆,直至根节点,这时整个二叉树就是一个大顶堆

我们有了非叶子节点的序号及获取左右节点序号的算式,所以很容易就可以将各非叶子节点及其左右子节点组成的完全二叉树调整至大顶堆。同时要注意如果非叶子节点同其左右子节点发生了调整,其左右子节点如果也是非叶子节点的话,也要检测是否破坏了堆特性,如破坏也需进行调整。

堆排序

堆排序:

  • 将初始待排序关键字序列(R0,R1….Rn-1)构建成大顶堆,此堆为初始的无序区
  • 将堆顶元素R[0]与最后一个元素R[n-1]交换,此时得到新的无序区(R0,R1,...,Rn-2)和新的有序区(Rn-1),且满足R[1,2…n-2]<=R[n-1]
  • 由于交换后新的堆顶R[0]可能违反堆的性质,因此需要对当前无序区(R0,R1,...,Rn-2)调整为新堆,然后再次将R[0]与无序区最后一个元素交换,得到新的无序区(R0,R1….Rn-3)和新的有序区(Rn-2,Rn-1)。不断重复此过程直到有序区的元素个数为n-1(第n-1次调整时完全二叉树只有2个节点,即可有序化),则整个排序过程完成。

即堆排序的过程:将待排序数组映射到完全二叉树中,通过调整完全二叉树至大顶堆,获取根节点的值(节点最大值)

那大顶堆的构建如何做呢?我用比较白话的方式讲一下

1、从 k 层开始,将每一层子节点的最大值与其父节点的值进行比较调整(如发生交换且子节点为非叶子节点,也要检查子节点的树是否满足了父节点大于子节点的特性)
2、重复第一步骤直到根节点,此时根节点的值即为完全二叉树节点中的最大值,即生成了大顶堆

时间复杂度:O(nlogn)
空间复杂度:O(1)

golang 源码实例

package mainimport ("fmt"
)func main() {// 待排序数组 go 的数组传参是值拷贝 我们用切片引用传值更方便些var arr = []int{1, 6, 2, 4, 5, 3, 7, 9, 8}HeapSort(arr)fmt.Print("sorted: ", arr)
}// 堆排序
func HeapSort(arr []int) {arrLength := len(arr)// 每一次的堆构建都能得到一个节点中的最大值(根节点)对于N个待排序数列,N-1次堆构建即可得出有序数列for i := 0; i < arrLength; i++ {// 无序区长度arrLengthUnsorted := arrLength - i// 无序区构建为完全二叉树后非叶节点的下标的范围unLeafNodeIndexRange := int(arrLengthUnsorted/2 - 1)// 从 k - 1 层开始 将非叶节点的子树分治递归构建成大顶堆for j := unLeafNodeIndexRange; j >= 0; j-- {HeapBuild(arr, j, arrLengthUnsorted)}// 打印下标 0 ~ arrLengthUnsorted-1 的数列fmt.Println("current heap: ", arr[0:arrLengthUnsorted])// 一次大顶堆构建完成,根节点为堆最大值,与无序区堆的最后一个节点交换// 无序区节点数-1// 破坏了堆结构 开始对新无序区做大顶堆构建SwapItemOfArray(arr, 0, arrLengthUnsorted-1)}
}// 将子树调整为大顶堆
func HeapBuild(arr []int, nodeIndex int, arrLengthUnsorted int) {// 完全二叉树子节点下标同父节点下标的关系式leftChildNodeIndex, rightChildNodeIndex := nodeIndex*2+1, nodeIndex*2+2// 防止子节点下标越界 && 子节点数值大于父节点 则交换节点值if leftChildNodeIndex < arrLengthUnsorted && arr[leftChildNodeIndex] > arr[nodeIndex] {SwapItemOfArray(arr, leftChildNodeIndex, nodeIndex)HeapBuild(arr, leftChildNodeIndex, arrLengthUnsorted) //左子树根节点改变 需调整堆结构}if rightChildNodeIndex < arrLengthUnsorted && arr[rightChildNodeIndex] > arr[nodeIndex] {SwapItemOfArray(arr, rightChildNodeIndex, nodeIndex)HeapBuild(arr, rightChildNodeIndex, arrLengthUnsorted) //右子树根节点改变 需调整堆结构}}// 交换数组两个元素
func SwapItemOfArray(arr []int, indexX int, indexY int) {temp := arr[indexX]arr[indexX] = arr[indexY]arr[indexY] = temp
}

运行过程及结果

current heap: [9 8 7 6 5 2 3 1 4]
current heap: [8 6 7 4 5 2 3 1]
current heap: [7 5 6 1 4 2 3]
current heap: [6 4 5 1 3 2]
current heap: [5 3 4 1 2]
current heap: [4 2 3 1]
current heap: [3 1 2]
current heap: [2 1]
current heap: [1]
sorted: [1 2 3 4 5 6 7 8 9]

排序算法 - 堆排序相关推荐

  1. 经典排序算法 - 堆排序Heap sort

    经典排序算法 - 堆排序Heap sort 堆排序有点小复杂,分成三块 第一块,什么是堆,什么是最大堆 第二块,怎么将堆调整为最大堆,这部分是重点 第三块,堆排序介绍 第一块,什么是堆,什么是最大堆 ...

  2. 排序算法 | 堆排序,算法的图解、实现、复杂度和稳定性分析

    今天讲解一下堆排序的原理以及实现.复杂度和稳定性分析 目录 1 堆的定义 2 堆排序的思路 3 代码实现 4 堆的输出(删除操作) 5 堆的插入操作 6 堆排序的特点 7 性能分析 1 堆的定义 堆排 ...

  3. php+堆排序算法,排序算法-堆排序-php

    什么是堆排序 堆排序是我们经常使用的排序算法,它是利用堆的结构进行排序,堆是一种二叉树结构,并且它的父节点的值都大于子节点或者都小于子节点,如果大于,就是大顶堆,如果小于就是小顶堆. 根据堆的定义,我 ...

  4. 数据结构与算法 / 排序算法 / 堆排序

    一.定义 借助堆结构实现的排序算法被称为堆排序. 二.过程说明 1.建堆 (1)方法1 原地建堆,对于数组来说,从前往后:对于树来说,从下向上. 将数组的第一个元素作为堆顶,第二个元素做向堆中插入数据 ...

  5. 排序算法-堆排序(C语言版)

    堆排序是一种基于完全二叉树结构的一种排序算法,其整体思想很简单,就是构建完全二叉树,但是这里需要引入堆的概念.如下 大顶堆:每个结点的值都大于或等于其左右孩子结点的值 小顶堆:每个结点的值都小于或等于 ...

  6. 十大经典排序算法----堆排序(超详细)

    目录 1. 堆排序的基础知识 1.1 大顶堆&&小顶堆 1.2 向下调整算法 1.3 物理结构与逻辑结构的关系 2. 堆排序详解 2.1 堆排序整体思路 2.2 思路详解 2.2.1  ...

  7. 排序算法 —— 堆排序

    引言 此文基于<经典数据结构--堆的实现>中堆结构,实现一个以堆处理排序的算法. 一.算法思想 基于堆结构的堆排序的算法思想非常简单,循环获取大根堆中的最大值(0位置的根节点)放到堆的末尾 ...

  8. 八大排序算法--堆排序

    序言 对于堆排序的学习,实际上就是对于 堆 这一种数据结构的学习,把堆学会了,堆排序自然也就学会了. 1.为什么使用堆这种数据结构 优先队列是一种很常用的队列,比如在游戏中,游戏角色在自动模式下,如何 ...

  9. 排序算法-------堆排序

    对于n个元素的序列{R0, R1, ... , Rn}当且仅当满足下列关系之一时,称之为堆: (1) Ri <= R2i+1 且 Ri <= R2i+2 (小顶堆) (2) Ri > ...

最新文章

  1. React、Vue、Angular对比 ---- 介绍及优缺点
  2. python内置函数map reduce filter详解,面试必备知识
  3. centos 7.0 查看selinux状态|关闭|开启
  4. c语言第七章函数调用题库,c语言题库7-函数.doc
  5. 消息队列入门(三)JMS标准及实现
  6. python3 缺少PIP解决办法
  7. 极大似然估计、拉普拉斯平滑定理、M-估计详解
  8. php中数据库怎样增加一列,php – 向wordpress数据库添加新列
  9. SURF算法学习心得
  10. Unity3d 调用C++写的DLL
  11. mil mm 单位换算
  12. 神奇软件:良心浏览器 纯净无捆绑,还有亿点点好用360极速浏览器X
  13. 固态硬盘 格式化 linux,ssd固态硬盘格式化图文详细教程
  14. IE 浏览器 安装证书 无响应 卡死
  15. UVA 488 Triangle Wave
  16. visual stadio code(VS code) 中 Markdown简明操作[持续更新]
  17. 【软件工具】之下载微软官方正版 windows 系统
  18. Xamarin for android 小白教程-HelloWorld !
  19. uni-app新闻小程序
  20. usc计算机科学硕士分支方向,一次说清北美计算机科学硕士的项目,选校,申请,排名,难度...

热门文章

  1. 04.微博消息的语言检测
  2. OpenCV 【二十】给图像添加边界
  3. C#对Microsoft.VisualBasic My对象兰台妙选【月儿原创】
  4. EMAIL发送系统(C#+基于SMTP认证) 2.0
  5. ASP.net中太长的数据缩略显示
  6. asp.net中显示DataGrid控件列序号的几种方法
  7. Java开发环境的搭建以及使用eclipse创建项目
  8. libev源码解析——I/O模型
  9. DllMain中不当操作导致死锁问题的分析——线程中调用GetModuleFileName、GetModuleHandle等导致死锁
  10. CUDA Samples: approximate prior vbox layer