快速排序演算法的兩個寫法

orchard發表於2014-06-20

    快速排序作為應用比較廣泛,而且時間複雜度比較優越的排序演算法備受大家的喜愛。最近有點悠閒,就又把這個快速演算法研究了一遍,目前掌握了兩種排序演算法的思路,為了以免忘記,故詳細的記錄下來,也供大家學習借鑑,不足之處望請指教。

快速排序的基本原理:

    假設一個待排序的陣列如上圖所示,排序的目的就是將其從小到大排序。快速排序的主要步驟就是設定一個待排序的元素(稱作主元,記作temp),經過一輪劃分排序後在這個主元左邊的元素值都小於它,在主元右邊的元素值都大於它,一輪劃分後的效果應該是這樣的,如下圖:

 

這樣以temp元素為分隔就將原來的陣列分成了兩個左右兩個陣列,然後再分別對左右兩個子陣列進行同樣的分隔,然後再對子子陣列進行分割,遞迴呼叫這種分隔,直到最後不能再分了,此時陣列也就是有序的了。

 

基本原理是相同的,但是具體是怎麼分隔的有兩種不同的思路。一種我戲稱為:“左右倒騰法”。此種方法將陣列分成了三個部分,小於主元的部分,大於主元的部分,未劃分的部分。如下圖所示:

 

 

首先,要選定一個元素作為主元temp,這裡將第一個元素left視為主元temp,然後將主元分別從左右兩頭開始比較,在右邊大元素區遇到小於temp的元素就將其放到左邊的小元素區,同時更新右邊比較的下標,轉到左邊小元素區比較,若在小元素區遇到大於temp的元素就將其放到右邊的大元素區;下面具體示例下過程。

1、首先將主元temp與右邊right區的元素比較,假設最右邊的兩個都是大於temp的,那麼它們的位置不變,就呆在藍區,注意這裡只比較與temp的大小,具體藍區這兩個元素誰大誰小沒有關係,只要大於temp,他們的順序無關緊要。右邊第三個元素小於temp,如下圖所示:

 

此時將右邊第三個小於temp的元素放到左邊left它應該呆在的地方,這時放到左邊第一個元素,不用擔心會把左邊第一個元素覆蓋掉,因為此時temp的值就是第一個元素的值。這時右邊第三個位置就空出來了,要存放下次在左邊區域找到的大於temp的元素值。

用程式碼來表示就是:

while((temp <= a[right]) &&(left < right))

           right --;

        a[left]= a[right];

 

2、每當發生一次交換的時候,就要反轉方向比較,此時要從左邊第一個開始比較,直到找到一個大於temp的元素,然後將這個元素放到右邊剛剛空出來的第三個位置。顯然此時左邊第一個值就是剛剛右邊第三個值,是滿足小於temp的,然後繼續比較左邊第二個元素,假設左邊第二個元素大於temp,顯然這個大於temp的元素是應該呆在右邊區域的,將此元素值放到第一步中空出來的右邊第三個元素中,此時:

用程式碼來描述就是:

while((temp > a[left]) && (left< right))

           left ++;

        a[right] = a[left];

經過左右兩次比較後,陣列的狀態是:

 

然後繼續重複1、2兩個步驟,直到將未比較區域的元素比較完,最後temp將處於一個唯一確定的位置,此位置也是整個陣列排序後的最後位置.

 

此時temp元素的左邊都是小於(小於等於)temp的,右邊都是大於(大於等於)temp的元素,但是藍色和綠色區域內的大小順序是不定的,此時要對其重複執行上述1、2,遞迴呼叫,直至所有的元素都處於唯一確定的位置,此時整個陣列也就是排序完成了。

 完整的一次劃分程式碼是:

int partion2(int a[],int left,int right)

{

   

   int tmp = a[left];

   while(left < right)

    {

       while((tmp <= a[right]) && (left < right))

           right --;

       a[left]= a[right];

        cout<<"In the first while"

           <<"  a[left] ="<<a[left]

           <<"  tmp ="<<tmp

           <<"  a[right] ="<<a[right]<<endl;

       while((tmp > a[left]) && (left < right))

           left ++;

       a[right] = a[left];

       cout<<endl<<"In the second while "

 

           <<"  a[left] ="<<a[left]

           <<"  tmp ="<<tmp

           <<"  a[right] ="<<a[right]<<endl;

    }

   a[left]= tmp;

   return left;

}

這個partion函式是整個快速排序中最重要的一步。下面的則是完成了對左右子陣列的遞迴劃分:

void quicksort(int a[],int left,int right)

{

   int p;

   if(left< right)

    {

       p = partion3(a,left,right);

       quicksort(a,left,p-1);

       quicksort(a,p+1,right);

    }

}

 

顯然,這種劃分的思路是從陣列的左右兩端依次分別比較,直至最終將主元放到應該放到的位置。第二種方法是這樣的。將整個陣列劃分為三個部分,自左向右分別是小於temp元素區,大於temp元素區,未比較元素區,如下:、

首先,選取左邊第一個元素為主元temp,設定兩個下標指標I,j分別指向第一個、第二個元素,綠色和藍色的區域分別向未比較的元素區域增長,i指向綠色區域的最右邊,表示是小於temp元素區的最右邊的一個值,j指向藍色區域的最右邊,表示大於temp元素區域的最右邊值,當j和未比較的元素區域的元素比較發現一個小於temp的值,就將此值與藍色區域的最左邊的一個值交換,此時綠色區域加1,藍色區域長度不變,如果沒有發現小於temp的元素,則j++,一直往下比較。當j一直到陣列的最後一個元素後,交換a[left]和a[i]的值,此時a[i]就是a[left]所應處的位置,i的左邊都是小於temp的值,i的右邊都是大於temp的值。

   流程如下:

1、i指向第一個元素,j指向第二個元素j,判斷a[j]與temp的大小,如果a[i]大於temp表示此時a[j]就是應該處於藍色區域,j++進行下一個比較。

2、如果a[j]<=temp,表示此時a[j]不應該處於這個大於temp的藍色區域,應該是處於小於temp的綠色區域。i++,然後交換a[j]和a[i]的值,由於此時i=0,j=1,此時a[i]和a[j]的交換其實就是第二個陣列元素自己的交換

假設,j在第5個元素髮現a[j]<temp,此時:

 

也就是,此時j指向的元素大於temp了,應該放到i所指向的小於temp區域的下一個位置,顯然要交換a[j]和藍色區域的最左邊的值,也就是a[i]的下一個值。完成後綠色區域有兩個值,藍色區域還是三個值,j指向下一個未比較的元素值。

 

 

然後依次重複進行這樣的比較,直到j達到最後一個,假設是這樣的:

 

但此時主元還是a[left],要交換a[lef]和a[i]的值,此時a[i]的左邊都是小於temp的元素,右邊都是大於temp的元素,如下:

 

這樣就完成了一次劃分,然後再對temp左右的子陣列遞迴呼叫這種劃分,直至最後完成所有元素最終位置的確定。

程式碼描述:

int partion3(inta[],int left,int right)

{

    int tmp = a[left];

    int i =left;

   

    for(int j=left+1;j<=right;j++)

    {

        if(a[j]<tmp)

        {

            i++;

            swap(a[i],a[j]);

        }

    }

    swap(a[left],a[i]);

    return i;

}

 

遞迴呼叫的函式和第一種方法是一樣的都是:

void quicksort(inta[],int left,int right)

{

    int p;

    if(left< right)

    {

        p = partion3(a,left,right);

        quicksort(a,left,p-1);

        quicksort(a,p+1,right);

    }

}

 

至此,兩種方法就介紹完了,兩種方法都是維持了兩個小於主元temp的區域(綠色)和大於主元temp的區域(藍色),在未比較區域(白色)尋找滿足條件的值,將其放到藍色和綠色區域。

下面是兩種方法完整的程式碼,並附有測試用例,其排序結果顯然是一樣的。

/*************************************************************************

    > File Name: quicksort.cpp

    > Author: songyuanguo

    > Description: quick sort

    > Created Time: Fri 20 Jun 2014 05:17:48AM CST

 ************************************************************************/

 

#include<iostream>

using namespacestd;

 

void swap(int&a,int &b)

{

    int tmp;

    tmp = a;

    a   =b;

    b   =tmp;

}

int partion1(inta[],int left,int right)

{

   

    int tmp = a[left];

    while(left < right)

    {

        while((tmp <= a[right]) &&(left < right))

            right --;

        a[left]= a[right];

        while((tmp > a[left]) &&(left < right))

            left ++;

        a[right] = a[left];

    }

    a[left]= tmp;

    return left;

}

int partion2(inta[],int left,int right)

{

    int tmp = a[left];

    int i =left;

   

    for(int j=left+1;j<=right;j++)

    {

        if(a[j]<tmp)

        {

            i++;

            swap(a[i],a[j]);

        }

    }

    swap(a[left],a[i]);

    return i;

}

   

 

voidquicksort1(int a[],int left,int right)

{

    int p;

    if(left< right)

    {

        p = partion1(a,left,right);

        quicksort1(a,left,p-1);

        quicksort1(a,p+1,right);

    }

}

 

voidquicksort2(int a[],int left,int right)

{

    int p;

    if(left< right)

    {

        p = partion2(a,left,right);

        quicksort2(a,left,p-1);

        quicksort2(a,p+1,right);

    }

}

 

int main(intarg,char *agv[])

{

    int a[] ={ 3,6,9,1,0,16};

    cout<<"befor sort is:"<<endl;

    for(int i =0;i<6;i++)

        cout<<a[i]<<" ";

    cout<<endl;

    quicksort1(a,0,5);

    cout<<"output of quick sort 1:"<<endl;

    for(int i=0;i<6;i++)

        cout<<a[i]<<" ";

    cout<<endl;

    cout<<"output of quick sort 2:"<<endl;

    quicksort2(a,0,5);

    for(int i=0;i<6;i++)

        cout<<a[i]<<" ";

    cout<<endl;

 

    return 0;

}

執行結果:

 

全文完,歡迎指教。

 

相關文章