咱們一般所說的堆是指二叉堆,二叉堆又稱徹底二叉樹或者叫近似徹底二叉樹。二叉堆又分爲最大堆和最小堆。ios
堆排序(Heapsort)是指利用堆這種數據結構所設計的一種排序算法,它是選擇排序的一種。能夠利用數組的特色快速定位指定索引的元素。數組能夠根據索引直接獲取元素,時間複雜度爲O(1),也就是常量,所以對於取值效率極高。算法
這裏以最大堆爲例:api
最大堆的特性以下:數組
父結點的鍵值老是大於或者等於任何一個子節點的鍵值安全
每一個結點的左子樹和右子樹都是一個最大堆數據結構
最大堆的算法思想是:dom
先將初始的R[0…n-1]創建成最大堆,此時是無序堆,而堆頂是最大元素函數
再將堆頂R[0]和無序區的最後一個記錄R[n-1]交換,由此獲得新的無序區R[0…n-2]和有序區R[n-1],且知足R[0…n-2].keys ≤ R[n-1].key性能
因爲交換後,前R[0…n-2]可能不知足最大堆的性質,所以再調整前R[0…n-2]爲最大堆,直到只有R[0]最後一個元素才調整完成。測試
最大堆排序完成後,實際上是升序序列,每次調整堆都是要獲得最大的一個元素,而後與當前堆的最後一個元素交換,所以最後所獲得的序列是升序序列。
構建堆:
1 #ifndef INC_06_HEAP_SORT_HEAP_H 2 #define INC_06_HEAP_SORT_HEAP_H 3 #include <algorithm> 4 #include <cassert> 5 using namespace std; 6 template<typename Item> 7 class MaxHeap{ 8 private: 9 Item *data; 10 int count; 11 int capacity; 12 13 void shiftUp(int k){ 14 while( k > 1 && data[k/2] < data[k] ){ 15 swap( data[k/2], data[k] ); 16 k /= 2; 17 } 18 } 19 20 void shiftDown(int k){ 21 while( 2*k <= count ){ 22 int j = 2*k; 23 if( j+1 <= count && data[j+1] > data[j] ) j ++; 24 if( data[k] >= data[j] ) break; 25 swap( data[k] , data[j] ); 26 k = j; 27 } 28 } 29 30 public: 31 32 // 構造函數, 構造一個空堆, 可容納capacity個元素 33 MaxHeap(int capacity){ 34 data = new Item[capacity+1]; 35 count = 0; 36 this->capacity = capacity; 37 } 38 // 構造函數, 經過一個給定數組建立一個最大堆 39 // 該構造堆的過程, 時間複雜度爲O(n) 40 MaxHeap(Item arr[], int n){ 41 data = new Item[n+1]; 42 capacity = n; 43 for( int i = 0 ; i < n ; i ++ ) 44 data[i+1] = arr[i]; 45 count = n; 46 47 for( int i = count/2 ; i >= 1 ; i -- ) 48 shiftDown(i); 49 } 50 ~MaxHeap(){ 51 delete[] data; 52 } 53 54 // 返回堆中的元素個數 55 int size(){ 56 return count; 57 } 58 59 // 返回一個布爾值, 表示堆中是否爲空 60 bool isEmpty(){ 61 return count == 0; 62 } 63 64 // 像最大堆中插入一個新的元素 item 65 void insert(Item item){ 66 assert( count + 1 <= capacity ); 67 data[count+1] = item; 68 shiftUp(count+1); 69 count ++; 70 } 71 72 // 從最大堆中取出堆頂元素, 即堆中所存儲的最大數據 73 Item extractMax(){ 74 assert( count > 0 ); 75 Item ret = data[1]; 76 swap( data[1] , data[count] ); 77 count --; 78 shiftDown(1); 79 return ret; 80 } 81 82 // 獲取最大堆中的堆頂元素 83 Item getMax(){ 84 assert( count > 0 ); 85 return data[1]; 86 } 87 }; 88 89 #endif
簡單堆排序:
1 #ifndef INC_06_HEAP_SORT_HEAPSORT_H 2 #define INC_06_HEAP_SORT_HEAPSORT_H 3 #include "Heap.h" 4 using namespace std; 5 // heapSort1, 將全部的元素依次添加到堆中, 在將全部元素從堆中依次取出來, 即完成了排序 6 // 不管是建立堆的過程, 仍是從堆中依次取出元素的過程, 時間複雜度均爲O(nlogn) 7 // 整個堆排序的總體時間複雜度爲O(nlogn) 8 template<typename T> 9 void heapSort1(T arr[], int n){ 10 11 MaxHeap<T> maxheap = MaxHeap<T>(n); 12 for( int i = 0 ; i < n ; i ++ ) 13 maxheap.insert(arr[i]); 14 15 for( int i = n-1 ; i >= 0 ; i-- ) 16 arr[i] = maxheap.extractMax(); 17 } 18 // heapSort2, 藉助咱們的heapify過程建立堆 19 // 此時, 建立堆的過程時間複雜度爲O(n), 將全部元素依次從堆中取出來, 實踐複雜度爲O(nlogn) 20 // 堆排序的整體時間複雜度依然是O(nlogn), 可是比上述heapSort1性能更優, 由於建立堆的性能更優 21 template<typename T> 22 void heapSort2(T arr[], int n){ 23 24 MaxHeap<T> maxheap = MaxHeap<T>(arr,n); 25 for( int i = n-1 ; i >= 0 ; i-- ) 26 arr[i] = maxheap.extractMax(); 27 } 28 #endif
插入排序:
1 #ifndef INC_06_HEAP_SORT_INSERTIONSORT_H 2 #define INC_06_HEAP_SORT_INSERTIONSORT_H 3 #include <iostream> 4 #include <algorithm> 5 using namespace std; 6 template<typename T> 7 void insertionSort(T arr[], int n){ 8 9 for( int i = 1 ; i < n ; i ++ ) { 10 11 T e = arr[i]; 12 int j; 13 for (j = i; j > 0 && arr[j-1] > e; j--) 14 arr[j] = arr[j-1]; 15 arr[j] = e; 16 } 17 18 return; 19 } 20 21 // 對arr[l...r]範圍的數組進行插入排序 22 template<typename T> 23 void insertionSort(T arr[], int l, int r){ 24 25 for( int i = l+1 ; i <= r ; i ++ ) { 26 27 T e = arr[i]; 28 int j; 29 for (j = i; j > l && arr[j-1] > e; j--) 30 arr[j] = arr[j-1]; 31 arr[j] = e; 32 } 33 34 return; 35 } 36 37 #endif
歸併排序:
1 #ifndef INC_06_HEAP_SORT_MERGESORT_H 2 #define INC_06_HEAP_SORT_MERGESORT_H 3 4 #include <iostream> 5 #include <algorithm> 6 #include "InsertionSort.h" 7 8 using namespace std; 9 10 11 // 將arr[l...mid]和arr[mid+1...r]兩部分進行歸併 12 // 其中aux爲完成merge過程所須要的輔助空間 13 template<typename T> 14 void __merge(T arr[], T aux[], int l, int mid, int r){ 15 16 // 因爲aux的大小和arr同樣, 因此咱們也不須要處理aux索引的偏移量 17 // 進一步節省了計算量:) 18 for( int i = l ; i <= r; i ++ ) 19 aux[i] = arr[i]; 20 21 // 初始化,i指向左半部分的起始索引位置l;j指向右半部分起始索引位置mid+1 22 int i = l, j = mid+1; 23 for( int k = l ; k <= r; k ++ ){ 24 25 if( i > mid ){ // 若是左半部分元素已經所有處理完畢 26 arr[k] = aux[j]; j ++; 27 } 28 else if( j > r ){ // 若是右半部分元素已經所有處理完畢 29 arr[k] = aux[i]; i ++; 30 } 31 else if( aux[i] < aux[j] ) { // 左半部分所指元素 < 右半部分所指元素 32 arr[k] = aux[i]; i ++; 33 } 34 else{ // 左半部分所指元素 >= 右半部分所指元素 35 arr[k] = aux[j]; j ++; 36 } 37 } 38 39 } 40 41 // 使用優化的歸併排序算法, 對arr[l...r]的範圍進行排序 42 // 其中aux爲完成merge過程所須要的輔助空間 43 template<typename T> 44 void __mergeSort(T arr[], T aux[], int l, int r){ 45 46 // 對於小規模數組, 使用插入排序 47 if( r - l <= 15 ){ 48 insertionSort(arr, l, r); 49 return; 50 } 51 52 int mid = (l+r)/2; 53 __mergeSort(arr, aux, l, mid); 54 __mergeSort(arr, aux, mid+1, r); 55 56 // 對於arr[mid] <= arr[mid+1]的狀況,不進行merge 57 // 對於近乎有序的數組很是有效,可是對於通常狀況,有必定的性能損失 58 if( arr[mid] > arr[mid+1] ) 59 __merge(arr, aux, l, mid, r); 60 } 61 62 63 template<typename T> 64 void mergeSort(T arr[], int n){ 65 66 // 在 mergeSort中, 咱們一次性申請aux空間, 67 // 並將這個輔助空間以參數形式傳遞給完成歸併排序的各個子函數 68 T *aux = new T[n]; 69 70 __mergeSort( arr , aux, 0 , n-1 ); 71 72 delete[] aux; // 使用C++, new出來的空間不要忘記釋放掉:) 73 } 74 75 #endif
單路快排:
1 #ifndef INC_06_HEAP_SORT_QUICKSORT_H 2 #define INC_06_HEAP_SORT_QUICKSORT_H 3 4 #include <iostream> 5 #include <ctime> 6 #include <algorithm> 7 #include "InsertionSort.h" 8 using namespace std; 9 // 對arr[l...r]部分進行partition操做 10 // 返回p, 使得arr[l...p-1] < arr[p] ; arr[p+1...r] > arr[p] 11 template <typename T> 12 int _partition(T arr[], int l, int r){ 13 14 // 隨機在arr[l...r]的範圍中, 選擇一個數值做爲標定點pivot 15 swap( arr[l] , arr[rand()%(r-l+1)+l] ); 16 17 T v = arr[l]; 18 int j = l; 19 for( int i = l + 1 ; i <= r ; i ++ ) 20 if( arr[i] < v ){ 21 j ++; 22 swap( arr[j] , arr[i] ); 23 } 24 25 swap( arr[l] , arr[j]); 26 27 return j; 28 } 29 30 // 對arr[l...r]部分進行快速排序 31 template <typename T> 32 void _quickSort(T arr[], int l, int r){ 33 34 // 對於小規模數組, 使用插入排序進行優化 35 if( r - l <= 15 ){ 36 insertionSort(arr,l,r); 37 return; 38 } 39 40 int p = _partition(arr, l, r); 41 _quickSort(arr, l, p-1 ); 42 _quickSort(arr, p+1, r); 43 } 44 45 template <typename T> 46 void quickSort(T arr[], int n){ 47 48 srand(time(NULL)); 49 _quickSort(arr, 0, n-1); 50 } 51 52 #endif
雙路快排:
1 #ifndef INC_06_HEAP_SORT_QUICKSORT2WAYS_H 2 #define INC_06_HEAP_SORT_QUICKSORT2WAYS_H 3 4 #include <iostream> 5 #include <algorithm> 6 #include "InsertionSort.h" 7 8 using namespace std; 9 10 // 雙路快速排序的partition 11 // 返回p, 使得arr[l...p-1] < arr[p] ; arr[p+1...r] > arr[p] 12 template <typename T> 13 int _partition2(T arr[], int l, int r){ 14 15 // 隨機在arr[l...r]的範圍中, 選擇一個數值做爲標定點pivot 16 swap( arr[l] , arr[rand()%(r-l+1)+l] ); 17 T v = arr[l]; 18 19 // arr[l+1...i) <= v; arr(j...r] >= v 20 int i = l+1, j = r; 21 while( true ){ 22 // 注意這裏的邊界, arr[i] < v, 不能是arr[i] <= v 23 24 while( i <= r && arr[i] < v ) 25 i ++; 26 27 // 注意這裏的邊界, arr[j] > v, 不能是arr[j] >= v 28 29 while( j >= l+1 && arr[j] > v ) 30 j --; 31 32 34 35 if( i > j ) 36 break; 37 38 swap( arr[i] , arr[j] ); 39 i ++; 40 j --; 41 } 42 43 swap( arr[l] , arr[j]); 44 45 return j; 46 } 47 48 // 對arr[l...r]部分進行快速排序 49 template <typename T> 50 void _quickSort2Ways(T arr[], int l, int r){ 51 52 // 對於小規模數組, 使用插入排序進行優化 53 if( r - l <= 15 ){ 54 insertionSort(arr,l,r); 55 return; 56 } 57 58 // 調用雙路快速排序的partition 59 int p = _partition2(arr, l, r); 60 _quickSort2Ways(arr, l, p-1 ); 61 _quickSort2Ways(arr, p+1, r); 62 } 63 64 template <typename T> 65 void quickSort2Ways(T arr[], int n){ 66 67 srand(time(NULL)); 68 _quickSort2Ways(arr, 0, n-1); 69 } 70 71 #endif
三路快排:
1 #ifndef INC_06_HEAP_SORT_QUICKSORT3WAYS_H 2 #define INC_06_HEAP_SORT_QUICKSORT3WAYS_H 3 4 #include <iostream> 5 #include <algorithm> 6 #include "InsertionSort.h" 7 8 using namespace std; 9 10 // 遞歸的三路快速排序算法 11 template <typename T> 12 void __quickSort3Ways(T arr[], int l, int r){ 13 14 // 對於小規模數組, 使用插入排序進行優化 15 if( r - l <= 15 ){ 16 insertionSort(arr,l,r); 17 return; 18 } 19 20 // 隨機在arr[l...r]的範圍中, 選擇一個數值做爲標定點pivot 21 swap( arr[l], arr[rand()%(r-l+1)+l ] ); 22 23 T v = arr[l]; 24 25 int lt = l; // arr[l+1...lt] < v 26 int gt = r + 1; // arr[gt...r] > v 27 int i = l+1; // arr[lt+1...i) == v 28 while( i < gt ){ 29 if( arr[i] < v ){ 30 swap( arr[i], arr[lt+1]); 31 i ++; 32 lt ++; 33 } 34 else if( arr[i] > v ){ 35 swap( arr[i], arr[gt-1]); 36 gt --; 37 } 38 else{ // arr[i] == v 39 i ++; 40 } 41 } 42 43 swap( arr[l] , arr[lt] ); 44 45 __quickSort3Ways(arr, l, lt-1); 46 __quickSort3Ways(arr, gt, r); 47 } 48 49 template <typename T> 50 void quickSort3Ways(T arr[], int n){ 51 52 srand(time(NULL)); 53 __quickSort3Ways( arr, 0, n-1); 54 } 55 56 #endif
測試用例:
1 #ifndef INC_06_HEAP_SORT_SORTTESTHELPER_H 2 #define INC_06_HEAP_SORT_SORTTESTHELPER_H 3 #include <iostream> 4 #include <algorithm> 5 #include <string> 6 #include <ctime> 7 #include <cassert> 8 #include <string> 9 using namespace std; 10 namespace SortTestHelper { 11 // 生成有n個元素的隨機數組,每一個元素的隨機範圍爲[rangeL, rangeR] 12 int *generateRandomArray(int n, int range_l, int range_r) { 13 int *arr = new int[n]; 14 srand(time(NULL)); 15 for (int i = 0; i < n; i++) 16 arr[i] = rand() % (range_r - range_l + 1) + range_l; 17 return arr; 18 } 19 // 生成一個近乎有序的數組 20 // 首先生成一個含有[0...n-1]的徹底有序數組, 以後隨機交換swapTimes對數據 21 // swapTimes定義了數組的無序程度 22 int *generateNearlyOrderedArray(int n, int swapTimes){ 23 int *arr = new int[n]; 24 for(int i = 0 ; i < n ; i ++ ) 25 arr[i] = i; 26 27 srand(time(NULL)); 28 for( int i = 0 ; i < swapTimes ; i ++ ){ 29 int posx = rand()%n; 30 int posy = rand()%n; 31 swap( arr[posx] , arr[posy] ); 32 } 33 34 return arr; 35 } 36 37 // 拷貝整型數組a中的全部元素到一個新的數組, 並返回新的數組 38 int *copyIntArray(int a[], int n){ 39 40 int *arr = new int[n]; 41 //* 在VS中, copy函數被認爲是不安全的, 請你們手動寫一遍for循環:) 42 copy(a, a+n, arr); 43 return arr; 44 } 45 46 // 打印arr數組的全部內容 47 template<typename T> 48 void printArray(T arr[], int n) { 49 50 for (int i = 0; i < n; i++) 51 cout << arr[i] << " "; 52 cout << endl; 53 54 return; 55 } 56 57 // 判斷arr數組是否有序 58 template<typename T> 59 bool isSorted(T arr[], int n) { 60 61 for (int i = 0; i < n - 1; i++) 62 if (arr[i] > arr[i + 1]) 63 return false; 64 65 return true; 66 } 67 68 // 測試sort排序算法排序arr數組所獲得結果的正確性和算法運行時間 69 // 將算法的運行時間打印在控制檯上 70 template<typename T> 71 void testSort(const string &sortName, void (*sort)(T[], int), T arr[], int n) { 72 73 clock_t startTime = clock(); 74 sort(arr, n); 75 clock_t endTime = clock(); 76 cout << sortName << " : " << double(endTime - startTime) / CLOCKS_PER_SEC << " s"<<endl; 77 78 assert(isSorted(arr, n)); 79 80 return; 81 } 82 83 // 測試sort排序算法排序arr數組所獲得結果的正確性和算法運行時間 84 // 將算法的運行時間以double類型返回, 單位爲秒(s) 85 template<typename T> 86 double testSort(void (*sort)(T[], int), T arr[], int n) { 87 88 clock_t startTime = clock(); 89 sort(arr, n); 90 clock_t endTime = clock(); 91 92 assert(isSorted(arr, n)); 93 94 return double(endTime - startTime) / CLOCKS_PER_SEC; 95 } 96 97 }; 98 99 #endif
測試結果:
平均時間複雜度 | 是不是原地排序 | 須要額外空間 | 穩定排序 | |
插入排序 | O(n^2) | 是 | O(1) | 是 |
歸併排序 | O(nlogn) | 否 | O(n) | 是 |
快速排序 | O(nlogn) | 是 | O(logn) | 否 |
堆排序 | O(nlogn) | 是 | O(1) | 否 |
穩定性解釋:排序後的元素相同元素的順序依然是排序以前的順序。
堆排序的最壞時間複雜度爲O(N*logN),其平均性能較接近於最壞性能。因爲初始建堆所需比較的次數較多,因此堆排序不適合記錄數較少的文件,其空間複雜度是O(1),它是一種不穩定的排序算法.