快速排序分區以及優化方法
一、快速排序掃描分區法
通過單向掃描,雙向掃描,以及三指針分區法分別實現快速排序算法。著重理解分區的思想。
單向掃描分區法
思路:用兩個指針將數組劃分為三個區間,掃描指針(scan_pos)左邊是確認小於等於主元的,掃描指針到某個指針(next_bigger_pos)中間為未知的,因此我們將第二個指針(next_bigger_pos)稱為未知區間指針,末指針的右邊區間為確認大於主元的元素。主元就是具體的劃分數組的元素,主元的選擇有講究,這裏選擇數組的首元素
代碼:
import java.util.Arrays; public class QuickSort {public static void main(String[] args) { int arr[] = new int[10]; for(int i=0;i<10;i++){ arr[i] = (int) ((Math.random()+1)*10); } System.out.println("排序前:"+Arrays.toString(arr)); quickSort(arr, 0, arr.length-1); System.out.println("排序後:"+Arrays.toString(arr)); }public static void quickSort(int[]A,int p,int r){ if (p<r) { int q = partition(A,p,r); // 得到分區返回的一個下標 以此劃分數組 quickSort(A, p, q-1); quickSort(A, q+1, r); } } private static int partition(int[] A, int p, int r) {int pivot = A[p]; // 主元 int sp = p + 1; // 掃描指針 int bigger = r; // 右側指針 while(sp<=bigger){ if (A[sp]<=pivot) { // 掃描元素左移,左指針向右移 sp++; }else { swap(A,sp,bigger); // 掃描元素大於主元,二指針的元素交換,右指針左移 bigger--; } } swap(A,p,bigger); return bigger; } private static void swap(int[] A, int p, int bigger) { int temp = A[p]; A[p] = A[bigger]; A[bigger] = temp; } }
結果:
雙向掃描分區法
思路:頭尾指針往中間掃描,從左找到大於主元的元素,從右找到小於等於主元的元素二者交換,繼續掃描,直到左側無大元素,右側無小元素。
代碼:
import java.util.Arrays; public class QuickSort2 { public static void main(String[] args) { int arr[] = new int[10]; for(int i=0;i<10;i++){ arr[i] = (int) ((Math.random()+1)*10); } System.out.println(Arrays.toString(arr)); quickSort(arr, 0, arr.length-1); System.out.println(Arrays.toString(arr)); } public static void quickSort(int[]A,int p,int r){ if (p<r) { int q = partition2(A,p,r); // 得到分區返回的一個下標 以此劃分數組 quickSort(A, p, q-1); quickSort(A, q+1, r); } } //雙向掃描分區法 public static int partition2(int[] arr, int p, int r) { int left = p + 1; //左側掃描指針 int right = r; //右側指針 int pivot = arr[p]; while(left <= right) { // left不停往右走,直到遇到大於主元的元素 // 循環退出時,left一定是指向第一個大於主元的位置 while(left <= right && arr[left] <= pivot) { left++; } // right不停往左走,直到遇到小於主元的元素 // 循環退出時,right一定是指向從右到左第一個小於於主元的位置 while(left <= right && arr[right] > pivot) { right--; } if(left < right) swap(arr, left, right); } // 循環退出時,兩者交錯,且right指向的最後一個小於等於主元的位置,也就是主元應該待的位置 swap(arr, p, right); return right; } private static void swap(int[] A, int p, int bigger) { int temp = A[p]; A[p] = A[bigger]; A[bigger] = temp; } }
結果:
三指針分區法
思路:當待排序數組中,如果有大量相同的元素,則可以三指針分區法,每次將與主元相等的元素找到,排好序,並記錄這組與主元相等元素序列的開始下標和結束下標。在進行下次遞歸排序時,排除這部分相同的元素。從而減少遞歸次數。
代碼:
import java.util.Arrays; public class QuickSort3 { public static void main(String[] args) { int arr[] = new int[10]; for(int i=0;i<10;i++){ arr[i] = (int) ((Math.random()+1)*10); } System.out.println("排序前:"+Arrays.toString(arr)); quickSort(arr, 0, arr.length-1); System.out.println("排序後:"+Arrays.toString(arr)); } public static void quickSort(int[]A,int p,int r){ if (p<r) { int q[] = partition3(A,p,r); // 得到分區返回的一個下標 以此劃分數組 quickSort(A, p, q[0]-1); quickSort(A, q[1]+1, r); } } private static int[] partition3(int[] arr, int p, int r) { int s = p + 1; //左掃描指針 int e = s; //記錄與主元相等元素序列的開始下標 int bigger = r; //右側掃描指針 int pivot = arr[p]; //主元 while (s <= bigger) { while(s <= bigger && arr[s] <= pivot) { //當從一開始沒有找到與主語相等的元素,且都小於主元時,指針右移 if(s <= bigger && s == e && arr[s] < pivot) { s++; e++; } //如過s!=e時,說明已經找到與主元相等的元素,且e記錄的為與主元相等元素的開始下標 //如果下一個元素小於主元,則將小於主元的元素和與主元相等序列的第一個元素交換位置 if(s <= bigger && s != e && arr[s] < pivot) { swap(arr, s, e); e++; s++; } //如果遇到等於主元的元素,左掃描指針++, 記錄與主元相等序列的開始下標e不變 if(s <= bigger && arr[s] == pivot) { s++; } } //右側掃描指針 while(s <= bigger && arr[bigger] > pivot) { bigger--; } //將左側指針指向大的元素與右側小於主元的元素交換 if(s <= bigger && arr[s] > arr[bigger]) { swap(arr, s, bigger); } } //最後,數組下標為p的開始元素,和與主元相等序列的前一個元素交換,e-- swap(arr, p, --e); //返回與主元相等序列的開始下標和結束下標 int[] q = {e, bigger}; return q; } private static void swap(int[] A, int p, int bigger) { int temp = A[p]; A[p] = A[bigger]; A[bigger] = temp; } }
結果:
二、快速排序在工程實踐中優化方法
1、三點中值法:Arrays.sort()方法就是采用的三點中值法。在上面的例子中,每次取的主元都是待排序子序列的首元素,很大可能不是屬於中間的元素,從而容易加大遞歸的層數。三點中值法就是對待排序數組的開始,中間,最後三個元素的大小進行比較,然後取中間值,這樣很大概率能使主元成為中間的元素,從而減少遞歸層數。
2、絕對中值法:三點中值法也有很大的隨機性,如果想要得到絕對的中值,可以通過絕對中值法來獲取主元,通過將待排序數組以5個元素分為一組,取中間值,取到整個數組的各組中間值,再將這些數排序,再取中間值作為主元。因為尋找絕對中值,也會花費時間,所以使用三點中值法居多。
3、待排序列表較短時,用插入排序:當排序列表小於8個時,通過計算發現插入排序比快速排序的性能要好。
快速排序分區以及優化方法