快速排序:挖坑法、前後指標法和三數取中優化
技術標籤:資料結構
上一篇部落格中,我們看過了快速排序的基本劃分方法——霍爾法的實現。事實上,區間劃分也可以通過挖坑法和前後指標法來完成,從而實現快速排序。那麼本篇部落格,我們來講解這兩種方法。
一、挖坑法
顧名思義,此方法的思路是將位置空出來(存入臨時變數),再將找到的資料填入這個空白位置。如下圖:
可以看到,利用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);
}
}