視頻學習網址:點擊打開鏈接
參考書籍《算法導論》
Pre比較:
理論時間:
堆排序 歸併排序 快速排序
最壞時間 O(nlogn) O(nlogn) O(n^2)
最好時間 O(nlogn) O(nlogn) O(nlogn)
平均時間 O(nlogn) O(nlogn) O(nlogn)
空間複雜度 O(1) O(n) ???這裏不對啊,我覺得是O(1)啊(ps:這裏的空間複雜度還包括了遞歸的過程,由於快排是遞歸實現的,所以有log(n)到n的空間複雜度,堆排序如果用遞歸實現也是logn的空間複雜度,不過堆排序可以改成非遞歸實現)
實際測量:
4種非平方級的排序:
希爾排序,堆排序,歸併排序,快速排序
我測試的平均排序時間:數據是隨機整數,時間單位是秒
數據規模 快速排序 歸併排序 希爾排序 堆排序
1000萬 0.75 1.22 1.77 3.57
5000萬 3.78 6.29 9.48 26.54
1億 7.65 13.06 18.79 61.31
堆排序是最差的。
這是算法硬傷,沒辦法的。因爲每次取一個最大值和堆底部的數據(記爲X)交換,重新篩選堆,把堆頂的X調整到位,有很大可能是依舊調整到堆的底部(堆的底部X顯然是比較小的數,纔會在底部),然後再次和堆頂最大值交換,再調整下來。
從上面看出,堆排序做了許多無用功。
排序算法穩定性定義:假定在待排序的記錄序列中,存在多個具有相同的關鍵字的記錄,若經過排序,這些記錄的相對次序保持不變,即在原序列中,ri=rj,且ri在rj之前,而在排序後的序列中,ri仍在rj之前,則稱這種排序算法是穩定的;否則稱爲不穩定的。
堆排序、快速排序、希爾排序、直接選擇排序 不是穩定的排序算法,(快排和直接選擇排序都是在發生交換的時候破壞的,希爾排序由於步長大於1時開頭就會跳過一些元素,堆排序在維護堆的時候會破壞穩定性)
而基數排序、冒泡排序、直接插入排序、折半插入排序、歸併排序、計數排序 是穩定的排序算法。
參考:百度百科
參考視頻:點擊打開鏈接
1 冒泡排序:兩兩比較,大的放後;關注for循環中的兩個變量的變化範圍;時間複雜度O(n^2)
#include <iostream>
class BubbleSort
{
public:
BubbleSort(){};//默認構造函數
BubbleSort(BubbleSort &other){};//拷貝構造函數
//冒泡排序函數
int *bubbleSort(int *pa, int n);
private:
};
//冒泡排序函數
int *BubbleSort::bubbleSort(int *pa, int n)
{
int temp = 0;
//for循環,兩兩比較排序
for (int j = 0; j < n - 1; j++)
{
for (int k = 0; k < n - 1 - j; k++)
{
if (pa[k]>pa[k + 1])
{
temp = pa[k];
pa[k] = pa[k + 1];
pa[k + 1] = temp;
}
}
}
//返回
return pa;
}
2 插入排序:逐個選取數組中元素,與前面元素比較插入;時間複雜度O(n^2)
class InsertionSort {
public:
int* insertionSort(int* A, int n)
{
// write code here
int key = 0;
//遍歷數組中每一個元素
for (int i = 1; i < n; i++)
{
key = A[i];
int j = i - 1;
//與前面的元素逐個比較,大於則插入
while (j>=0 && A[j]>key)
{
A[j + 1] = A[j];
j--;
}
A[j + 1] = key;
}
return A;
};
private:
};
3 選擇排序:選擇最大值,放到數組的後面;時間複雜度O(n^2)
class SelectionSort {
public:
int* selectionSort(int* A, int n) {
// write code here
int maxKey = 0;
int maxInd = 0;
int temp = 0;
//每次選擇最大的,放在數組的後面
for (int i = n - 1; i >= 0; i--)
{
maxKey = A[0];
maxInd = 0;
//找出最大的
for (int j = 0; j <= i; j++)
{
if (A[j] > maxKey)
{
maxKey = A[j];
maxInd = j;
}
}
//交換 最大值 和 查找範圍末尾位置的值
temp = A[i];
A[i] = A[maxInd];
A[maxInd] = temp;
}
return A;
}
};
4 歸併排序:遞歸分解爲子問題,將子問題合併。時間複雜度O(nlogn),空間上需要動態分配內存輔助,額外的空間需求
class MergeSort
{
public:
int *mergeSort(int *A,int n)
{
mergeSortAC(A, 0, n - 1);
return A;
}
private:
//將pa指向的數組,劃分成兩個小數組,排序後再合併
int mergeSortAC(int *pa, int a, int c);
//合併函數
int merge(int *pa, int a, int b, int c);
};
//將pa指向的數組,劃分成兩個小數組,排序後再合併
int MergeSort::mergeSortAC(int *pa, int a, int c)
{
//邊界條件:只有一個元素,返回
if (a >= c) return 0;
int b = (a + c) >> 1;
//對[a,b],[b+1,c]的兩個數組分別排序,遞歸調用
mergeSortAC(pa, a, b);
mergeSortAC(pa, b + 1, c);
//合併兩個已經排好序的數組
merge(pa, a, b, c);
return 0;
}
//合併函數
int MergeSort::merge(int *pa, int a, int b, int c)
{
//分配兩個動態數組
int *pb = new int[b - a + 2];
assert(pb != NULL);
int *pc = new int[c - b + 1];
assert(pc != NULL);
//將要排序的部分複製到動態數組中
int i = a;
int j = b + 1;
while (i <= b)
{
pb[i - a] = pa[i];
i++;
}
while (j <= c)
{
pc[j - b - 1] = pa[j];
j++;
}
//爲動態數組添加末尾無窮大值
pb[b - a + 1] = INT_MAX;
pc[c - b] = INT_MAX;
//逐個比較複製到pa中
i = j = 0;
for (int k = a; k <= c; k++)
{
if (pb[i] < pc[j])
{
pa[k] = pb[i];
i++;
}
else
{
pa[k] = pc[j];
j++;
}
}
//釋放動態內存
delete[] pb;
delete[] pc;
//返回
return 0;
}
5 快速排序:隨機抽取一個數,小的放左邊,大的放右邊。利用小於等於區間,可以原址排序。使用隨機函數,引入隨機選擇。理論最差情況,時間複雜度O(n^2),期望複雜度是O(nlogn),實際使用中,快排的速度是最快的,而且由於是原址排序,所以空間需求少,不像歸併排序需要O(n)的輔助空間。
#include <stdlib.h>
#include <time.h>
class QuickSort
{
public:
int *quickSort(int *A, int n)
{
quickSortBC(A,0,n-1);
return A;
}
int *quickSortBC(int *A, int b, int c)
{
//邊界條件
if (b >= c) return 0;
int i = 0;
i = partition(A, b, c);//劃分
quickSortBC(A, b, i-1);//對劃分的子集,快速排序
quickSortBC(A, i+1, c);
return 0;
}
//劃分函數
int partition(int *A, int b, int c)
{
//邊界條件
if (b >= c) return 0;
//爲快排加入隨機選擇
srand((unsigned)time(0));
int ind = b + rand() % (c - b + 1);//ind取值範圍[b,c]
int key = A[ind];
//將ind對應的元素,換到數組末尾
A[ind] = A[c];
A[c] = key;
int temp = key;
int i = b - 1;
int j = b - 1;
while (j < c)
{
j++;
if (A[j] <= key)
{
i++;
temp = A[i];
A[i] = A[j];
A[j] = temp;
}
}
//返回key值的索引
return i;
}
private:
};
6 堆排序:利用最大堆,每次取堆根,然後維護堆;時間複雜度上界是O(nlogn),堆排在實際中並不快,做了很多無用的比較;
class HeapSort
{
public:
int *heapSort(int *A, int n);
private:
int buildHeap(int *A, int size);//建堆
int maxHeap(int *A, int ind, int size);//維護最大堆
inline int exchange(int *A, int a, int b);//交換兩個元素
};
int *HeapSort::heapSort(int *A, int n)
{
//將最小元素換到A[0],因爲A[0]是不參與建堆的
int minEle = A[0];
int ind = 0;
for (int i = 0; i < n; i++)
{
if (minEle>A[i])
{
minEle = A[i];
ind = i;
}
}
A[ind] = A[0];
A[0] = minEle;
//建堆
buildHeap(A, n - 1);
//循環取堆根放到數組A末尾,維護堆
for (int size = n - 1; size > 1;)
{
exchange(A, 1, size);
size--;
maxHeap(A, 1, size);
}
return A;
}
int HeapSort::buildHeap(int *A, int size)//建堆
{
//from size/2 to 1
for (int i = size >> 1; i > 0; i--)
{
maxHeap(A, i, size);
}
return 0;
}
int HeapSort::maxHeap(int *A, int ind, int size)//維護最大堆
{
int largest = ind;
int left = (ind << 1);
int right = (ind << 1) + 1;
if (left <= size && A[left] > A[ind])//與左孩子比較
{
largest = left;
}
if (right <= size && A[right] > A[largest])//與右孩子比較
{
largest = right;
}
if (largest != ind)
{
exchange(A, largest, ind);
maxHeap(A, largest, size);
}
return 0;
}
inline int HeapSort::exchange(int *A, int a, int b)
{
int temp = A[a];
A[a] = A[b];
A[b] = temp;
return 0;
}
7 希爾排序