之前介紹過幾種排序算法,今天說一說堆排序算法。雖然堆排序在實踐中不常用,經常被快速排序的效率打敗,但堆排序的優點是與輸入的數據無關,時間復雜度穩定在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