1. 程式人生 > >演算法導論學習之快排+各種排序演算法時間複雜度總結

演算法導論學習之快排+各種排序演算法時間複雜度總結

快排是一種最常用的排序演算法,因為其平均的時間複雜度是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)的時間複雜度是經常可以達到,並且快排中的常數因子比較小,所以在實際中快排一般是最快的排序演算法,不愧他”快排”的稱號。