1. 程式人生 > >快速排序遞迴與非遞迴演算法

快速排序遞迴與非遞迴演算法

快速排序是不穩定的,是對氣泡排序的改進。

它的改進之處在於每輪會使一個基數歸位,同時可以使基數兩邊的兩組數基本有序(基數左邊的數都小於基數,基數右邊的數都大於基數)

它的平均時間複雜度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;
}

可以看到註釋裡的是用棧寫的快速排序非遞迴演算法,沒註釋的是用佇列寫的

其實也可以用陣列寫,這都不是重點。它們只是容器用來記錄陣列分組界限的下標的。