1. 程式人生 > 其它 >快速排序:挖坑法、前後指標法和三數取中優化

快速排序:挖坑法、前後指標法和三數取中優化

技術標籤:資料結構

上一篇部落格中,我們看過了快速排序的基本劃分方法——霍爾法的實現。事實上,區間劃分也可以通過挖坑法和前後指標法來完成,從而實現快速排序。那麼本篇部落格,我們來講解這兩種方法。


一、挖坑法

顧名思義,此方法的思路是將位置空出來(存入臨時變數),再將找到的資料填入這個空白位置。如下圖:
在這裡插入圖片描述
可以看到,利用end和begin尋找比基準值小和大的元素的思路不變,在尋找到之後,直接將找到的元素“填入”“坑”中,最後再將基準值填入,就完成了一趟排序。程式碼如下:

int partition2(int* arr, int begin, int end){ //挖坑法
	int key =
arr[begin]; while (begin < end){ while (begin < end && arr[end] >= key){ end--; } arr[begin] = arr[end]; while (begin < end && arr[begin] <= key){ begin++; } arr[end] = arr[begin]; } arr[begin] = key; return begin; }

二、前後指標法

前後指標法的大體思路為:設定一個prev指標和一個cur指標,這兩個指標初始時是相鄰的。它們同時向後掃描,若cur找到大於基準值的元素,則prev停止移動,cur繼續向後移動,直到找到下一個小於基準值的元素。找到此元素之後,交換prev和cur指標所在元素。交換結束後cur繼續向後掃描。重複此過程,直到cur掃描到陣列末尾為止。簡單來說就是:若prev和cur不相鄰,且cur所在位置的值小於基準值,則交換cur和prev位置的值。示意圖如下:

在這裡插入圖片描述
程式碼如下:

int partition3(int* arr, int begin, int end){ //前後指標法
	int key = arr[begin];
	int prev = begin;
	int cur = prev + 1;

	while (cur <= end){
		if (arr[cur] < key && ++prev != cur){	//利用了C語言的邏輯運算短路特性
			Swap(&arr[prev], &arr[cur]);		//若第一個條件不滿足,則第二個條件不運算
		}										//短路時prev不變
		cur++
; } Swap(&arr[begin], &arr[prev]); return prev; }

三、優化——三數取中法

快速排序是一種不穩定的排序方法,平均時間複雜度為O(nlogn),空間複雜度O(logn)。然而,當序列基本有序時,快速排序會逐漸退化成氣泡排序,時間複雜度降為O(n^2)。也就是說,快排對於雜亂無序的序列效率最高,對基本有序的序列效率最低。
因此,我們需要使用一種優化的方法。

本段敘述參考:https://blog.csdn.net/qq_44819750/article/details/106133915
在首,中,尾這三個資料中,選擇一個值的大小處於中間的資料作為基準值,進行快速排序,即可進一步提高快速排序的效率。那麼為什麼要取中間呢?我們可以假設待排序的數列是一組高度有序的數列,顯然首極大可能是最小值,尾極大可能是最大值,此時如果我們選取一個排在中間的值,哪怕是在最壞的情況下,begin和end只需要走到中間位置,那麼這個中間值的位置也就確定下來,而不需要begin或end指標要把整個數列遍歷一邊,從而大大提高快速排序的效率。

三數取中法程式碼如下:

int getMid(int* arr, int begin, int end){
	int mid = begin + (end - begin) / 2;
	if (arr[begin] > arr[mid]){
		if (arr[mid] > arr[end]){
			return mid;
		}
		else if (arr[begin] > arr[end]){
			return end;
		}
		else{
			return begin;
		}
	}
	else{
		if (arr[mid] < arr[end]){
			return mid;
		}
		else if (arr[begin] < arr[end]){
			return end;
		}
		else{
			return begin;
		}
	}
}

在劃分函式中呼叫此函式即可:

int partion3(int* arr, int begin, int end){ //前後指標法
	int mid = getMid(arr, begin, end);	//取得中間值
	Swap(&arr[mid], &arr[begin]);		//交換中間值和序列首元素的位置

	int key = arr[begin];
	int prev = begin;
	int cur = prev + 1;

	while (cur <= end){
		if (arr[cur] < key && ++prev != cur){
			Swap(&arr[prev], &arr[cur]);
		}
		cur++;
	}
	Swap(&arr[begin], &arr[prev]);
	return prev;
}

void insertSort(int* arr, int n){
	for (int i = 1; i < n; i++){
		int val = arr[i];
		int end = i - 1;
		for (; end >= 0 && arr[end] > val; end--){
			arr[end + 1] = arr[end];
		}
		arr[end + 1] = val;
	}
}

四、小區間優化

當需要排序的元素個數很少時,插入排序比快速排序效率更高。因此,直接呼叫插入排序即可:

void quickSort2(int* arr, int begin, int end){
	if (begin >= end){
		return;
	}
	if (end - begin <= 10){	//小區間優化
		insertSort(arr + begin, end - begin + 1);
	}
	else{
		int div = partion3(arr, begin, end);
		quickSort(arr, begin, div - 1);
		quickSort(arr, div + 1, end);
	}
}