常用排序算法彙總
1、插入排序
簡介:作爲算法導論上的第一個排序算法,插入排序理解起來不難。其基本原理如圖所示
排序機理:從左向右掃描,每遇到一個數字temp就將其從右向左,與位於temp-1的數進行大小比較,如果滿足大小在兩數之間,就執行插入,所謂的插入,本質是通過改變元素在數組中的位置,每比較一個數字,若不滿足,則temp向左一位,temp-1的數字右移一位。
時間複雜度:O(n^2)
穩定性:穩是穩,就會慢了點
代碼:
void insertion_sort (int arr[],int length)
{//插入排序
int i,j;
for(i=1;i<length;i++)
{
int temp=arr[i];
for(j=i;j>0&&arr[j-1]>temp;j--)
{
arr[j]=arr[j-1];
}
arr[j]=temp;
}
}
2、希爾排序
簡介:希爾排序是在插入排序基礎上改進而來,其本質也是插入排序,改進後的算法時間複雜度降到了O(n log n)。
排序機理:根據步長由長到短分組,比如步長爲5,那就把間隔爲5的元素取出來,算爲一組進行插入排序,這組排完之後再以步長爲2進行插入排序,直到步長爲1爲止。本質上是在插入排序上加入了間隔機制。
時間複雜度:O(n log n)
穩定性:不穩定
代碼:
void shellsort(int arr[],int n)
{//希爾排序
for(int gap=n/2;gap>0;gap/=2)
{
for(int i=gap;i<n;i++)
{
int temp=arr[i];
int j;
for(j=i;j>=gap&&arr[j-gap]>temp;j-=gap)
arr[j]=arr[j-gap];
arr[j]=temp;
}
}
}
3、基數排序
簡介:基數排序是分別先對數字的個位進行排序,然後十位,百位,千位,萬位等等,最後一步一次性排好,其中數位不足的就要用0補全。排序過程的話,有點像這種感覺(如圖)
排序機理:
時間複雜度:O(n k)
穩定性:又穩又快
代碼:
int getMax(int arr[],int n)//尋找數組中最大元素
{
int mx=arr[0];
for(int i=1;i<n;i++)
if(arr[i]>mx)
mx=arr[i];
return mx;
}
void countsort(int arr[],int n,int exp)
{
int output[n];
int i,count[10]={0};
for(i=0;i<n;i++)
count[(arr[i]/exp)%10]++;//取出某一位,進行10個數字的分配
for(i=1;i<10;i++)
count[i]+=count[i-1];//count數組從1到9逐次累加前一項
for(i=n-1;i>0;i--)
{
output[count[(arr[i]/exp)%10]-1]=arr[i];//將arr中的元素按照取餘的梯度直方圖,依次放到合適的位置
count[(arr[i]/exp)%10]--;//該基數的剩餘空位置相應減少一個
}
for(i=0;i<n;i++)
arr[i]=output[i];
}
void radixsort(int arr[],int n)
{
int m=getMax(arr,n);
for(int exp=1;m/exp>0;exp*=10)
countsort(arr,n,exp);
}
4、冒泡排序
簡介:大學中學習 C++ 的第二個排序算法,很經典。
排序機理:冒泡排序顧名思義,如果把這個數組以左邊爲底,右邊爲頂,逆時針旋轉90度,就像一個水中的氣泡,氣泡有什麼特點?在水中基本就是向上勻速浮動的,而且氣泡的體積在不考慮壓強變化的情況下體積不變。在冒泡排序中,氣泡裏面有兩個數字,僅將在氣泡裏面的兩個數字進行大小比較,小的靠左,大的靠右。排完之後,氣泡像上冒一格,也就是向右右移一格。當若n(n爲總元素的個數)個泡泡冒完之後,也就排好序了。
時間複雜度:O(n^2)
穩定性:穩是穩,就是慢了點
代碼:
void bubblesort(int a[],int n)//void bubblesort(vector<int>& a)
{
bool swapp=true;
while(swapp)
{
swapp=false;
for(int i=0;i<n;i++)//for(size_t i=0;i<a.size()-1;i++)
{
if(a[i]>a[i+1])
{
a[i] += a[i+1];
a[i+1] = a[i]-a[i+1];
a[i] -= a[i+1];
swapp=true;
}
}
}
}
5、快速排序
簡介:簡稱快排,時間複雜度不固定,在最壞情況下(元素剛好是反向的)速度比較慢,達到 O(n^2),但如果在比較理想的情況下時間複雜度 O(nlogn)。快排本質也用到分治思想,快排算法每次選擇一個元素並且將整個數組以那個元素分爲兩部分,根據實現算法的不同,元素的選擇一般有如下幾種:
- 永遠選擇第一個元素
- 永遠選擇最後一個元素
- 隨機選擇元素
- 取中間值
代碼以永遠選擇第一個元素爲例
算法機理:
(PS:圖片來源圖解快速排序 - MOBIN - 博客園)
時間複雜度:O(n log n)
穩定性:不穩定,速度一般
代碼:
//快速排序
void exchange(int *p,int *q)
{
int temp=*p;
*p=*q;
*q=temp;
}
void quicksort(int arr[],int left,int right)
{
if(left>=right) return ;
int i=left,j=right,temp=arr[left];
while(i<j)
{
while(i<j&&arr[j]>=temp) j--;
while(i<j&&arr[i]<=temp) i++;
if(i<j) exchange(&arr[i],&arr[j]);
}
arr[left]=arr[i];//更換下一個參考數
arr[i]=temp;//參考數回到i,j之間
quicksort(arr,i+1,right);//帶入遞歸
quicksort(arr,left,i-1);
}
6、堆排序
簡介:搞清楚堆排序,首先要搞明白幾個概念:最大堆,最小堆,堆源自完全二叉樹,完全二叉樹與滿二叉樹的關係,滿二叉樹與普通二叉樹區別,普通二叉樹與多叉樹的特點,樹的基本屬性,樹與無向不閉合圖的關係。開玩笑的啦,其實這裏面只要理解最大堆就好了,別的就當串聯複習吧。
排序機理:堆排序,顧名思義是在堆的基礎上進行排序。所以首先要創建一個堆,並且把初始堆調整爲最大堆,這樣堆中的最大數就排到了最頂端,然後,只需將最頂端的數與數組中(也是堆中)最後一位調換位置,把調換過位置的最大值稱之爲有序區,接着再繼續把調換之後無序區的堆,調整爲最大堆即可,最後連續進行n-1個循環即可實現堆排序。過程就像下圖一樣(圖來自公衆號:五分鐘學算法)
實現效果就像下圖一樣:
時間複雜度:O(n log n)
穩定性:不太穩,速度也還行
代碼:
void heapify(int arr[],int n,int i)//不斷調整以保證是最大堆
{
int largest=i;
int l=2*i+1;
int r=2*i+2;
if(l<n&&arr[l]>arr[largest])//最大元素向上排
largest=l;//本質是數組的下標largest在移動
if(r<n&&arr[r]>arr[largest])
largest=r;
if(largest!=i)//遞歸處理子堆
{
swap(arr[i],arr[largest]);
heapify(arr,n,largest);
}
}
void heapsort(int arr[],int n)
{
for(int i=n/2-1;i>=0;i--)//建立堆
heapify(arr,n,i);
for(int i=n-1;i>=0;i--)//n-1次將堆頂最大值與有序區交換
{
swap(arr[0],arr[i]);
heapify(arr,i,0);
}
}
7 、歸併排序
簡介:歸併排序是在前面的排序的基礎上加上了分治思想,分而治之。分到什麼程度?分到直到只剩1,不能再分爲止。然後呢?然後再兩兩相比較來合併成有序的數組,將若干個這樣的數組中的元素兩兩相比,按照大小順序合併。
算法機理:下面用一張動態的直方圖來表示這個過程,直方圖中不同高低代表不同數值大小的元素。先分後後合一目瞭然。另外根據這個圖也說明了,遞歸的本質就是樹。
時間複雜度:O(n log n)
穩定性:穩定,速度一般
代碼:
void merge(int arr[],int l,int m,int r)
{
int i,j,k;
int n1=m-l+1;//切分
int n2=r-m;
int L[n1],R[n2];
for(i=0;i<n1;i++)
L[i]=arr[l+i];
for(j=0;j<n2;j++)
R[j]=arr[m+1+j];
i=0;
j=0;
k=l;
while(i<n1&&j<n2)//合併操作
{
if(L[i]<=R[j])
{
arr[k]=L[i];
i++;
}
else
{
arr[k]=R[j];
j++;
}
k++;
}
while(i<n1)//解決兩個數組比較後大小不同問題
{
arr[k]=L[i];
i++;
k++;
}
while(j<n2)
{
arr[k]=R[j];
j++;
k++;
}
}
void mergesort(int arr[],int l,int r)
{
if(l<r)
{
int m=l+(r-l)/2;
mergesort(arr,l,m);
mergesort(arr,m+1,r);
merge(arr,l,m,r);
}
}
8、選擇排序
簡介:選擇排序是一種很簡單的排序方法,直觀明瞭。也是通過比較並通過循環調換位置來進行排序的。
算法機理:
時間複雜度:O(n^2)
穩定性:不穩定,速度慢
代碼:
//選擇排序
void selectionsort(int arr[],int length)
{
int temp,minindex=0;
for(int i=0;i<length;i++)
{
for(int j=i+1;j<length;j++)
if(arr[j]<arr[minindex]) minindex=j;
temp=arr[i];
arr[i]=arr[minindex];
arr[minindex]=temp;
}
}
9、計數排序
簡介:計數排序作爲線性時間複雜度的排序,必須具有明確的數據範圍。計數排序不是基於比較的方法,而是將元素數值按儲存到另外開闢數組空間內。
算法機理:
時間複雜度:O(n+k)
穩定性:穩定,速度快
代碼:
void countingsort(int arr[],int maxvalue,int length)//計數排序需要提供數據範圍
{
int* counting=new int[maxvalue+1]();
int a=0;
for(int i=0;i<=length;i++)
counting[arr[i]]++;
for(int i=1;i<=maxvalue;i++)
{
int temp=counting[i];
counting[i]+=counting[i-1];
while(temp--)
arr[a++]=i;
}
}
10、桶排序
簡介:桶排序其實就是加強版的計數排序。桶排序的思路就是將一組數據先按照一定的規則放到不同的桶中,桶的分配一般有簡單分桶和規化分桶,
- 簡單分桶:桶號=max / 10 – min / 10 + 1
- 規約化分桶:桶號=(array[i] - min) / (max - min) * array.length
分完桶後,先對桶號排序,再對每個桶內的元素分別排序(這裏有可能使用到別的排序方法或者是以遞歸方式繼續用桶排序)。
排序機理:
至於每個桶裏面的排序就不贅述了,直接嵌套別的排序方法即可。
時間複雜度:O(n+k)
穩定性:穩定,速度很快
代碼:
int getmax(int arr[],int n)//尋找數組中最大元素
{
int mx=arr[0];
for(int i=1;i<n;i++)
if(arr[i]>mx)
mx=arr[i];
return mx;
}
int getmin(int arr[],int n)//尋找數組中最大元素
{
int mi=arr[0];
for(int i=1;i<n;i++)
if(arr[i]<mi)
mi=arr[i];
return mi;
}
void bucketsort(int arr[],int n)
{
vector<int> b[n]; //向量b的大小應當≥桶序號bn可能出現的最大值
int max=getmax(arr,n);
int min=getmin(arr,n);
for(int i=0;i<n;i++)
{
int bn=max/10-min/10+1;
// int bn=(arr[i]-min)/(max-min)*10;//簡單分桶
// int bn=arr[i]/(N/100);//歸約化分桶
// cout<<i<<"bn="<<bn<<endl;
b[bn].push_back(arr[i]);//在序號爲bn的桶中(序列最後)插入目標元素
}
for(int i=0;i<n;i++)//sort需要include <algorithm>
sort(b[i].begin(),b[i].end());
int index=0;//桶合併
for(int i=0;i<n;i++)
for(int j=0;j<b[i].size();j++)
arr[index++]=b[i][j];
}
時間複雜度對比
附:調試程序
#include<iostream>
#include<stdlib.h>
#include<time.h>
#include<vector>
#include<cstddef>
#include<algorithm>
#define N 10000
using namespace std;
void insertion_sort (int arr[],int length)
{//插入排序
int i,j;
for(i=1;i<length;i++)
{
int temp=arr[i];
for(j=i;j>0&&arr[j-1]>temp;j--)
{
arr[j]=arr[j-1];
}
arr[j]=temp;
}
}
//////////////////////////////////////////////////////////////////
void shellsort(int arr[],int n)
{//希爾排序
for(int gap=n/2;gap>0;gap/=2)
{
for(int i=gap;i<n;i++)
{
int temp=arr[i];
int j;
for(j=i;j>=gap&&arr[j-gap]>temp;j-=gap)
arr[j]=arr[j-gap];
arr[j]=temp;
}
}
}
////////////////////////////////////////////////////
//基數排序
int getMax(int arr[],int n)//尋找數組中最大元素
{
int mx=arr[0];
for(int i=1;i<n;i++)
if(arr[i]>mx)
mx=arr[i];
return mx;
}
void countsort(int arr[],int n,int exp)
{
int output[n];
int i,count[10]={0};
for(i=0;i<n;i++)
count[(arr[i]/exp)%10]++;//取出某一位,進行10個數字的分配
for(i=1;i<10;i++)
count[i]+=count[i-1];//count數組從1到9逐次累加前一項
for(i=n-1;i>0;i--)
{
output[count[(arr[i]/exp)%10]-1]=arr[i];//將arr中的元素按照取餘的梯度直方圖,依次放到合適的位置
count[(arr[i]/exp)%10]--;//該基數的剩餘空位置相應減少一個
}
for(i=0;i<n;i++)
arr[i]=output[i];
}
void radixsort(int arr[],int n)
{
int m=getMax(arr,n);
for(int exp=1;m/exp>0;exp*=10)
countsort(arr,n,exp);
}
//////////////////////////////////////////////////////////////////////////
void bubblesort(int a[],int n)//void bubblesort(vector<int>& a)
{
bool swapp=true;
while(swapp)
{
swapp=false;
for(int i=0;i<n;i++)//for(size_t i=0;i<a.size()-1;i++)
{
if(a[i]>a[i+1])
{
a[i] += a[i+1];
a[i+1] = a[i]-a[i+1];
a[i] -= a[i+1];
swapp=true;
}
}
}
}
////////////////////////////////////////////////////////////////////////////
//歸併排序
void merge(int arr[],int l,int m,int r)
{
int i,j,k;
int n1=m-l+1;//切分
int n2=r-m;
int L[n1],R[n2];
for(i=0;i<n1;i++)
L[i]=arr[l+i];
for(j=0;j<n2;j++)
R[j]=arr[m+1+j];
i=0;
j=0;
k=l;
while(i<n1&&j<n2)//合併操作
{
if(L[i]<=R[j])
{
arr[k]=L[i];
i++;
}
else
{
arr[k]=R[j];
j++;
}
k++;
}
while(i<n1)//解決兩個數組比較後大小不同問題
{
arr[k]=L[i];
i++;
k++;
}
while(j<n2)
{
arr[k]=R[j];
j++;
k++;
}
}
void mergesort(int arr[],int l,int r)
{
if(l<r)
{
int m=l+(r-l)/2;
mergesort(arr,l,m);
mergesort(arr,m+1,r);
merge(arr,l,m,r);
}
}
/////////////////////////////////////////////////////////////////
//堆排序
void heapify(int arr[],int n,int i)//不斷調整以保證是最大堆
{
int largest=i;
int l=2*i+1;
int r=2*i+2;
if(l<n&&arr[l]>arr[largest])//最大元素向上排
largest=l;//本質是數組的下標largest在移動
if(r<n&&arr[r]>arr[largest])
largest=r;
if(largest!=i)//遞歸處理子堆
{
swap(arr[i],arr[largest]);
heapify(arr,n,largest);
}
}
void heapsort(int arr[],int n)
{
for(int i=n/2-1;i>=0;i--)//建立堆
heapify(arr,n,i);
for(int i=n-1;i>=0;i--)//n-1次將堆頂最大值與有序區交換
{
swap(arr[0],arr[i]);
heapify(arr,i,0);
}
}
/////////////////////////////////////////////////////////////
//桶排序
int getmax(int arr[],int n)//尋找數組中最大元素
{
int mx=arr[0];
for(int i=1;i<n;i++)
if(arr[i]>mx)
mx=arr[i];
return mx;
}
int getmin(int arr[],int n)//尋找數組中最大元素
{
int mi=arr[0];
for(int i=1;i<n;i++)
if(arr[i]<mi)
mi=arr[i];
return mi;
}
void bucketsort(int arr[],int n)
{
vector<int> b[n]; //向量b的大小應當≥桶序號bn可能出現的最大值
int max=getmax(arr,n);
int min=getmin(arr,n);
for(int i=0;i<n;i++)
{
int bn=max/10-min/10+1;
// int bn=(arr[i]-min)/(max-min)*10;//簡單分桶
// int bn=arr[i]/(N/100);//歸約化分桶
// cout<<i<<"bn="<<bn<<endl;
b[bn].push_back(arr[i]);//在序號爲bn的桶中(序列最後)插入目標元素
}
for(int i=0;i<n;i++)//sort需要include <algorithm>
sort(b[i].begin(),b[i].end());
int index=0;//桶合併
for(int i=0;i<n;i++)
for(int j=0;j<b[i].size();j++)
arr[index++]=b[i][j];
}
///////////////////////////////////////////////////////
//計數排序
void countingsort(int arr[],int maxvalue,int length)//計數排序需要提供數據範圍
{
int* counting=new int[maxvalue+1]();
int a=0;
for(int i=0;i<=length;i++)
counting[arr[i]]++;
for(int i=1;i<=maxvalue;i++)
{
int temp=counting[i];
counting[i]+=counting[i-1];
cout<<i<<'\t'<<temp<<'\t'<<counting[i]<<'\t'<<'\t';
while(temp--)
{
arr[a++]=i;
}
}
}
////////////////////////////////////////////////////////////////////////
//選擇排序
void selectionsort(int arr[],int length)
{
int temp,minindex=0;
for(int i=0;i<length;i++)
{
for(int j=i+1;j<length;j++)
if(arr[j]<arr[minindex]) minindex=j;
temp=arr[i];
arr[i]=arr[minindex];
arr[minindex]=temp;
}
}
///////////////////////////////////////////////////////////////////////////////
//快速排序
void exchange(int *p,int *q)
{
int temp=*p;
*p=*q;
*q=temp;
}
void quicksort(int arr[],int left,int right)
{
if(left>=right) return ;
int i=left,j=right,temp=arr[left];
while(i<j)
{
while(i<j&&arr[j]>=temp) j--;
while(i<j&&arr[i]<=temp) i++;
if(i<j) exchange(&arr[i],&arr[j]);
}
arr[left]=arr[i];//更換下一個參考數
arr[i]=temp;//參考數回到i,j之間
quicksort(arr,i+1,right);//帶入遞歸
quicksort(arr,left,i-1);
}
////////////////////////////////////////////////////////////////////////////////
int main()
{//以下注釋默認N=10000
int a[N];//幾乎不消耗時間
for(int i=0;i<N;i++)//消耗約200ms
a[i]=rand()%N;
for(int i=0;i<N;i++)//消耗約5000ms
{
cout<<a[i]<<'\t';
if((i+1)%15==0) cout<<'\n';
}
cout<<endl<<endl;
clock_t start=clock();//clock返回當前次數
//insertion_sort(a,N);// 插入排序消耗約112ms
//shellsort(a,N) ;//希爾排序約2ms
//radixsort(a,N);//基數排序 4ms
//bubblesort(a,N);//冒泡排序1157ms
//mergesort(a,0,N);//歸併排序5ms
//heapsort(a,N); //堆排序3ms
//bucketsort(a,N);//桶排序5ms
//countingsort(a,N,N);//計數排序0ms
//selectionsort(a,N);//選擇排序204ms
quicksort(a,0,N); //快速排序
clock_t end =clock();
for(int i=0;i<N;i++)//消耗約4700ms
{
cout<<a[i]<<'\t';
if((i+1)%15==0) cout<<'\n';
}
cout<<endl<<"耗時:"<<(double)(end-start)/CLOCKS_PER_SEC<<"s"<<endl;
return 0;
}