快速排序遞迴與非遞迴演算法
阿新 • • 發佈:2019-02-08
快速排序是不穩定的,是對氣泡排序的改進。
它的改進之處在於每輪會使一個基數歸位,同時可以使基數兩邊的兩組數基本有序(基數左邊的數都小於基數,基數右邊的數都大於基數)
它的平均時間複雜度O(nlogn),最壞時間複雜度就是退化成氣泡排序O(n^2)
思路
無論是遞迴還是非遞迴,都需要給基數歸位,那麼基數怎樣歸位呢?
首先是選取基數(一般選取陣列第一個或者是最後一個,這樣方便計算)。然後從陣列最右端依次向左端搜尋,當遇到第一個小於基數的數時停下,再從陣列最左端向右搜尋,遇到第一個大於基數的數時停下,這時再交換這個兩個逆序的數,就可以使得兩邊基本有序了。一直這樣做直至左邊的指標等於右邊的指標(此時指標所指位置就是基數應該位於陣列的裡的位置)。
下面給出C語言遞迴程式碼
#include<stdio.h> #define n 10 //快速快速 //思想是跳著交換兩個位置,每次遍歷都會使一個基數歸位 //升序排列。 void quickSort(int a[],int left,int right); int main(void){ int num[n]={20,38,84,23,6,17,49,12,37,21}; //原始未排序數列 //quick sort quickSort(num,0,n-1); //遞迴實現快速排序 int i; for(i=0;i<n;i++){ printf("%d\n",num[i]); } return 0; } void quickSort(int a[],int left,int right){ int i,j,temp,t; if(left+1>right) return; //每輪完成的是left位置的基數歸位 temp=a[left]; i=left;j=right; while(i<j){ //歸為基數 while(a[j]>=temp&&i<j){ //從基數位右邊把小於基數的數與左邊互換位置完成分組 j--; }while(a[i]<=temp&&i<j){ i++; } if(i<j){ //左右互換 t=a[i]; a[i]=a[j]; a[j]=t; } } a[left]=a[i]; a[i]= temp; quickSort(a,left,i-1); //遞迴分完組的左邊 quickSort(a,i+1,right); // 遞迴分完組的右邊 }
其實按照思路來寫,並不是很難。
那麼非遞迴演算法又該如何寫呢。
因為要用到stl容器,下面給出C++程式碼
#include <iostream> #include <algorithm> #include <stack> #include <queue> #define n 10 using namespace std; int main(void){ int num[n] = {4,7,1,2,9,0,4,6,11,3}; // stack<int> st; // st.push(0); // st.push(n-1); // while(!st.empty()){ // int end = st.top(); // st.pop(); // int start = st.top(); // st.pop(); // int index = num[start]; // int i = start ; // int j = end; // while (i<j){ // while(i<j&&num[j]>=index){ // j--; // } // while(i<j&&num[i]<=index){ // i++; // } // if(i<j){ // int temp=num[i]; // num[i]=num[j]; // num[j]=temp; // } // } // int temp = num[i]; // num[i]=num[start]; // num[start] = temp; // if(start<i-1){ // st.push(start); // st.push(i-1); // }if(i+1<end){ // st.push(i+1); // st.push(end); // } // } // queue<int> q; //無論是佇列還是棧,都是來儲存陣列下標的。 q.push(0); //將最左端的下標加入佇列 q.push(n-1); //將最右端的下標加入佇列 while(!q.empty()){ //可以把快速排序的非遞迴演算法想象成廣度優先搜素 int left = q.front(); q.pop(); int right = q.front(); q.pop(); int i = left; int j = right; int index = num[left]; //選擇最左端的數作為基數 while(i<j){ while(i<j&&num[j]>=index){ //從右向左找一個比基數小的數 j--; }while(i<j&&num[i]<=index){ //從左向右找一個比基數大的數 i++; } if(i<j){ int temp = num[i]; num[i]= num[j]; num[j] = temp; } } int temp = num[left]; //將基數歸為 num[left] = num[i]; num[i] = temp; if(left<i-1){ //這就相當於遞迴。 就是把分組的陣列上下界加入佇列 q.push(left); q.push(i-1); }if(right>i+1){ q.push(i+1); q.push(right); } } for(int i=0;i<n;i++){ cout<<num[i]<<endl; } return 0; }
可以看到註釋裡的是用棧寫的快速排序非遞迴演算法,沒註釋的是用佇列寫的
其實也可以用陣列寫,這都不是重點。它們只是容器用來記錄陣列分組界限的下標的。