堆排序的时间复杂度是O(N*longN),空间复杂度O(1),是一个不稳定的排序。
1. 堆结构
可以分为大顶堆和小顶堆,是一个完全二叉树。而堆排序是根据堆的这种数据结构设计的一种排序。
1.1. 大顶堆和小顶堆
大顶堆定义:每个节点的值都大于其左孩子和右孩子节点的值。
小顶堆定义:每个节点的值都小于其左孩子和右孩子节点的值。
原始数据,20,10,59,13,54,70
大顶堆建造过程(图中绿色表示节点需要调整):
第一步:将第一个元素放入一个堆顶,自己和自己比较。
第二步:将10放入第一个元素的左节点上,发现父节点大于新增的子节点,不调整堆结构。
第三步:将59放入第一个元素的右节点上,发现父节点小于新增的子节点,调整堆结构。
第四步:将13放入10节点的左节点上,发现父节点小于新增的子节点,调整堆结构。
第五步:将54放入13节点的右节点上,发现父节点小于新增的子节点,调整堆结构。
第六步:将70放入20节点的左节点上,发现父节点小于新增的子节点,调整堆结构。
调整过后发现上层几点也不满足大顶堆性质,所以继续调整。
关于节点位置在二叉树中的计算公式:
父节点索引 (current - 1)/ 2
左孩子索引 2*current + 1
右孩子索引 2*current + 2
总结:
添加规则:
当最下一层节点未满时,新的节点从左到右依次添加。
当最下一层节点满时,会新增一层,新的节点从左到右依次添加。
只有这样添加才能保证是完全二叉树。
调整规则:
如果发现新加入的节点大于或小于(大顶堆大于,小顶堆小于)父节点,那么调整当前节点与父节点的位置(这里不需要关心另一个节点,因为他一定是最小或最大(大顶堆最小,小顶堆最大)的。
调整后发现调整过后的父节点大于爷爷节点,那么继续调整,直到调整到根。
大顶堆需要调整的伪代码:arr[i] > arr[(current - 1)/ 2]
小顶堆需要调整的伪代码:arr[i] < arr[(current - 1)/ 2]
2. 堆排序
思想:
- 将一个数组构建成为大顶堆,此时最大值就在数组中的第一个位置。
- 将数组中的头元素与尾元素进行交换,这是需要排序的数组长度-1。
- 将需要排序的数组再次构建称为大顶堆,将数组中的头元素与尾元素进行交换,排序的数组长度-1。再次待排序数组进行构建大顶堆,反复执行,直到带排序数组长度为1停止。
画图说明下图如何变成有序的。
第一步:将最大元素与左后一个元素进行交换(绿色表示需要交换,蓝色表示不在使用元素),
第二步:将除70以外的节点再次构建称为大顶堆。
将头节点和尾节点交换(绿色表示需要交换,蓝色表示不在使用元素)。
第N步骤:将剩下的元素构建称为大顶堆,将最到的元素放在尾部,直到戴排序数组长度为1。
3. 代码实现堆排序
package com
class DumpSortDemo {
static void main(String[] args) {
def arr = new int[]{20, 10, 59, 13, 54, 70}
heapSort(arr)
println arr
}
static void heapSort(int[] arr) {
for (i in 0..<arr.length) {
buildBigTopDump(arr, arr.length - i - 1)
swap(arr, 0, arr.length - i - 1)
}
}
static void buildBigTopDump(int[] arr, int endIndex) {
for (i in 0..endIndex) {
// 获取父元素位置
int parentIndex = (i - 1) / 2
// 新增元素大于父元素 。如果相同则退出
while (arr[i] > arr[parentIndex]) {
// 进行交换
swap(arr, i, parentIndex)
// 已经到头元素了直接结束
if (parentIndex == 0) {
break
}
// 获取爷爷元素位置,让其和新加入的元素进行比较
i = parentIndex
parentIndex = (parentIndex - 1) / 2
}
}
}
static void swap(int[] arr, int i, int j) {
def temp = arr[i]
arr[i] = arr[j]
arr[j] = temp
}
}
实验数据:20,10,59,13,54,70
运行结果: