快排確實厲害!!!
總的思想是分治遞歸,取定一個值作為標簽,比該值小的去左邊,比該值大的去右邊。
單向掃描分區法:
去左邊的操作:只將sp++即可。
去右邊的操作:具體是將sp指向的值與bigger指向的值交換。
考慮邊界:當掃描指針sp與bigger相等時,再執行一次循環后,sp剛好在bigger的右邊一格。
1 /* 2 * 快速排序算法 3 */ 4 void swp(int arr[], int n, int m) 5 { 6 int temp = arr[n]; 7 arr[n] = arr[m]; 8 arr[m] = temp; 9 } 10 int Part(int arr[], int p, int r) 11 { 12 int pivot = arr[p]; // 定中間數 13 int sp = p + 1; // 掃描指針 14 int bigger = r; // 右側指針 15 while (sp <= bigger) 16 { 17 if (arr[sp] > pivot) { 18 swp(arr, sp, bigger); 19 bigger--; 20 } 21 else 22 sp++; 23 } 24 swp(arr, p, bigger); 25 return bigger; 26 } 27 void quickSort(int arr[], int p, int r) 28 { 29 int q = 0; 30 if (p < r) { 31 // 分區:小於的數移動到左邊,大於的數移動到右邊 32 q = Part(arr, p, r); 33 // 將排序問題分治 34 quickSort(arr, p, q - 1); 35 quickSort(arr, q + 1, r); 36 } 37 } 38 int main() 39 { 40 int ar[2] = {1, -1}; 41 quickSort(ar, 0, 1); 42 // 打印 43 for (int i = 0; i <= 1; i++) 44 cout << ar[i] << endl; 45 return 0; 46 }
雙向掃描分區法:
與單向掃描分區類似,但left指針一直往右移,直到大於中間值時停止;right指針一直往左移,直到小於中間值時停止。然后left值與right值交換。之后left繼續一直左移,right一直右移。重復執行。
小心:left一直左移(或right一直右移)導致的數組越界問題。
1 /* 2 * 快速排序算法 3 */ 4 void swp(int arr[], int n, int m) 5 { 6 int temp = arr[n]; 7 arr[n] = arr[m]; 8 arr[m] = temp; 9 } 10 int Part(int arr[], int p, int r) 11 { 12 int pivot = arr[p]; // 定中間數 13 int left = p + 1; // 左指針 14 int right = r; // 右指針 15 // 雙向掃描核心 16 while (left <= right) 17 { 18 while (left <= right && arr[left] <= pivot) left++; 19 while (left <= right && arr[right] > pivot) right--; 20 if (left < right) 21 swp(arr, left, right); 22 } 23 swp(arr, p, right); 24 return right; 25 } 26 void quickSort(int arr[], int p, int r) 27 { 28 int q = 0; 29 if (p < r) { 30 // 分區:小於等於的數移動到左邊,大於的數移動到右邊 31 q = Part(arr, p, r); 32 // 將排序問題分治 33 quickSort(arr, p, q - 1); 34 quickSort(arr, q + 1, r); 35 } 36 } 37 int main() 38 { 39 int ar[8] = {2, 3, 3, 3, 45, 8, 4, 6}; 40 quickSort(ar, 0, 7); 41 // 打印 42 for (int i = 0; i <= 7; i++) 43 cout << ar[i] << " "; 44 return 0; 45 }
三指針分區法:
三指針分區法對於相等元素較多的數組能提升一定的效率。
Next_Less_Pos指針始終指向數組的相等區第一個元素;Next_Scan_Pos指針始終指向要掃描區域的第一個元素;Next_Bigger_Pos指針的右區域所有元素總大於主元。
1 /* 2 * 快速排序算法 3 */ 4 void swp(int arr[], int n, int m) 5 { 6 int temp = arr[n]; 7 arr[n] = arr[m]; 8 arr[m] = temp; 9 } 10 void Part(int arr[], int p, int &e, int &bigger) 11 { 12 int pivot = arr[p]; // 定中間數 13 int s = e; 14 // 三指針分區核心 15 while (s <= bigger) { 16 if (arr[s] < pivot) { 17 swp(arr, s, e); 18 s++; e++; 19 } 20 else if (arr[s] > pivot) { 21 swp(arr, s, bigger); 22 bigger--; 23 } 24 else s++; 25 } 26 swp(arr, e - 1, p); 27 } 28 void quickSort(int arr[], int p, int r) 29 { 30 int left = p + 1, right = r; 31 if (p < r) { 32 // 分區:小於的數移動到左邊,大於的數移動到右邊,等於的數在中間 33 Part(arr, p, left, right); 34 // 將排序問題分治 35 if (left > p) quickSort(arr, p, left - 1); 36 if (right < r) quickSort(arr, right + 1, r); 37 } 38 } 39 int main() 40 { 41 int ar[20]; 42 srand((unsigned)time(nullptr)); 43 for (int i = 0; i <= 19; i++) 44 ar[i] = rand() % (10 - 1 + 1) + 1; 45 quickSort(ar, 0, 19); 46 // 打印 47 for (int i = 0; i <= 19; i++) 48 cout << ar[i] << " "; 49 return 0; 50 }
小心邊界:必須是相等區域的前一個小於元素,和相等區域的后一個大於元素(即 left - 1 和 right + 1),這時能避免無限循環。
但在避免無限循環的同時,又得保證邊界下標的合理性,故我們此時加 if 語句判斷。
補充 工程實踐中的一些優化:
- 三點中值法(去首元素、尾元素和中間元素,取三元素的中間值作為主元)
- 絕對中值法(花費 O(n) 的時間,尋找中間值作為主元)
- 部分插入排序(在遞歸樹中,對於元素較少的部分直接用插入排序)