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] 消耗的時間為[1]毫秒
     * 陣列長度[64000] 值範圍[1-64000] 消耗的時間為[3]毫秒
     * 陣列長度[128000] 值範圍[1-128000] 消耗的時間為[6]毫秒
     * 陣列長度[256000] 值範圍[1-256000] 消耗的時間為[16]毫秒
     * 陣列長度[512000] 值範圍[1-512000] 消耗的時間為[35]毫秒
     * 陣列長度[1024000] 值範圍[1-1024000] 消耗的時間為[71]毫秒
     * 陣列長度[2048000] 值範圍[1-2048000] 消耗的時間為[153]毫秒
     * 陣列長度[4096000] 值範圍[1-4096000] 消耗的時間為[325]毫秒
     * 陣列長度[8192000] 值範圍[1-8192000] 消耗的時間為[675]毫秒
     * 陣列長度[16384000] 值範圍[1-16384000] 消耗的時間為[1397]毫秒
     * 陣列長度[32768000] 值範圍[1-32768000] 消耗的時間為[2890]毫秒
     * 陣列長度[65536000] 值範圍[1-65536000] 消耗的時間為[6031]毫秒
     * 陣列長度[131072000] 值範圍[1-131072000] 消耗的時間為[12727]毫秒
     * 陣列長度[262144000] 值範圍[1-262144000] 消耗的時間為[25993]毫秒
     * <p>
     * Process finished with exit code 0
     */
    @Test
    public void quickSortTest() {
        ArraysSort arraysSort = new QuickSortMethod();
        SortHelper.arraySort(arraysSort, 2000, 20);
    }

實現程式碼


    public static final Random RANDOM = new Random();

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

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


    /**
     * 最初的快排演算法邏輯
     *
     * @param ints 整個陣列
     * @param l    當前最小索引
     * @param r    當前最大索引
     */
    private int quickSort(int[] ints, int l, int r) {
        //這裡以每個l記錄進行比較
        //但是這裡有個問題
        //在快速排序處理近乎有序的陣列時 如果預設選取的是最左側索引節點
        //這樣會造成左分塊除了l近乎沒有,右分塊又會出現很多
        //以此迴圈會造成遞迴樹非常深 遞迴樹的深度可能就無限接近於n長度
        //如果是這樣的話 該排序的時間效率就無限接近n^2

        //每次隨機選取l-r一個索引 與l 索引進行交換就解決了該問題
        SortHelper.swap(ints, l, RANDOM.nextInt(r - l + 1) + l);
        int v = ints[l];

        //初始化開始記錄比v小和比v大的中間索引點
        //j理論上記錄的是比v小的最大索引
        int j = l;
        for (int i = l + 1; i <= r; i++) {
            //如果出現比v小的情況
            //就需要做交換 移動j的索引後移一格
            if (ints[i] < v) {
                //每次j+1=比v大一邊的第一個索引
                //讓大一邊第一個索引和當前迴圈到的這個索引做交換
                //以上交換後j+1=比v小一邊的最後一個索引
                //之前比v大一邊的第一個索引就跑到i索引下 也是當時大一邊的最後一個索引
                SortHelper.swap(ints, ++j, i);
            }
        }
        //到這裡j記錄的就是比v小一邊的最大索引
        //以上迴圈完成後就可以確定l索引在ints陣列中排序的位置
        SortHelper.swap(ints, j, l);
        //返回l在ints中排好序確定的索引位置 並以j分割繼續遞迴排序
        return j;
    }

    /**
     * 最初的快排演算法邏輯
     *
     * @param ints
     * @param l
     * @param r
     */
    public void sort(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 = quickSort(ints, l, r);
        //以p中心點-1和+1為界限繼續遞迴快速排序
        sort(ints, l, p - 1);
        sort(ints, p + 1, r);
    }
 

相關文章