快速排序作為應用比較廣泛,而且時間複雜度比較優越的排序演算法備受大家的喜愛。最近有點悠閒,就又把這個快速演算法研究了一遍,目前掌握了兩種排序演算法的思路,為了以免忘記,故詳細的記錄下來,也供大家學習借鑑,不足之處望請指教。
快速排序的基本原理:
假設一個待排序的陣列如上圖所示,排序的目的就是將其從小到大排序。快速排序的主要步驟就是設定一個待排序的元素(稱作主元,記作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;
}
執行結果:
全文完,歡迎指教。