排序演算法(六):快速排序(Quick Sort)
阿新 • • 發佈:2018-11-26
基本思想:
1)選擇一個基準元素,通常選擇第一個元素或者最後一個元素,
2)通過一趟排序講待排序的記錄分割成獨立的兩部分,其中一部分記錄的元素值均比基準元素值小。另一部分記錄的 元素值比基準值大。
3)此時基準元素在其排好序後的正確位置
4)然後分別對這兩部分記錄用同樣的方法繼續進行排序,直到整個序列有序。
快速排序的示例:
演算法的實現:
遞迴實現:
#include <stdio.h> #include <iostream> using namespace std; static int i = 0; void print(int a[],int i) { cout<<i <<" : "; for(int j= 0; j<10; j++) { cout<<a[j] <<" "; } cout<<endl; } void swap(int *a, int *b) { int tmp = *a; *a = *b; *b = tmp; } int partition(int a[], int low, int high) { int privotKey = a[low]; //基準元素 while(low < high) { //從表的兩端交替地向中間掃描 while(low < high && a[high] >= privotKey) --high; swap(&a[low], &a[high]); //從high 所指位置向前搜尋,至多到low+1 位置。 //將比基準元素小的交換到低端 while(low < high && a[low] <= privotKey ) ++low; swap(&a[low], &a[high]); } print(a,i++); return low; } void quickSort(int a[], int low, int high) { if(low < high){ int privotLoc = partition(a, low, high); //將表一分為二 quickSort(a, low, privotLoc -1); //遞迴對低子表遞迴排序 quickSort(a, privotLoc + 1, high); //遞迴對高子表遞迴排序 } } int main() { int H[10] = {3,1,5,7,2,4,9,6,10,8}; quickSort(H,0, 9); return 0; }
執行結果:
0 : 2 1 3 7 5 4 9 6 10 8
1 : 1 2 3 7 5 4 9 6 10 8
2 : 1 2 3 6 5 4 7 9 10 8
3 : 1 2 3 4 5 6 7 9 10 8
4 : 1 2 3 4 5 6 7 9 10 8
5 : 1 2 3 4 5 6 7 8 9 10
快速排序優化1:三數取中法
因為雖然快速排序整體的效率可觀,但是當最壞情況發生時它的效率就會降低,為了降低最壞情況發生的概率,我們可以做如下改進。
當我們每次劃分的時候選擇的基準數接近於整組資料的最大值或者最小值時,快速排序就會發生最壞的情況,但是每次選擇的基準數都接近於最大數或者最小數的概率隨著排序元素的增多就會越來越小,我們完全可以忽略這種情況。但是在陣列有序的情況下,它也會發生最壞的情況,為了避免這種情況,我們在選擇基準數的時候可以採用三數取中法來選擇基準數。
三數取中法:
選擇這組資料的第一個元素、中間的元素、最後一個元素,這三個元素裡面值居中的元素作為基準數。
程式碼實現:
#include <stdio.h> #include <iostream> using namespace std; static int i = 0; void print(int a[]) { cout<<"第"<<i++ <<"次 : "; for(int j= 0; j<10; j++) { cout<<a[j] <<" "; } cout<<endl; } void swap(int *a, int *b) { int tmp = *a; *a = *b; *b = tmp; } //優化1:三數取中 int GetMidIndex(int* a, int left, int right)//優化1:三數取中法 { int mid = left + ((right - left) >> 1);//取區間中間元素的下標 if (a[left] > a[mid])//left > mid { if (a[left] < a[right]) { //mid < left < right //swap(&a[left], &a[mid]); return left; } else//left > mid,left > right { if (a[right] > a[mid]){ //mid < right < left swap(&a[left], &a[right]); return right; } else{ //right < mid < left swap(&a[left], &a[mid]); return mid; } } } else//left < mid { if (mid < right){ //left < mid < right swap(&a[left], &a[mid]); return mid; }else //mid > left,mid > right { if (left < right){ //left < right < mid swap(&a[left], &a[right]); return right; }else{ //right < left < mid //swap(&a[left], &a[mid]); return left; } } } } int partition(int a[], int low, int high) { int privotKey = a[low]; //基準元素 while(low < high){ //從表的兩端交替地向中間掃描 while(low < high && a[high] >= privotKey) --high; //從high 所指位置向前搜尋,至多到low+1 位置。 //將比基準元素小的交換到低端 swap(&a[low], &a[high]); while(low < high && a[low] <= privotKey ) ++low; swap(&a[low], &a[high]); } print(a); return low; } void qsort_improve(int r[ ],int low, int high) { if(low < high) { //長度大於k時遞迴, k為指定的數 GetMidIndex(r, low, high); int pivot = partition(r, low, high); // 呼叫的Partition演算法保持不變 qsort_improve(r, low, pivot - 1); qsort_improve(r, pivot + 1, high); } } int main() { int H[10] = {3,1,5,7,2,4,9,6,10,8}; qsort_improve(H, 0, 9); return 0; }
執行結果:
第0次 : 2 1 3 7 5 4 9 6 10 8
第1次 : 1 2 3 7 5 4 9 6 10 8
第2次 : 1 2 3 8 5 4 7 6 9 10
第3次 : 1 2 3 4 5 6 7 8 9 10
第4次 : 1 2 3 4 5 6 7 8 9 10
第5次 : 1 2 3 4 5 6 7 8 9 10
優化二:當待排序序列的長度分割到一定大小後,使用插入排序
優化原因:對於待排序的序列長度很小或是基本趨於有序時,快排的效率還是插排好。
自定義截止範圍:序列長度N=10。當待排序的序列長度被分割到10時,採用快排而不是插排。
if (n <= 10)//當整個序列的大小n<=10時,採用插排
{
InsertSort(a, n);
}
優化三:取隨機基數
/*隨機選擇樞軸的位置,區間在low和high之間*/
int SelectPivotRandom(int arr[],int low,int high)
{
//產生樞軸的位置
srand((unsigned)time(NULL));
int pivotPos = rand()%(high - low) + low;
//把樞軸位置的元素和low位置元素互換,此時可以和普通的快排一樣呼叫劃分函式
swap(arr[pivotPos],arr[low]);
return arr[low];
}