1. 程式人生 > >快速排序分區以及優化方法

快速排序分區以及優化方法

技術分享 右移 bsp 發現 優化方法 相等 技術 uic 說明

一、快速排序掃描分區法

  通過單向掃描,雙向掃描,以及三指針分區法分別實現快速排序算法。著重理解分區的思想。

  單向掃描分區法

    思路:用兩個指針將數組劃分為三個區間,掃描指針(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個時,通過計算發現插入排序比快速排序的性能要好。

快速排序分區以及優化方法