快速排序演算法的思想和幾種實現方式
快速排序演算法是基於分治策略的另一個排序演算法。
該方法的基本思想是:
1.先從數列中取出一個數作為基準數,記為x。
2.分割槽過程,將不小於x的數全放到它的右邊,不大於x的數全放到它的左邊。(這樣key的位置左邊的沒有大於key的,右邊的沒有小於key的,只需對左右區間排序即可)
3.再對左右區間重複第二步,直到各區間只有一個數
快排目前有兩類實現演算法,第一種是標準演算法,第二種是兩頭交換法。總的思想與上面三步一樣,在細節處理上有一些差異。
標準演算法思想及實現
標準算演算法採用的思想是挖坑填坑的思想:
以一個數組作為示例,取區間第一個數為基準數。
0 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
72 |
6 |
57 |
88 |
60 |
42 |
83 |
73 |
48 |
85 |
初始時,i = 0; j = 9; X = a[i] = 72
由於已經將a[0]中的數儲存到X中,可以理解成在陣列a[0]上挖了個坑,可以將其它資料填充到這來。
從j開始向前找一個比X小或等於X的數。當j=8,符合條件,將a[8]挖出再填到上一個坑a[0]中。a[0]=a[8]; i++; 這樣一個坑a[0]就被搞定了,但又形成了一個新坑a[8],這怎麼辦了?簡單,再找數字來填a[8]這個坑。這次從i開始向後找一個大於X的數,當i=3,符合條件,將a[3]挖出再填到上一個坑中a[8]=a[3]; j--;
陣列變為:
0 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
48 |
6 |
57 |
88 |
60 |
42 |
83 |
73 |
88 |
85 |
i = 3; j = 7; X=72
再重複上面的步驟,先從後向前找,再從前向後找。
從j開始向前找,當j=5,符合條件,將a[5]挖出填到上一個坑中,a[3] = a[5]; i++;
從i開始向後找,當i=5時,由於i==j退出。
此時,i = j = 5,而a[5]剛好又是上次挖的坑,因此將X填入a[5]。
陣列變為:
0 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
48 |
6 |
57 |
42 |
60 |
72 |
83 |
73 |
88 |
85 |
可以看出a[5]前面的數字都小於它,a[5]後面的數字都大於它。因此再對a[0…4]和a[6…9]這二個子區間重複上述步驟就可以了。
對挖坑填數進行總結
1.i =L; j = R; 將基準數挖出形成第一個坑a[i]。
2.j--由後向前找比它小的數,找到後挖出此數填前一個坑a[i]中。
3.i++由前向後找比它大的數,找到後也挖出此數填到前一個坑a[j]中。
4.再重複執行2,3二步,直到i==j,將基準數填入a[i]中。
程式碼實現如下:
#include <iostream>
using namespace std;
void quick_sort(int s[],int l, int r){
int i = l,j = r, x = s[l];
while(i < j){
while(i < j && s[j] > x) j--;
if(i < j)
s[i++] = s[j];
while(i < j && s[i] < x) i++;
if(i < j)
s[j--] = s[i];
}
//此時i==j,下面s[i]或者s[j]都可以,j-1,j+1也ok
s[j] = x;
if (l<i) quick_sort(s,l, i - 1);
if (r>i) quick_sort(s,i + 1, r);
};
int main()
{
int test[] = {34,5,4,5,3,2,6,90,5};
quick_sort(test,0,8);
for(auto c : test){
cout<<c<<" ";
}
cout<<endl;
return 0;
}
兩頭交換法思想及實現
兩頭交換法與標準演算法思想的差異是,先從左邊開始找到大於基準值的那個數,再從右邊找到小於基準值的那個數,將兩個數交換(這樣比基準值小的都在左邊,比基準值大的都在右邊)。直到數列分成大於基準值和小於基準值的兩個區間,以這兩個區間進行同樣的排序操作。
程式碼實現如下:
#include <iostream>
using namespace std;
void quickSort(int a[],int beg,int end){
//partition非遞迴實現,官方版
if(beg >= end) return;
int i = beg, j = end, x = a[(i + j)>>1],tmp =0;//這裡基準值選了中間的值
while(i <= j){//取等號,確保分成兩個不相交區間
while( a[i] < x) i++;
while(a[j] > x) j--;
if(i <= j ){
tmp = a[i];
a[i] = a[j];
a[j] = tmp;
i++;
j--;
}
}
quickSort(a,beg,j);
quickSort(a,i,end);
};
int main()
{
int test[] = {34,6,4,5,1,2,6,90,7};
quickSort(test,0,8);
for(auto c : test){
cout<<c<<" ";
}
cout<<endl;
return 0;}
上面的演算法是兩頭交換法官方的版本,邊界情況較少,比較健壯。
兩頭交換法還有另一個實現方式,這種實現方式,基準值只能選區間第一個值或最後一個值。基準值不參與交換,將除基準值之外的所有值按照與基準值的大小關係分成兩部分,然後將區間分界點的值填到基準值的坑裡,將基準值放在區間分界點。對於基準值左右的區間進行再次排序。
程式碼實現:
#include <iostream> using namespace std; //兩點交換法,固定軸點的實現,基準點不參與排序 //基準點選第一個值,中間交換點選j, //基準點選第一個值,中間交換點選i //不然,會出現死迴圈 //將除第一個值之外的其他值交換使得小於基準值的在前,大於的在後,然後最中間點較小的j位置的值的與第一個值交換,交換後前面的小於基準值,後面的大於基準值 int partition(int b[],int first,int last){ int x = b[first],temp = 0; int i = first,j = last + 1;//因為後面判斷是--j while(true){ while(b[++i] < x && i <= last); while(b[--j] > x); if(i >= j){ break; } temp = b[i]; b[i] = b[j]; b[j] = temp; } b[first] = b[j]; b[j] = x; return j; }; void quickSort(int a[],int beg,int end){ if(beg < end){ int q = partition(a,beg,end); quickSort(a,beg,q-1); quickSort(a,q+1,end); } }; int main() { int test[] = {34,5,4,5,3,2,6,90,5}; quickSort(test,0,8); for(auto c : test){ cout<<c<<" "; } cout<<endl; return 0; }
注意:兩頭交換法,最後填坑點的選擇與基準值的選擇有關係,當基準值在區間前半部分則填坑點選值較小的j,反之則選i.