堆排序源代码详细分析

        之前介绍过几种排序算法,今天说一说堆排序算法。虽然堆排序在实践中不常用,经常被快速排序的效率打败,但堆排序的优点是与输入的数据无关,时间复杂度稳定在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((i1)/2)]

左孩子:Left(i)left(i)A[2i+1]

右孩子:Right(i)right(i)A[2i+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

 

猜你喜欢

转载自www.cnblogs.com/outerspace/p/11098461.html