排序是最基本的算法,裡面包含了最基礎的思想。一個簡單的優化可以讓排序快很多。
O ( n 2 ) O(n^2) O(n2)的排序算法
冒泡排序
//冒泡排序
template <typename T>
void bubbleSort(T *arr, int size)
{
for (int i = 0; i < size; ++i)
{
for (int j = 0; j < size - i - 1; ++j)
{
if (arr[j] > arr[j + 1])
swap(arr[j], arr[j + 1]);
}
}
}
插入排序
template<typename T>
void insertSort(T *arr,int size)
{
for(int i = 0;i < size;++i)
{
int j;
for(j = i;j > 0 && arr[j] < arr[j-1];--j){swap(arr[j],arr[j-1]);}
}
}
選擇排序
//選擇排序 複雜度O(n^2)
template<typename T>
void selectionSort(T *arr,int size)
{
int k;
for(int i = 0;i < size-1; ++i)
{
k = i;
for(int j = i+1;j < size;++j)
if(arr[j] < arr[k])
k = j;
if(k != i) mySwap(arr[k],arr[i]);
}
}
測試排序使用時間的時候,總是選擇排序快于插入排序,按理說,插入排序應該比選擇排序要快啊,因為插入排序可以提前終止循環,這是為什麼呢?
[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-JMwWFKuE-1587546526297)(https://i.loli.net/2020/03/10/Ymcw1fX3ge8pPhq.png)]
原因是選擇排序比較的是下标,而插入排序每一次比較都要交換,而交換所耗費的時間是高于簡單的比較的。
插入排序優化-将交換變成指派
template<typename T>
void insertSort(T *arr,int size)
{
for(int i = 0;i < size;++i)
{
T e = arr[i];
int j;
for(j = i;j > 0 && arr[j-1] > e;--j){arr[j] = arr[j-1];}
arr[j] = e;
}
}
運作時間明顯變快了
[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-SPbt3sFy-1587546526302)(https://i.loli.net/2020/03/10/VhB69LnXso8tHIJ.png)]
對于近乎有序的資料來說,插入排序的速度要快很多,近乎 O ( n ) O (n) O(n)。而插入排序的實際應用有很多,比如日志,日志的時間是近乎有序的,但是生成日志可能會出錯,需要進行時間排序處理,這個時候使用插入排序會更好;還有銀行的一些流水單等等
拓展:
C++運算符重載
一般在類中實作,有兩種可以實作的方法
運算符重載例子,使用在一個類中class Student { public: string name; int score; bool operator<(const Student &otherStudent) { return this->score < otherStudent.score; } friend ostream &operator<<(ostream &os, const Student &student) { os << "Student:" << student.name << " "<<student.score<<endl; return os; } };
- 使用友元函數
傳回值類型 operator 運算符(形參表) { ... } //例Complex是一個複數類 friend Complex operator+(const Complex &c1,const Complex &c2){ return Complex(c1.i + c2.i,c1.j + c2.j); }
- 使用類裡面的函數
傳回值類型 operator 運算符(形參表) { ... } //例Complex是一個複數類 Complex operator+(const Complex &complex){ return Complex(this->i + complex.i,this->j + complex.j); }
它們的差別就是參數的個數不同以及需不需要加上
fridend
這個關鍵字
O ( n log ( n ) ) O(n\log (n)) O(nlog(n))的排序算法
歸并排序
代碼實作
template <typename T>
void __merge(T *arr, int l, int middle, int r)
{
T aux[r - l + 1];
for (int i = l; i <= r; ++i)
aux[i - l] = arr[i];
int i = l, j = middle + 1;
for (int k = l; k <= r; ++k)
{
if (i > middle)
{
arr[k] = aux[j - l];
j++;
}
else if (j > r)
{
arr[k] = aux[i - l];
i++;
}
else if (aux[i - l] < aux[j - l])
{
arr[k] = aux[i - l];
i++;
}
else
{
arr[k] = aux[j - l];
j++;
}
}
}
template <typename T>
void __mergeSort(T *arr, int l, int r)
{
if (l >= r)
return;
int middle = (l + r) / 2;
__mergeSort(arr, l, middle);
__mergeSort(arr, middle+1, r);
if(arr[middle] > arr[middle+1])
__merge(arr, l, middle, r);
}
//歸并排序
template <typename T>
void mergeSort(T *arr, int size)
{
__mergeSort(arr, 0, size - 1);
}
下面這段代碼的标記部分需要考慮溢出的問題
[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-1D7OF2Vo-1587546526307)(https://i.loli.net/2020/03/11/9GStd4s1fe7kINL.png)]
歸并排序快是快,但是要耗費多一倍 O ( n ) O(n) O(n)的存儲空間,也就是使用空間換時間。
希爾排序
動畫示範(來自@五分鐘算法):
代碼實作:
//希爾排序
template <typename T>
void shellSort(T *arr, int size)
{
int dk[] = {5, 3, 1};
for (int index = 0; index < 3; ++index)
{
for (int i = 0; i < size / dk[index]; ++i)
{
int j;
int e = arr[i];
for (j = i + dk[index]; j > dk[index] && arr[j] > e; j -= dk[index])
{
arr[j] = arr[j - dk[index]];
}
arr[j] = e;
}
}
}
希爾排序相當于是插入排序的更新版,增加了一個步長參數,使用希爾排序可以讓零散的資料實作跳躍行的交換,最後逐漸将數組轉化為有序,這樣最後使用步長為1的插入排序就非常快了。
快速排序
被稱為二十世紀影響最大的算法之一!
動畫示範(來自@五分鐘算法):
代碼實作:
template<typename T>
int __partition(T *arr,int l,int r){
T v = arr[l];
int j = l;
for(int i = l+1;i <= r;++i){
if(arr[i] < v){
swap(arr[i],arr[++j]);
}
}
swap(arr[l],arr[j]);
return j;
}
template<typename T>
void __quickSort(T *arr,int l,int r)
{
if(l >= r) return;
int p = __partition(arr,l,r);
__quickSort(arr,l,p-1);
__quickSort(arr,p+1,r);
}
//快速排序
template<typename T>
void quickSort(T *arr,int size)
{
__quickSort(arr,0,size-1);
}
優化一:
在數組的元素個數小于15個的時候使用插入排序進行優化:
template<typename T>
void __quickSort(T *arr,int l,int r)
{
+ if(r - l <= 15) insertionSort(arr,l,r);
int p = __partition(arr,l,r);
__quickSort(arr,l,p-1);
__quickSort(arr,p+1,r);
}
優化二:
使用随機值作為劃分标準
template<typename T>
int __partition(T *arr,int l,int r){
+ swap(arr[l],arr[rand() % (r-l+1) + l]);
T v = arr[l];
int j = l;
for(int i = l+1;i <= r;++i){
if(arr[i] < v){
swap(arr[i],arr[++j]);
}
}
swap(arr[l],arr[j]);
return j;
}
template<typename T>
void quickSort(T *arr,int size)
{
+ srand(time(NULL));
__quickSort(arr,0,size-1);
}
缺點:
- 在近乎有序的數組排序中,快速排序的性能很差。時間複雜度也近乎 O ( n 2 ) O(n^2 ) O(n2)
- 對于有很多重複元素的數組,快速排序的性能也很差
快速排序版本二:兩路快排
使用兩個下标分别處理大于v與小于v的部分。(v為基準元素)
代碼實作:
template<typename T>
int __partition2(T *arr,int l,int r){
swap(arr[l],arr[rand() % (r-l+1) + l]);
T v = arr[l];
int i = l + 1,j = r;
while(true)
{
while(arr[i] < v && i <= r) ++i;
while(arr[j] > v && j >= l+1) --j;
if(i > j) break;
swap(arr[i++],arr[j--]);
}
swap(arr[l],arr[j]);
return j;
}
template<typename T>
void __quickSort2(T *arr,int l,int r)
{
if(r - l<= 15){
insertSort(arr,l,r);
return;
}
int p = __partition2(arr,l,r);
__quickSort2(arr,l,p-1);
__quickSort2(arr,p+1,r);
}
//快速排序版本二,雙路快排
template<typename T>
void quickSort2(T *arr,int size)
{
srand(time(NULL));
__quickSort2(arr,0,size-1);
}
快速排序版本三:三路快排
使用三個下标分别處理大于v、等于v與小于v的部分。(v為基準元素)
代碼實作:
template<typename T>
void __quickSort3(T *arr,int l,int r)
{
if(r - l<= 15){
insertSort(arr,l,r);
return;
}
swap(arr[l],arr[rand() % (r-l+1) + l]);
T v = arr[l];
int lt = l; //arr[l+1...lt] < v
int gt = r + 1; //arr[gt...r] > v
int i = l+1; //arr[lt+1...i] == v
while(i < gt){
if(arr[i] < v){
swap(arr[i++],arr[++lt]);
}else if(arr[i] > v){
swap(arr[i],arr[--gt]);
}else{
i++;
}
}
swap(arr[l],arr[lt]);
__quickSort3(arr,l,lt-1);
__quickSort3(arr,gt,r);
}
//快速排序版本三,三路快排
template<typename T>
void quickSort3(T *arr,int size)
{
srand(time(NULL));
__quickSort3(arr,0,size-1);
}
堆排序
基數排序
桶排序
排序算法總結
圖檔:
未完待續…🛴
參考:
- https://www.cnblogs.com/onepixel/p/7674659.html
- https://github.com/MisterBooo/Article