排序算法 - 堆排序

堆排序是一种树形选择排序,是对直接选择排序的有效改进。

基本思想

堆的定义如下:具有n个元素的序列(k1,k2,…,kn),当且仅当满足
堆的条件

时称之为堆。由堆的定义可以看出,堆顶元素(即第一个元素)必为最小项(小顶堆)。 若以一维数组存储一个堆,则堆对应一棵完全二叉树,且所有非叶结点的值均不大于(或不小于)其子女的值,根结点(堆顶元素)的值是最小(或最大)的。如:
(a)大顶堆序列:(96, 83,27,38,11,09)
(b)小顶堆序列:(12,36,24,85,47,30,53,91)
堆举例

初始时把要排序的n个数的序列看作是一棵顺序存储的二叉树(一维数组存储二叉树),调整它们的存储顺序,使之成为一个堆,将堆顶元素输出,得到n 个元素中最小(或最大)的元素,这时堆的根节点的数最小(或者最大)。然后对前面(n-1)个元素重新调整使之成为堆,输出堆顶元素,得到n 个元素中次小(或次大)的元素。依此类推,直到只有两个节点的堆,并对它们作交换,最后得到有n个节点的有序序列。称这个过程为堆排序。
因此,实现堆排序需解决两个问题:

  1. 如何将n 个待排序的数建成堆;
  2. 输出堆顶元素后,怎样调整剩余n-1 个元素,使其成为一个新堆。

首先讨论第二个问题:输出堆顶元素后,对剩余n-1元素重新建成堆的调整过程。 调整小顶堆的方法:
1)设有m 个元素的堆,输出堆顶元素后,剩下m-1 个元素。将堆底元素送入堆顶((最后一个元素与堆顶进行交换),堆被破坏,其原因仅是根结点不满足堆的性质。
2)将根结点与左、右子树中较小元素的进行交换。
3)若与左子树交换:如果左子树堆被破坏,即左子树的根结点不满足堆的性质,则重复方法 (2).
4)若与右子树交换,如果右子树堆被破坏,即右子树的根结点不满足堆的性质。则重复方法 (2).
5)继续对不满足堆性质的子树进行上述交换操作,直到叶子结点,堆被建成。
称这个自根结点到叶子结点的调整过程为筛选。如图:
堆例子

再讨论对n 个元素初始建堆的过程。
建堆方法:对初始序列建堆的过程,就是一个反复进行筛选的过程。
1)n 个结点的完全二叉树,则最后一个结点是第节点个结点的子树。
2)筛选从第节点 个结点为根的子树开始,该子树成为堆。
3)之后向前依次对各结点为根的子树进行筛选,使之成为堆,直到根结点。
如图建堆初始过程:无序序列:(49,38,65,97,76,13,27,49)
建堆
建堆说明

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
package main.java.com.study.sortingAalgorithm;

import main.java.com.study.utils.CommonUtils;

/**
* @author: whb
* @description: 堆排序
*/
public class HeapSort {

/**
* 堆排序的是集合了插入排序的单数组操作,又有归并排序的时间复杂度,完美的结合了2者的优点。
*/
public static void heapSort(int[] numArr) {
//将无序堆构造成一个大根堆,大根堆有length/2个父节点
for (int i = numArr.length / 2 - 1; i >= 0; i--) {
headAdjust(numArr, i, numArr.length);
}
//逐步将每个最大值的根节点与末尾元素交换,并且再调整其为最大堆
for (int i = numArr.length - 1; i > 0; i--) {
//将堆顶元素和当前未经排序的子序列的最后一个元素交换位置
swap(numArr, 0, i);
headAdjust(numArr, 0, i);
}
}

/**
* 构造大根堆
*/
public static void headAdjust(int[] numArr, int parent, int length) {
//保存当前父节点
int temp = numArr[parent];
//得到左孩子
int leftChild = 2 * parent + 1;
while (leftChild < length) {
//如果parent有左孩子,则要判断左孩子是否小于右孩子
if (leftChild + 1 < length && numArr[leftChild] < numArr[leftChild + 1]) {
leftChild++;
}
//如果父节点大于子节点,就不交换
if (temp >= numArr[leftChild]) {
break;
}
//将较大子节点的值赋给父节点
numArr[parent] = numArr[leftChild];
//然后将子节点作为父节点
parent = leftChild;
//找到该父节点较小的左孩子
leftChild = 2 * parent + 1;
}
//最后将temp的值赋给较大的子节点,以形成两值交换
numArr[parent] = temp;
}

/**
* 交换数组中两个位置的元素
*/
public static void swap(int[] numArr, int top, int last) {
int temp = numArr[top];
numArr[top] = numArr[last];
numArr[last] = temp;
}

public static void main(String[] args) {
int[] numArr = {27, 11, 13, 45, 34, 22, 19, 8, 3, 99, 74, 55, 88, 66};
System.out.println("**************堆排序******************");
System.out.println("排序前:");
CommonUtils.display(numArr);
System.out.println("排序后:");
heapSort(numArr);
CommonUtils.display(numArr);
}
}

CommonUtils工具类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main.java.com.study.utils;

/**
* @author: whb
* @description: 工具类
*/
public class CommonUtils {

/**
* 遍历打印
*
* @param numArr
*/
public static void display(int[] numArr) {
if (numArr != null && numArr.length > 0) {
for (int num : numArr) {
System.out.print(num + " ");
}
}
System.out.println("");
}
}

测试结果

堆排序测试结果

本文标题:排序算法 - 堆排序

文章作者:王洪博

发布时间:2018年06月15日 - 10:06

最后更新:2019年09月12日 - 10:09

原始链接:http://whb1990.github.io/posts/1ca0adae.html

▄︻┻═┳一如果你喜欢这篇文章,请点击下方"打赏"按钮请我喝杯 ☕
0%