O(nlogn)快速排序-雙路排序+詳細註解

WRY_發表於2020-11-20

排序使用的相關工具類與介面
在這裡插入圖片描述
執行效率

    /**
     * 這時一個雙路快速排序
     * 這是一個雙路快速排序
     * 陣列長度[2000] 值範圍[1-2000] 消耗的時間為[1]毫秒
     * 陣列長度[4000] 值範圍[1-4000] 消耗的時間為[1]毫秒
     * 陣列長度[8000] 值範圍[1-8000] 消耗的時間為[1]毫秒
     * 陣列長度[16000] 值範圍[1-16000] 消耗的時間為[1]毫秒
     * 陣列長度[32000] 值範圍[1-32000] 消耗的時間為[4]毫秒
     * 陣列長度[64000] 值範圍[1-64000] 消耗的時間為[4]毫秒
     * 陣列長度[128000] 值範圍[1-128000] 消耗的時間為[7]毫秒
     * 陣列長度[256000] 值範圍[1-256000] 消耗的時間為[16]毫秒
     * 陣列長度[512000] 值範圍[1-512000] 消耗的時間為[34]毫秒
     * 陣列長度[1024000] 值範圍[1-1024000] 消耗的時間為[72]毫秒
     * 陣列長度[2048000] 值範圍[1-2048000] 消耗的時間為[147]毫秒
     * 陣列長度[4096000] 值範圍[1-4096000] 消耗的時間為[306]毫秒
     * 陣列長度[8192000] 值範圍[1-8192000] 消耗的時間為[644]毫秒
     * 陣列長度[16384000] 值範圍[1-16384000] 消耗的時間為[1335]毫秒
     * 陣列長度[32768000] 值範圍[1-32768000] 消耗的時間為[2754]毫秒
     * 陣列長度[65536000] 值範圍[1-65536000] 消耗的時間為[5657]毫秒
     * 陣列長度[131072000] 值範圍[1-131072000] 消耗的時間為[11612]毫秒
     * 陣列長度[262144000] 值範圍[1-262144000] 消耗的時間為[24653]毫秒
     */
    @Test
    public void quickSort2WaysMethod() {
        ArraysSort arraysSort = new QuickSort2WaysMethod();
        SortHelper.arraySort(arraysSort, 2000, 20);
    }

程式碼實現


    private final static Random RANDOM=new Random();

    @Override
    public String getSortName() {
        return "這是一個雙路快速排序";
    }

    @Override
    public int[] arraySortMethod(int[] ints) {
        optimizeSort(ints, 0, ints.length - 1);
        return ints;
    }

    /**
     * 雙路快速排序
     * 優化後快速排序分配方法
     *
     * @param ints
     * @param l
     * @param r
     */
    public void optimizeSort(int[] ints, int l, int r) {
        //在出現左索引等於右索引的時候
        //它們都是指向同一索引所以沒有相比的必要
        //這裡直接返回
//        if (l >= r) {
//            return;
//        }

        //在資料量較小的時候使用插入排序
        //這是因為經過了r-l>15的快速排序後 小一邊和大一邊的模糊排序後
        //在r-l<=15 的時候之間的最大差已經很小了
        //在最大差越小的情況下順序可能是越有序的
        //說不定已經近乎有序的了 優化的快速排序對近乎有序的排序效率非常高
        //優化後的插入排序對近乎有序的陣列進行效率相對較高
        //插入排序相對歸併排序減少了陣列交換的過程
        if (r - l <= 15) {
            InsertSortMethod.insertSortMethod(ints, l, r);
            return;
        }

        //個人理解快排核心
        //每次遍歷排序對l-r之間的陣列進行區分
        //好處1.每次遍歷可以定位一個p索引在ints陣列中確定的位置
        //好處2.因為每次排序都對l-r之間的資料進行大小劃分做了一次模糊的排序
        //保證下次排序l-r排序之間最大差越來越小
        //所以下次遍歷都比上次排序更加有序效率相對更快
        int p = optimizeQuickSort(ints, l, r);
        //以p中心點-1和+1為界限繼續遞迴快速排序
        optimizeSort(ints, l, p - 1);
        optimizeSort(ints, p + 1, r);
    }

    /**
     * 雙路快速排序
     * partition
     * 優化後的排序演算法邏輯
     * 之前的排序演算法邏輯是從左到右排序演算法邏輯有個問題
     * 在處理重複資料較多的情況下 可能會出現等於當時比較的元素較多的情況
     * 等於的那個元素要麼在<=v方或者>=方 這樣就會造成其中一段分配的元素數量極度不平衡
     * 為避免這種情況 讓相等於當時比較元素的元素 相對平均的分配到每一邊
     * 所以改為從兩邊向中間遞進 遇到相等時的元素時交換一下
     * 這樣在處理元素差較小又相對有序的陣列的時候可以更加平均的把相等的元素分配到兩邊
     *
     * @param ints 整個陣列
     * @param l    當前最小索引
     * @param r    當前最大索引
     */
    private int optimizeQuickSort(int[] ints, int l, int r) {
        //隨機獲取交換一個需要對比的索引
        //如果不懂 可以看下面註釋
        SortHelper.swap(ints, l, RANDOM.nextInt(r - l + 1) + l);
        //以交換後第l個索引為為比較物件
        int v = ints[l];
        //初始化左開始索引 和 右開始索引
        int i = l + 1, j = r;
        //開始迴圈啦
        while (true) {
            //從左迴圈開始
            //始終保持i<=r 不會超出迴圈界限 如果inst[i]<v 則繼續迴圈
            //如果等於或者大於v則跳出當前迴圈 找到下一個需要交換的索引指標
            while (i <= r && ints[i] < v) {
                i++;
            }
            //從右迴圈開始
            //始終保持j>=i 不會超出迴圈界限 如果inst[i]>v 則繼續迴圈
            //如果等於或者小於v則跳出當前迴圈 開始準備i與j交換索引
            while (j >= i && ints[j] > v) {
                j--;
            }
            //如果i>j 說明其中一段已經遍歷到另一端的域中
            //說明已經遍歷完成了 直接退出迴圈
            if (i > j) {
                break;
            }
            //通過以上判斷後 確認有出現相等或者小一端有大於v的資料或者大一端有小於v的資料
            //開始交換索引資料
            SortHelper.swap(ints, i++, j--);
        }
        //以上迴圈完成 j處於大一端的初始位置  i的話就是小一端的結束位置
        //其實這裡與i 或者j 交換都可以
        //交換j與l的位置
        SortHelper.swap(ints, l, j);
        return j;
    }

相關文章