堆排序优化与几个排序算法时间复杂度

我们通常所说的堆是指二叉堆,二叉堆又称完全二叉树或者叫近似完全二叉树。二叉堆又分为最大堆和最小堆。

堆排序(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 

简单堆排序:

#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 

插入排序:

#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

归并排序:

#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 

单路快排:

#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 

双路快排:

#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

三路快排:

#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

测试用例:

#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 

测试结果:

  平均时间复杂度 是否是原地排序 需要额外空间 稳定排序
插入排序 O(n^2) O(1)
归并排序 O(nlogn) O(n)
快速排序 O(nlogn) O(logn)
堆排序 O(nlogn) O(1)

稳定性解释:排序后的元素相同元素的顺序依然是排序之前的顺序。

 堆排序的最坏时间复杂度为O(N*logN),其平均性能较接近于最坏性能。由于初始建堆所需比较的次数较多,所以堆排序不适合记录数较少的文件,其空间复杂度是O(1),它是一种不稳定的排序算法.

相关文章

背景:计算机内部用补码表示二进制数。符号位1表示负数,0表...
大家好,我们现在来讲解关于加密方面的知识,说到加密我认为不...
相信大家在大学的《算法与数据结构》里面都学过快速排序(Qui...
加密在编程中的应用的是非常广泛的,尤其是在各种网络协议之...
前言我的目标是写一个非常详细的关于diff的干货,所以本文有...
对称加密算法 所有的对称加密都有一个共同的特点:加密和...