快速排序(面試必備,多種解法)

###1.原理/思路
快速排序一般基於遞歸實現。其思路是這樣的:
1.選定一個合適的值(理想情況中值最好,但實現中一般使用數組第一個值),稱爲“樞軸”(pivot)。
2.基於這個值,將數組分爲兩部分,較小的分在左邊,較大的分在右邊。
3.可以肯定,如此一輪下來,這個樞軸的位置一定在最終位置上。
4.對兩個子數組分別重複上述過程,直到每個數組只有一個元素。
5.排序完成。

###2.分析做法
1.附設兩個指針left和right,初始化時分別指向數組的下界和上界,設樞軸關鍵字爲pivotloc
(第一趟 left = 0 ,right = arr.length - 1)
2.從表的最右側位置,依次向左搜索找到第一個關鍵字小於pivotloc的記錄和樞軸記錄交換。具體操作:當left<right時,
如果arr[right]的值大於等於pivotloc,則向左移動指針right(–right);否則將arr[right]與樞軸記錄交換。
3.從表的最左側位置,依次向右搜索找到第一個關鍵字大於pivotloc的記錄和樞軸記錄交換。具體操作:當left<right時,
如果arr[left]的值小於等於pivotloc,則向右移動指針left(–left);否則將arr[left]與樞軸記錄交換。
4.重複步驟2 和3,直到left和right相等爲止,此時left或right的位置即爲樞軸在此排序中的最終位置,原表被分成兩個字表。

###3.代碼實現

#####1. 普通快速排序實現

public void quickSort(int[] arr, int l, int r) {
    if (l >= r) return;
    int p = partition1(arr, l, r);
    quickSort1(arr, l, p - 1);
    quickSort1(arr, p + 1, r);
}

//返回p  使得arr[l...p-1] < arr[p];  arr[p+1...r] > arr[p]
private int partition(int[] arr, int l, int r) {
    int v = arr[l];
    // arr[l+1,j] < v ;  arr[j+1,i) > v
    int j = l;
    for (int i = l + 1; i <= r; i++) {
        if (arr[i] < v) {
            SortUtil.swap(arr, i, j + 1);
            j++;
        }
    }
    SortUtil.swap(arr, l, j);
    return j;
}

#####2. 優化方式一

/*
 * 將第一個元素爲基準值改爲隨機一個元素爲基準值 
 * 降低退化爲O(n^2)的概率 
 * 數學期望值爲O(nlogn) 
 * 這樣就不會受“幾乎有序的數組”的干擾了 
 * 但是對“幾乎亂序的數組”的排序性能可能會稍微下降
 * 亂序時這個交換沒有意義 
 */
//返回p  使得arr[l...p-1] < arr[p];  arr[p+1...r] > arr[p]
private int partition1(int[] arr, int l, int r) {

    //產生一個 l 到 r 的隨機數作爲數組基準值下標
    //將隨機數與數組第一個數交換,即讓隨機數作爲基準值
    //減小近乎有序的概率
	int ran = (int) (Math.random() * (r - l + 1) + l);
	SortUtil.swap(arr,l,ran);
    int v = arr[l];
    // arr[l+1,j] < v ;  arr[j+1,i) > v
    int j = l;
    for (int i = l + 1; i <= r; i++) {
        if (arr[i] < v) {
            SortUtil.swap(arr, i, j + 1);
            j++;
        }
    }
    SortUtil.swap(arr, l, j);
    return j;
}

#####3. 優化方式二

左邊去找比基準值大的元素,右邊找比基準值小的元素,如果都找到了,就交換左右兩邊的元素,同時左邊的索引i++,右邊的索引j–,繼續查找下一個

/*
 * 如果數組中存在大量重複元素,並且第一個數也是重複元素時
 * 我們一般以第一個元素作爲基準值,這樣就有可能導致大量
 * 可能導致兩邊子遞歸規模差距懸殊的情
 * 時間複雜度就很有可能退化爲O(n^2) 
 */
private void quickSort(int[] arr, int l, int r) {
    if (l >= r) return;
    int p = partition(arr, l, r);
    quickSort(arr, l, p - 1);
    quickSort(arr, p + 1, r);
}

private int partition(int[] arr,int l,int r){
    int v = arr[l];
    int i = l+1;
    int j = r;
    while (true){
        //左邊開始掃描,直到找到一個比v大的數值,否則i++ i不能等於r 因爲後面i++就會數組越界
        while (arr[i] < v && i < r)
            i++;
        //右邊開始掃描,直到找到一個比v小的數值,否則j--
        while (arr[j] > v && j >= l)
            j--;
        if(i >= j) break;
        SortUtil.swap(arr,i,j);
        i++;
        j--;
    }
    SortUtil.swap(arr,l,j);
    return j;
}

#####4. 優化方式三(三路快排)

private void quickSort(int[] arr,int l,int r){
    if(l >= r)
        return;
    int v = arr[l];
    //變量定義要保證初始空間爲空
    int lt = l; //arr[l+1...lt] < v;
    int gt = r + 1; //arr[gt...r] > v;
    int i = l + 1; //arr[lt+1...i) == v;
    while (i < gt){
        if(arr[i] == v){
            i++;
        }else if(arr[i] < v){
            SortUtil.swap(arr,i,lt+1);
            lt++;
            i++;
        }else{ // arr[i] > v
            SortUtil.swap(arr,i,gt-1);
            gt--;
        }
    }
    SortUtil.swap(arr,l,lt);
    //上面元素互換之後arr[lt] = v; 所以 <v 部分的遞歸調用排序的時候我們只要到lt-1即可
    //==v 部分就不再需要排序了
    quickSort(arr,l,lt- 1);    // <v 部分排序
    quickSort(arr,gt,r);       // >v 部分排序
}

###4.測試

@Test
public void test(){
    int arr[] = {0,1,1,2,3,3,4,8,7,6,12,22,65};
    //        int arr[] = {6,3,8,2,9,1};
    System.out.println("排序前");
    printArr(arr);
    
    int[] data = quickSort(arr);
    
    System.out.println("排序後");
    printArr(data);
}

public void printArr(int[] arr){
    for (int i = 0; i < arr.length; i++) {
        System.out.print(arr[i]+" ");
    }
    System.out.println();
}

###5.總結
快速排序在大多數情況下都是適用的,尤其在數據量大的時候性能優越性更加明顯。
特點:
1.記錄非順次地移動導致排序方法是不穩定的。
2.排序過程中需要定位表的下界和上界,所以適合用於順序結構,很難用於鏈式結構。
3.適合初始記錄無序,n較大的情況,當n較大時,在平均情況下快速排序是所有內部排序中速度最快的一種。

###6.聯繫方式
QQ:1509815887

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章