之前介绍过几种排序算法,今天说一说堆排序算法。虽然堆排序在实践中不常用,经常被快速排序的效率打败,但堆排序的优点是与输入的数据无关,时间复杂度稳定在O(N*lgN),不像快排,最坏的情况下时间复杂度为O(N2)。
说明,了解堆排序的前提是要掌握二叉树的概念,可自行百度,本文不做介绍。
说到堆排序,首先需要了解一种数据结构——堆。堆是一种完全二叉树,这种结构通常可以用数组表示。在实际应用中,堆又可以分为最小堆和最大堆,两者的区别如下:
-max-heap property :对于所有除了根节点(root)的节点 i,A[Parent(i)]≥A[i]
-min-heap property :对于所有除了根节点(root)的节点 i,A[Parent(i)]≤A[i]
对于用数组表示的堆来说,每个节点的父子节点索引关系如下:
索引为i的节点的父节点:Parent(i): parent(i)←A[floor((i−1)/2)]
左孩子:Left(i): left(i)←A[2∗i+1]
右孩子:Right(i): right(i)←A[2∗i+2]
了解完堆的基本概念以后,可以开始进行堆排序了。堆排序可以分解为两步:
1、将待排序数组,根据堆的定义,构建成一个堆。
2、排序:每次取下堆的顶部元素(最大或最小值),与堆的最后一个元素交换位置,并重构堆;在本步骤中,每取走一个顶部元素后,堆的大小都会减1.
重复第2步,直至堆的大小为1为止,此时堆只剩下1个元素,所有比它大(或小)的元素,均已按顺序排列在数组中,排序完成。
以下的分析以最大堆为例。
在以上过程中,用到了一个关键的函数:构建以某个元素为父元素的堆——_maxHeapify,该方法的源码及详细分析如下。具体:
1 void _maxHeapify<E extends Comparable<E>>(List<E> a, int i, int size) {
2 // 父节点为i,假设父节点元素为最大;左孩子为l,右孩子为r;
3 var mi = i, l = (i << 1) + 1, r = (i << 1) + 2;
4
5 // 寻找父、左、右中最大的那个节点
6 if (l < size && a[l].compareTo(a[mi]) > 0) mi = l;
7 if (r < size && a[r].compareTo(a[mi]) > 0) mi = r;
8
9 // 如果最大节点不是父节点,则将最大节点交换至父节点,完成节点i的最大堆构建;
10 if (mi != i) {
11 _swap(a, i, mi);
12
13 // 注意,交换以后,其对应的mi孩子节点的最大堆的性质可能被破坏,因此需递归调用
14 // 以保证堆的有序性;
15 _maxHeapify(a, mi, size);
16 }
17 }
对于一个数组,构建最大堆的过程,则是从最后一个元素的父元素开始,递归向上构建,直至根元素A[0]。具体代码分析如下:
1 void _buildMaxHeap<E extends Comparable<E>>(List<E> a) {
2 // 从最后一个节点的父节点开始,逐个向上
3 for (var i = (a.length >> 1) - 1; i >= 0; i--) _maxHeapify(a, i, a.length);
4 }
最后,开始使用堆排序,详细代码分析如下:
1 void heapSort<E extends Comparable<E>>(List<E> a) {
2 // 第一步:构建堆
3 _buildMaxHeap(a);
4
5 // i+1表示堆的大小.每次循环堆的大小减1;
6 // 当堆只剩1个元素时,排序结束。此时数组为升序排列
7 for (var i = a.length - 1; i > 0; i--) {
8 // 每次取出堆顶元素(也是最大的元素)放在堆的尾部。
9 _swap(a, 0, i);
10
11 // 交换上来的元素可能会破坏堆的性质,因此重构堆;
12 _maxHeapify(a, 0, i);
13 }
14 }
最后附上swap的代码:
1 void _swap<E>(List<E> a, int i, int j) {
2 var t = a[i];
3 a[i] = a[j];
4 a[j] = t;
5 }
从以上代码也可以看出,堆排序也不需要额外的存储空间,因此,堆排序的空间复杂度为O(1).
测试代码如下:
1 import 'dart:math';
2 import 'package:data_struct/sort/heap_sort.dart';
3
4 void main() {
5 var rd = Random();
6 // 随机生成一个数组
7 List<num> a = List.generate(12, (_) => rd.nextInt(200));
8
9 // 原始数组
10 print(a);
11 print('---------------------------------');
12
13 // 排序
14 heapSort(a);
15 // 排序后数组
16 print(a);
17 }
输出结果:
1 [182, 9, 105, 86, 181, 87, 166, 98, 90, 142, 192, 176]
2 ---------------------------------
3 [9, 86, 87, 90, 98, 105, 142, 166, 176, 181, 182, 192]
4 Exited