快速排序的遞歸解法(C++實現)

快速的排序在這裏討論兩種常規解法和一種考慮數組中有相同值情況時的簡便解法。
首先來看常規解法一:
參考快速排序—(面試碰到過好幾次)
如下圖所示,假設要給由4個元素7,13,5,9組成的數組進行排序。最開始的基準數據爲數組第一個元素7。首先用一個臨時變量tmp來存儲基準數據,然後分別從數組的兩端掃描整個數組,設兩個指示標誌:L指向左邊的起始位置,R指向右邊的末尾位置。
在這裏插入圖片描述
首先,從末尾位置開始,如果掃描到的值大於基準數據就讓R減一,即往前移動一個位置。
賦值之前
如果發現有元素比該基準數據的值小,比如下一個數5比7小,就將R位置的值賦給L位置,結果如下:
賦值之後
賦值結束之後,轉爲從前往後掃描,如果掃描到的值小於等於基準數據,就讓L加1,即往後移動一個位置。如果掃描到的值大於基準數據,將L位置的元素賦值給R位置的元素,結果如下:
在這裏插入圖片描述
接着轉爲從後往前掃描,發現L=R,結束循環。
在這裏插入圖片描述
此時L或R的下標就是基準數據7。
在這裏插入圖片描述
上述演示的是隻有四個元素數組的排序過程,如果有更多元素的話,過程如下:

  1. 先從隊尾開始掃描且當L<R時,如果a[R] > tmp,則R–,但如果a[R] < tmp,則將a[R]的值賦給a[L],即a[R]= a[L],同時要轉換數組掃描的方式,即需要從隊首開始向隊尾掃描了。
  2. 先從隊首開始掃描且當L<R時,如果a[L] < tmp,則L++,但如果a[L] > tmp,則將a[L]的值賦給a[R],即a[L]= a[R],同時要轉換數組掃描的方式,即需要從隊尾開始向隊首掃描了。
  3. 不斷重複1和2,知道L=R,L或R的位置就是基準數據在數組中的位置。

代碼C++實現如下:

#include <iostream>
using namespace std;
int partition1(int arr[], int L, int R) {
	int num = arr[L];
	while(L < R) {
		while(L < R && arr[R] > num) {
			R--;
		}
		arr[L] = arr[R];
		while(L < R && arr[L] <=num) {
			L++;
		}
		arr[R] = arr[L];
	}
	arr[L] = num;
	return L;
}
int quickSort(int arr[], int L, int R) {
	if(L < R) {
		int less = partition1(arr, L, R);
		quickSort(arr, L ,less-1);
		quickSort(arr, less + 1, R);
	}
}
int main()
{
	int arr[] = {8, 4, 1, 5, 2, 3, 7, 0, 6};
	int len = sizeof(arr)/sizeof(arr[0]);
 	quickSort(arr, 0 , len-1);
	for(int i = 0; i < len; i++) {
		cout<<arr[i]<<endl;
	}
	return 0;
 } 

其中如果將賦值語句改爲交換,就不需要在partition的最後將基準元素賦值給arr[L],小修改之後的程序如下:

#include <iostream>
using namespace std;
void swap(int arr[], int a, int b)
{
	if(a == b) {
		return ;	
	}
	int c = arr[a];
	arr[a] = arr[b];
	arr[b] = c;
}
int partition1(int arr[], int L, int R) {
	int num = arr[L];
	while(L < R) {
		while(L < R && arr[R] > num) {
			R--;
		}
		swap(arr, L, R); 
		//arr[L] = arr[R];
		while(L < R && arr[L] <=num) {
			L++;
		}
		//arr[R] = arr[L];
		swap(arr, L, R);
	}
	//arr[L] = num;
	return L;
}
int quickSort(int arr[], int L, int R) {
	if(L < R) {
		int less = partition1(arr, L, R);
		quickSort(arr, L ,less-1);
		quickSort(arr, less + 1, R);
	}
}
int main()
{
	int arr[] = {8, 4, 1, 5, 2, 3, 7, 0, 6};
	int len = sizeof(arr)/sizeof(arr[0]);
 	quickSort(arr, 0 , len-1);
	for(int i = 0; i < len; i++) {
		cout<<arr[i]<<endl;
	}
	return 0;
 } 

下面來看常規解法二:
在這裏引入小於等於區的概念,less是小於等於區的右邊界。假設要給包含21、45、1、9、12、19這五個元素的數組arr進行排序。首先用一個臨時變量p存儲基準數據:p = arr[R]。less的初始值爲L-1,因爲初始狀態下默認所有的數都在小於等於區右邊界的右面。此時L指向數組中的第一個元素,判斷該元素與基準數據之間的大小關係,如果arr[L]>p,則將L繼續向右移動,不做其他操作。
在這裏插入圖片描述
當L指向數組中第二個元素,發現arr[L]>p,繼續向下移動,不做其他操作。
在這裏插入圖片描述
當L指向數組中第三個元素時, 發現arr[L]<p,將arr[L]與arr[less++]進行交換,結果如下:
在這裏插入圖片描述
當L指向數組中第四個元素時,發現arr[L]<p,將arr[L]與arr[less++]進行交換,結果如下:
在這裏插入圖片描述
當L指向數組中第五個元素時,發現arr[L]<p,將arr[L]與arr[less++]進行交換,結果如下:
在這裏插入圖片描述
當L指向數組中第六個元素時,此時已經到達了數組的末端,arr[L]>p,這時候如果將arr[L]與arr[less++]交換之後,就可以的達到如下所示的數組:
在這裏插入圖片描述
此時,arr[less]就是小於等於區的有右邊界,它的右邊就是大於區,從而實現了把基準數大的都放在基準數的右邊,把比基準數小的放在基準數的左邊。以後採用遞歸的方式分別對前半部分和後半部分排序,當前半部分和後半部分均有序時該數組就自然有序。
代碼C++實現如下:

#include <iostream>
using namespace std;
void swap(int arr[], int a, int b)
{
	if(a == b) {
		return ;	
	}
	int c = arr[a];
	arr[a] = arr[b];
	arr[b] = c;
}
int partition1(int arr[], int L, int R) {
	int p = arr[R];
	int less = L - 1;
	for(int i = L; i <= R; i++) {
		if (arr[i] < p) {
			swap(arr, ++less, i);
		}
	}
	swap(arr, ++less, R);
	return less;
}
int quickSort(int arr[], int L, int R) {
	if(L < R) {
		int less = partition1(arr, L, R);
		quickSort(arr, L ,less-1);
		quickSort(arr, less + 1, R);
	}
}
int main()
{
	int arr[] = {8, 4, 3, 1, 5, 2, 3,3, 7, 0, 6};
	int len = sizeof(arr)/sizeof(arr[0]);
 	quickSort(arr, 0 , len-1);
	for(int i = 0; i < len; i++) {
		cout<<arr[i]<<endl;
	}
	return 0;
 } 

最後來看對數組中相同元素的特殊考慮
在劃分的過程,這種算法將數組元素分爲小於區、等於區和大於區。
比如0,4,5,4,3,6,4這幾個數,以最後一個數4作爲劃分值,小於區的右邊界是整個數組的前一個數,小於區剛開始是沒有數的,大於區剛開始只存入最後一個數4,雖然這個數並不符合要求,但是暫且先將它放在這個區域。用less來標記小於區的右邊界,more來標記大於區的左邊界,示意圖如下:
在這裏插入圖片描述
L指向數組中左邊的數,arr[L]<arr[R],小於區的下一個數和當前數交換,小於區指向的是數組的前一個數,當前數是0,所以就是自己和自己交換。
在這裏插入圖片描述
接着遍歷下一個數,此時arr[L]=arr[R],直接跳到下一個,此時arr[L]指向的元素是6,大於arr[R],將arr[L]與大於區的前一個數6進行交換,大於區前移,如下圖所示:
在這裏插入圖片描述
交換完後,下標L繼續停留在原位置,看它是大於還是小於劃分值。6大於劃分值,將6和3交換,數組就變成了0,4,3,4,6,5,4。大於區域再向左擴一個位置,擴完之後,這個下標停在原位置。
在這裏插入圖片描述
此時arr[L]的值爲3,小於4,和小於區(less)下一個元素進行交換,數組就變成了0,3,4,4,6,5,4,小於區向右擴一個位置,當前數挪到下一個。
在這裏插入圖片描述
繼續向下走,L=more,滿足了while循環結束的條件。
在這裏插入圖片描述
此時發現大於區最右的數和最左的數是不對的,所以將arr[more]與arr[R]兩個元素進行交換。
在這裏插入圖片描述
總結: 當前數小於劃分值和當前數等於劃分值時,下標都是往下跳的,唯獨當前數大於劃分值時,下標留在原地。下一個數是4,到達了大於區的左邊界,就停下來。大於區的最右的數和最左的數位置是不對的,所以將這兩個數交換。
C++程序代碼實現如下:

#include <iostream>
using namespace std;
void swap(int arr[], int a, int b)
{
	if(a == b) {
		return ;	
	}
	int c = arr[a];
	arr[a] = arr[b];
	arr[b] = c;
}
int data[2] = {0};
int* partition1(int arr[], int L, int R) {
	int less = L - 1;
	int more = R;
	while(L < more) {
		if (arr[L] < arr[R]) {
			swap(arr, ++less, L++);
		} else if (arr[L] > arr[R]) {
			swap(arr, --more, L);
		} else {
			L++;
		}
	}
	swap(arr, more, R);
	data[0] = less + 1;
	data[1] = more;
	return data;
}

int quickSort(int arr[], int L, int R) {
	if(L < R) {
		int *record = partition1(arr, L, R);
		quickSort(arr, L , record[0]-1);
		quickSort(arr, record[1] + 1, R);
	}
}
int main()
{
	int arr[] = {0, 3, 4, 4, 6, 5, 4};
	int len = sizeof(arr)/sizeof(arr[0]);
 	quickSort(arr, 0 , len-1);
	for(int i = 0; i < len; i++) {
		cout<<arr[i]<<endl;
	}
	return 0;
 } 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章