堆排序算法詳解及源代碼分析


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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM