[算法]——快速排序(Quick Sort)


顧名思義,快速排序(quick sort)速度十分快,時間復雜度為O(nlogn)。雖然從此角度講,也有很多排序算法如歸並排序、堆排序甚至希爾排序等,都能達到如此快速,但是快速排序使用更加廣泛,以至於STL中默認排序方法就是快速排序。此外,快速排序的思想——划分(Partition)思想給人很多啟發。下面以非降序排序進行介紹,不求有更深的理解,只求為自己做個簡要筆記。

1)划分(Partition)

划分思想十分簡單,卻又十分重要,應用廣泛。即:將待排序數組以某一個元素為鍵值(Key),將比此key小的放在左邊,否則放在右邊。其可能的情況為:3,1,4,2,0,KEY=5,6,9,7,8.鍵值Key可以任意選擇。好的鍵值對於提高性能有幫助,但選擇最優鍵值的方法,本身就是一種排序思想。所以,對於排序來說,一般選擇第一個元素作為Key。以下為Partition代碼的一種實現:

// Partition - <small> KEY <big>
int partition(int arr[], int begin, int end)
{
	int i=begin, j=end-1, key;
	if (i>=j) return i;
	for (key=arr[i]; i<j;){
		// find last arr[j]<key
		for(; i<j && arr[j]>=key; --j);
		arr[i] = arr[j];
		// find first arr[i]>key
		for(; i<j && arr[i]<=key; ++i);
		arr[j] = arr[i];
	}
	arr[i] = key;
	return i;		// current key position
}	// Time O(n)
// after partition, the KEY must be the position where ordered

首先定義兩個下標變量i,j,分別指向開始和結尾,並將開始begin的元素作為鍵值key。然后從后向前遍歷arr[],找到小於key的元素j,將其覆蓋i;從前向后遍歷arr[],找到大於key的元素i,將其覆蓋j。直到i與j碰頭,將key寫回i。此時i的位置即選中的key應該在的位置,即:key左邊的元素不大於key,key右邊的不小於key。返回此時key的下標位置i。其時間復雜度為O(n).

2)快速排序(quick sort)

快速排序就是利用划分思想。每次經過划分之后,其key所在位置(下標)必然是經過排序后key所在的位置!如上面的3,1,4,2,0,KEY=5,6,9,7,8。元素5此時在位置5,正是其排序后的位置。再如:3,5,1,KEY=10,23,19.元素10在位置3,也是其排完序后所在的位置。正是如此,利用划分進行快速排序才成為可能。

快速排序思想為:對於數組arr[]以及其首位元素位置begin和末尾元素end,選擇其中一個元素作為Key,進行一趟划分,得到key此時應該在的位置i;然后對於key的左部分begin~i-1進行划分,對於key右部分i+1~end進行划分;逐漸划分更小的范圍進行划分。最終排序完畢。每次進二分划分,一共划分了logn次,故快速排序時間復雜度為O(nlogn)。

// quick sort, ascending order
void quick_sort(int arr[], int begin, int end)
{
	int mid = partition(arr,begin,end);
	if (mid<0) return;
	quick_sort(arr,begin,mid);
	quick_sort(arr,mid+1,end);
}

一種寫法更加巧妙的快速排序,將quick_sort與partition結合,如下:

// A simple quick sort version, which combine partition and sort
void quick_sort(int arr[], int begin, int end)
{
    int i=begin, j=end-1, key;
    if (i>=j) return;
    for(key=arr[i]; i<j;){
        for(; i<j && arr[j]>=key; --j);
        arr[i] = arr[j];
        for(; i<j && arr[i]<=key; ++i);
        arr[j] = arr[i];
    }
    arr[i] = key;
    quick_sort(arr,begin,i);
    quick_sort(arr,i+1,end);
}

對於長度為N的數組arr[]而言,快速排序只需調用quick_sort(arr,0,N)。

3)鏈表的快速排序

鏈表的排序方法也有很多,此處使用快速排序對單鏈表進行排序。其中鏈表節點定義如下:

// A simple quick sort version, which combine partition and sort
// A simple ListNode define
typedef struct __ListNode
{
	int val;
	struct __ListNode *next;
}ListNode;

由於數組進行划分時,其元素進行移動很費時,然而對於鏈表而言,其元素划分后,並不需要移動,只需要指針交換即可。所以,定義兩個指針mid和p,p進行快速向后遍歷,當遇到小於KEY的節點時,將其加入到mid尾部並且mid后移一位。執行一趟partition后,p到達尾部NULL,mid為KEY的位置,然后繼續划分begin~mid以及mid~end。具體過程見下面:

/*  quick sort - list version (ascending order)
 *  5 -> 3 -> 2 -> 7 -> 8 -> 1 -> 9 ->NULL
 *  |__begin: as KEY                   |__end
 *  |__mid  : as sorted list mid
 *
 *       p1   p2
 *  5 -> 3 -> 2 -> 7 -> 8 -> 1 -> 9 ->NULL
 *  |__begin
 *       |__mid (1)
 *            |__mid (2)
 *                 p3   p4
 *  5 -> 3 -> 2 -> 7 -> 8 -> 1 -> 9 ->NULL
 *  |__begin  |
 *            |__mid (2)
 *                           p5
 *  5 -> 3 -> 2 -> 7 -> 8 -> 1 -> 9 ->NULL
 *  |__begin  |
 *            |__mid (2): move mid, and swap(mid,p)
 *                           ||
 *                           p5
 *  5 -> 3 -> 2 -> 1 -> 8 -> 7 -> 9 ->NULL
 *  |__begin       |
 *                 |__mid (3)
 *
 *  at last, swap begin and mid:
 *  1 -> 3 -> 2 -> 5 -> 8 -> 7 -> 9 ->NULL
 *  |__begin       |
 *                 |__mid
 *  
 * */

其代碼如下:

// quick sort by ascending order for list
void qsort(ListNode *begin, ListNode *end)
{
	if (begin==end || begin==NULL) return;
	ListNode *p, *mid;
	for(mid=begin, p=mid->next; p!=end; p=p->next){
		if (p->val > begin->val) continue;
		mid = mid->next;
		if (mid!=p) swap(p->val,mid->val);
	}
	swap(begin->val,mid->val);
	qsort(begin,mid);
	qsort(mid->next,end);
}

對於一個無頭節點的單鏈表ListNode *head而言,進行快速排序只需調用qsort(head,NULL);即可。此處已將parition合並到快速排序中,並沒有單獨給出Partition,如需要請自行寫出。

4) 第K小的數字(Kth-smallest number)

划分可以不用完全排序,就可以求一個數組中第k小(或第k大)的數字。

例如給定一個數組{5,2,3,1,4},求其第2小的數字。如果排序,則很容易找到其答案為2,但使用的時間為O(nlogn)。在划分時,如果當前返回的key的位置pos正是k,則可以直接找到答案,而不必完全排序。

// Kth-smallest number, partition by ascending order
int kth_smallest(int k, int arr[], int n) {
	int i=0, j=n, pos=-1;
	for(--k; pos!=k;){
		pos = partition(arr,i,j);
		i = (pos<k ? pos+1 : i);
		j = (pos>k ? pos : j);
	}
	return arr[k];
}

類似的題目可參見第k大元素

注:本文涉及的源碼:https://git.oschina.net/eudiwffe/codingstudy/blob/master/src/sort/quicksort.c


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM