演算法導論學習之快排+各種排序演算法時間複雜度總結
快排是一種最常用的排序演算法,因為其平均的時間複雜度是nlgn,並且其中的常數因子比較小。
一.快速排序
快排和合並排序一樣都是基於分治的排序演算法;快排的分治如下:
分解:對區間A[p,r]進行分解,返回q,使得A[p–q-1]都不大於A[q] A[q+1,r]都大於A[q];
求解:對上面得到的區間繼續遞迴進行快排
合併:因為快排是原地排序,所以不需要特別的合併
從上可以看出最重要的就是分解函式,其按關鍵值將陣列劃分成3部分,其具體實現的過程見程式碼註釋。
我們一般取陣列的最後一個元素作為劃分比較的關鍵值,如下面的程式碼
int Paratition(int *a,int p,int r)
{ ///在迴圈時a[p--i]表示的是不大於key的元素
///a[i+1--j]表示的是當前大於key的元素
///劃分的過程其實就是將每一個元素通過比較放到這兩個區間去(主要是i的增長)。
///當然最後還要將a[i+1]和a[r]交換,使得a[i+1]表示劃分元素
int key=a[r]; ///取最後一個元素作為比較的關鍵值
int i=p-1;
for(int j=p;j<r;j++)
if(a[j]<=key)
{
i++;
swq(a[j],a[i]);
}
swq(a[i+1 ],a[r]);
return i+1;
}
但是我們也可以在區間a[p,r]中任取一個元素作為關鍵值,這樣可以使得每次的劃分更加的均勻,從而提高效率,對應的程式碼如下:
///隨機劃分函式
int RandParatition(int *a,int p,int r)
{
int x=rand()%(r-p+1)+p; ///產生一個[p,r]之間的隨機數x
swq(a[x],a[r]); ///將a[x],a[r]交換,使得將a[x]作為劃分的關鍵值
return Paratition(a,p,r);
}
下面給出一份快排的完整程式碼:
#include<iostream>
#include<cstdio>
#include<ctime>
#include<cstring>
using namespace std;
void swq(int &a,int &b)
{
int t=a;
a=b;
b=t;
}
///劃分函式
int Paratition(int *a,int p,int r)
{
int key=a[r];
int i=p-1;
for(int j=p;j<r;j++)
if(a[j]<=key)
{
i++;
swq(a[j],a[i]);
}
swq(a[i+1],a[r]);
return i+1;
}
///快排
void QiuckSort(int *a,int p,int r)
{
if(p>=r)
return;
int q=Paratition(a,p,r);
QiuckSort(a,p,q-1);
QiuckSort(a,q+1,r);
}
///以上是一般的快排,還有一個隨機快排
///隨機快排的思想在於劃分a[p,r]時我們不是每次都選a[r]作為關鍵值
///而是每次隨機的在a[p,r]中取一個元素作為劃分的關鍵值。
///隨機劃分函式
int RandParatition(int *a,int p,int r)
{
int x=rand()%(r-p+1)+p; ///產生一個[p,r]之間的隨機數x
swq(a[x],a[r]); ///將a[x],a[r]交換,使得將a[x]作為劃分的關鍵值
return Paratition(a,p,r);
}
///隨機快排函式
void RandQiuckSort(int *a,int p,int r)
{
if(p>=r)
return;
int q=RandParatition(a,p,r); ///隨機快排呼叫的是隨機劃分函式
RandParatition(a,p,q-1);
RandParatition(a,q+1,r);
}
int main()
{
int n=5,a[10];
cout<<"請輸入"<<n<<"個數:"<<endl;
for(int i=1;i<=n;i++)
cin>>a[i];
///RandQiuckSort(a,1,n);
QiuckSort(a,1,n);
cout<<"排序以後的陣列:"<<endl;
for(int i=1;i<=n;i++)
cout<<a[i]<<" ";
cout<<endl;
return 0;
}
二.快排的時間複雜度:快排的時間複雜度分析是一件很麻煩的事情,我們知道如果每次的劃分都是完全不平衡的即T(n)=T(n-1)+O(n),那麼快排的時間複雜度是n^2;如果每次都是均等的劃分即T(n)=T(n/2)+T(n/2)+O(n),那麼時間複雜度就是nlgn。但是這兩種劃分在平時都是不常見的,所以不具有代表性;但是如果我們考察一些不平衡的劃分,如每次都是9:1即T(n)=T(n/10)+T(n*9/10)+O(n),我們可以發現在這種情況下時間複雜度仍然是nlgn;並且即使不平衡性進一步增加(只要不是完全不平衡),達到99:1的程度,時間複雜度仍然會是nlgn(關於這一段演算法導論上有大篇幅的證明)。所以我們可以說快排的平均時間複雜度是nlgn,這個複雜度是經常可以達到的。
三.常用排序演算法的時間複雜度總結
以下簡單的羅列出前面學習過的排序演算法的時間複雜度,不給出嚴格的證明。
排序演算法 最好時間複雜度 最壞時間複雜度
插入排序 O(n)(原陣列有序) O(n^2)(原陣列逆序)
合併排序 O(nlgn) O(nlgn)
堆排序 O(nlgn) O(nlgn)
快速排序 O(n^2)(每次都是完全不平衡劃分) O(nlgn)(大多數情況)
從上面的表格看似乎合併排序和堆排序都要比快排好,但實際中因為快排O(nlgn)的時間複雜度是經常可以達到,並且快排中的常數因子比較小,所以在實際中快排一般是最快的排序演算法,不愧他”快排”的稱號。