我们通常所说的堆是指二叉堆,二叉堆又称完全二叉树或者叫近似完全二叉树。二叉堆又分为最大堆和最小堆。
堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法,它是选择排序的一种。可以利用数组的特点快速定位指定索引的元素。数组可以根据索引直接获取元素,时间复杂度为O(1),也就是常量,因此对于取值效率极高。
这里以最大堆为例:
最大堆的特性如下:
父结点的键值总是大于或者等于任何一个子节点的键值
每个结点的左子树和右子树都是一个最大堆
最大堆的算法思想是:
先将初始的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 capacity; 12 13 void shiftUp( k){ 14 while( k > 1 && data[k/2] < data[k] ){ 15 swap( data[k/2],data[k] ); 16 k /= ; 17 } 18 } 19 20 void shiftDown(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] ) break25 swap( data[k],data[j] ); 26 k = j; 27 28 29 30 public31 32 // 构造函数,构造一个空堆,可容纳capacity个元素 33 MaxHeap( capacity){ 34 data = new Item[capacity+1]; 35 count = 036 this->capacity =37 38 39 该构造堆的过程,时间复杂度为O(n) 40 MaxHeap(Item arr[], n){ 41 data = new Item[n+42 capacity = n; 43 for( int i = 0 ; i < n ; i ++ ) 44 data[i+1] = arr[i]; 45 count =46 47 int i = count/2 ; i >= 1 ; i --48 shiftDown(i); 49 50 ~MaxHeap(){ 51 delete[] data; 52 53 54 返回堆中的元素个数 55 size(){ 56 return57 58 59 返回一个布尔值,表示堆中是否为空 60 bool isEmpty(){ 61 return count == 62 63 64 像最大堆中插入一个新的元素 item 65 void insert(Item item){ 66 assert( count + 1 <= capacity ); 67 data[count+ item; 68 shiftUp(count+); 69 count ++70 71 72 从最大堆中取出堆顶元素,即堆中所存储的最大数据 73 Item extractMax(){ 74 assert( count > ); 75 Item ret = data[76 swap( data[77 count --78 shiftDown(79 ret; 80 81 82 获取最大堆中的堆顶元素 83 Item getMax(){ 84 assert( count > 85 return data[86 87 }; 88 89 #endif @H_987_404@
简单堆排序:
#ifndef INC_06_HEAP_SORT_HEAPSORT_H #define INC_06_HEAP_SORT_HEAPSORT_H 3 #include "Heap.h" 4 heapSort1,将所有的元素依次添加到堆中,在将所有元素从堆中依次取出来,即完成了排序 6 无论是创建堆的过程,还是从堆中依次取出元素的过程,时间复杂度均为O(nlogn) 整个堆排序的整体时间复杂度为O(nlogn) 8 template<typename T> 9 void heapSort1(T arr[],1)">10 11 MaxHeap<T> maxheap = MaxHeap<T>(n); 12 13 maxheap.insert(arr[i]); 14 15 int i = n-1 ; i >= 0 ; i--16 arr[i] = maxheap.extractMax(); } heapSort2,借助我们的heapify过程创建堆 19 此时,创建堆的过程时间复杂度为O(n),将所有元素依次从堆中取出来,实践复杂度为O(nlogn) 20 堆排序的总体时间复杂度依然是O(nlogn),但是比上述heapSort1性能更优,因为创建堆的性能更优 21 template<typename T> 22 void heapSort2(T arr[],1)">23 24 MaxHeap<T> maxheap = MaxHeap<T>(arr,n); 25 26 arr[i] =#endif @H_987_404@
插入排序:
#ifndef INC_06_HEAP_SORT_INSERTIONSORT_H #define INC_06_HEAP_SORT_INSERTIONSORT_H 3 #include <iostream> 4 #include <algorithm> 6 template<typename T> void insertionSort(T arr[],1)"> 8 9 1 ; i < n ; i ++ ) { 11 T e =12 13 for (j = i; j > 0 && arr[j-1] > e; j--) 14 arr[j] = arr[j-15 arr[j] = e; 16 17 18 20 21 对arr[l...r]范围的数组进行插入排序 22 template<typename T> 23 int l,1)"> r){ 24 int i = l+1 ; i <= r ; i ++26 27 T e =28 29 for (j = i; j > l && arr[j-30 arr[j] = arr[j-31 arr[j] =32 33 34 35 36 #endif@H_987_404@
归并排序:
#ifndef INC_06_HEAP_SORT_MERGESORT_H #define INC_06_HEAP_SORT_MERGESORT_H 3 4 #include <iostream> 5 #include <algorithm> 6 #include InsertionSort.h 7 9 11 将arr[l...mid]和arr[mid+1...r]两部分进行归并 12 其中aux为完成merge过程所需要的辅助空间 13 template<typename T> 14 void __merge(T arr[],T aux[],1)">int mid,1)">15 16 由于aux的大小和arr一样,所以我们也不需要处理aux索引的偏移量 17 进一步节省了计算量:) int i = l ; i <= r; i ++19 aux[i] =21 初始化,i指向左半部分的起始索引位置l;j指向右半部分起始索引位置mid+1 22 int i = l,j = mid+23 int k = l ; k <= r; k ++ ){ 25 if( i > mid ){ 如果左半部分元素已经全部处理完毕 26 arr[k] = aux[j]; j ++else if( j > r ){ 如果右半部分元素已经全部处理完毕 29 arr[k] = aux[i]; i ++31 if( aux[i] < aux[j] ) { 左半部分所指元素 < 右半部分所指元素 32 arr[k] = aux[i]; i ++33 34 else{ 左半部分所指元素 >= 右半部分所指元素 35 arr[k] = aux[j]; j ++36 38 39 40 41 使用优化的归并排序算法,对arr[l...r]的范围进行排序 42 43 template<typename T> 44 void __mergeSort(T arr[],1)">45 46 对于小规模数组,使用插入排序 47 if( r - l <= 15 insertionSort(arr,l,r); 49 50 51 52 int mid = (l+r)/53 __mergeSort(arr,aux,mid); 54 __mergeSort(arr,mid+,1)">55 56 对于arr[mid] <= arr[mid+1]的情况,不进行merge 57 对于近乎有序的数组非常有效,但是对于一般情况,有一定的性能损失 58 if( arr[mid] > arr[mid+] ) 59 __merge(arr,mid,1)">60 61 62 63 template<typename T> 64 void mergeSort(T arr[],1)">65 66 在 mergeSort中,我们一次性申请aux空间,67 并将这个辅助空间以参数形式传递给完成归并排序的各个子函数 68 T *aux = new T[n]; 69 70 __mergeSort( arr,0,n-delete[] aux; 使用C++,new出来的空间不要忘记释放掉:) 74 75 #endif @H_987_404@
单路快排:
#ifndef INC_06_HEAP_SORT_QUICKSORT_H #define INC_06_HEAP_SORT_QUICKSORT_H 5 #include <ctime> 6 #include <algorithm> 7 #include 对arr[l...r]部分进行partition操作 10 返回p,使得arr[l...p-1] < arr[p] ; arr[p+1...r] > arr[p] 11 template <typename T> int _partition(T arr[],1)">13 14 随机在arr[l...r]的范围中,选择一个数值作为标定点pivot 15 swap( arr[l],arr[rand()%(r-l+1)+l] ); 16 17 T v = arr[l]; int j = l; 19 int i = l + 20 if( arr[i] < v ){ 21 j ++ swap( arr[j],arr[i] ); swap( arr[l],arr[j]); 27 对arr[l...r]部分进行快速排序 31 template <typename T> void _quickSort(T arr[],使用插入排序进行优化 35 37 38 39 40 int p = _partition(arr,1)">41 _quickSort(arr,p-42 _quickSort(arr,p+43 44 45 template <typename T> 46 void quickSort(T arr[],1)">47 srand(time(NULL)); 49 _quickSort(arr,1)">#endif @H_987_404@
双路快排:
#ifndef INC_06_HEAP_SORT_QUICKSORT2WAYS_H #define INC_06_HEAP_SORT_QUICKSORT2WAYS_H 双路快速排序的partition 12 template <typename T> int _partition2(T arr[],1)">16 swap( arr[l],1)">18 arr[l+1...i) <= v; arr(j...r] >= v 1,j = r; while( true22 注意这里的边界,arr[i] < v,不能是arr[i] <= v 23 24 while( i <= r && arr[i] < v ) 25 i ++27 28 while( j >= l+1 && arr[j] >30 j --32 34 35 if( i > j ) 36 37 swap( arr[i],arr[j] ); 39 i ++40 j --42 45 49 template <typename T> void _quickSort2Ways(T arr[],1)">53 54 55 56 57 调用双路快速排序的partition _partition2(arr,1)">60 _quickSort2Ways(arr,1)">61 _quickSort2Ways(arr,1)">64 template <typename T> 65 void quickSort2Ways(T arr[],1)">66 67 68 _quickSort2Ways(arr,1)">69 70 71 #endif@H_987_404@
三路快排:
#ifndef INC_06_HEAP_SORT_QUICKSORT3WAYS_H #define INC_06_HEAP_SORT_QUICKSORT3WAYS_H 递归的三路快速排序算法 void __quickSort3Ways(T arr[],1)">17 21 swap( arr[l],1)">l ] ); 22 23 T v =int lt = l; arr[l+1...lt] < v 26 int gt = r + 1; arr[gt...r] > v 1; arr[lt+1...i) == v 28 while( i < gt ){ 30 swap( arr[i],arr[lt+]); 31 i ++32 lt ++if( arr[i] >35 swap( arr[i],arr[gt-36 gt --38 else{ arr[i] == v 39 i ++40 45 __quickSort3Ways(arr,lt- __quickSort3Ways(arr,gt,1)">47 48 void quickSort3Ways(T arr[],1)">53 __quickSort3Ways( arr,1)">#endif@H_987_404@
测试用例:
#ifndef INC_06_HEAP_SORT_SORTTESTHELPER_H #define INC_06_HEAP_SORT_SORTTESTHELPER_H 5 #include <string> 6 #include <ctime> 7 #include <cassert> 8 #include < SortTestHelper { 生成有n个元素的随机数组,每个元素的随机范围为[rangeL,rangeR] int *generateRandomArray(int n,1)">int range_l,1)"> range_r) { int *arr = new [n]; srand(time(NULL)); 15 for (0; i < n; i++16 arr[i] = rand() % (range_r - range_l + 1) + range_l; arr; 生成一个近乎有序的数组 首先生成一个含有[0...n-1]的完全有序数组,之后随机交换swapTimes对数据 swapTimes定义了数组的无序程度 int *generateNearlyOrderedArray( swapTimes){ 23 for(25 arr[i] = i; 0 ; i < swapTimes ; i ++29 int posx = rand()%n; 30 int posy = rand()%31 swap( arr[posx],arr[posy] ); 37 拷贝整型数组a中的所有元素到一个新的数组,并返回新的数组 int *copyIntArray(int a[],1)">40 41 * 在VS中,copy函数被认为是不安全的,请大家手动写一遍for循环:) 42 copy(a,a+n,arr); 打印arr数组的所有内容 47 template<typename T> 48 void printArray(T arr[],1)"> n) { 49 50 51 cout << arr[i] << " "52 cout << endl; 54 55 56 判断arr数组是否有序 58 template<typename T> bool isSorted(T arr[],1)">60 0; i < n - 1; i++62 if (arr[i] > arr[i + ]) 63 return false64 65 66 67 68 测试sort排序算法排序arr数组所得到结果的正确性和算法运行时间 69 将算法的运行时间打印在控制台上 70 template<typename T> 71 void testSort(const string &sortName,1)">void (*sort)(T[],1)">int),T arr[],1)">72 73 clock_t startTime = clock(); 74 sort(arr,1)">75 clock_t endTime =76 cout << sortName << : " << double(endTime - startTime) / CLOCKS_PER_SEC << s"<<endl; 77 78 assert(isSorted(arr,n)); 79 80 81 82 83 84 将算法的运行时间以double类型返回,单位为秒(s) 85 template<typename T> 86 double testSort(87 88 clock_t startTime =90 clock_t endTime =91 92 93 94 double(endTime - startTime) / CLOCKS_PER_SEC; 95 96 97 98 99 #endif @H_987_404@
测试结果:
平均时间复杂度 | 是否是原地排序 | 需要额外空间 | 稳定排序 | |
插入排序 | O(n^2) | 是 | O(1) | 是 |
归并排序 | O(nlogn) | 否 | O(n) | 是 |
快速排序 | O(nlogn) | 是 | O(logn) | 否 |
堆排序 | O(nlogn) | 是 | O(1) | 否 |
稳定性解释:排序后的元素相同元素的顺序依然是排序之前的顺序。
堆排序的最坏时间复杂度为O(N*logN),其平均性能较接近于最坏性能。由于初始建堆所需比较的次数较多,所以堆排序不适合记录数较少的文件,其空间复杂度是O(1),它是一种不稳定的排序算法.