最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

java 排序算法之快速排序

 更新時間:2021年09月01日 15:01:53   作者:天然呆dull  
這篇文章主要介紹了java 排序算法之快速排序,文中通過圖片和代碼講解相關知識非常詳細,大家如果有需要的話可以參考一下這篇文章

簡單介紹

快速排序(Quicksort) 是對 冒泡排序的一種改進。

基本思想

快速排序算法通過多次比較和交換來實現(xiàn)排序,其排序流程如下:

  • (1)首先設定一個分界值(基準值),通過該分界值將數(shù)組分成左右兩部分。
  • (2)將大于或等于分界值的數(shù)據(jù)集中到數(shù)組右邊,小于分界值的數(shù)據(jù)集中到數(shù)組的左邊。此時,左邊部分中各元素都小于或等于分界值,而右邊部分中各元素都大于或等于分界值。
  • (3)然后,左邊和右邊的數(shù)據(jù)可以獨立排序。對于左側(cè)的數(shù)組數(shù)據(jù),又可以取一個分界值,將該部分數(shù)據(jù)分成左右兩部分,同樣在左邊放置較小值,右邊放置較大值。右側(cè)的數(shù)組數(shù)據(jù)也可以做類似處理。
  • (4)重復上述過程,可以看出,這是一個遞歸定義。通過遞歸將左側(cè)部分排好序后,再遞歸排好右側(cè)部分的順序。當左、右兩個部分各數(shù)據(jù)排序完成后,整個數(shù)組的排序也就完成了。

該思想可以概括為:挖坑填數(shù) + 分治法。

比如如下的示意圖:

  • 上圖以 最后一個元素的值 作為基準
  • 比基準值小的,排在左側(cè),比基準值大的排在右側(cè)
  • 然后再以分好的部分重復以上操作,直到每個部分中只有一個數(shù)據(jù)時,就排好序了

思路分析

基本思想如上,但是實現(xiàn)思路有多種,一般取基準值都是以首尾或者中間,這里使用 數(shù)組中間 的作為基準值,進行講解。

  • 原始數(shù)組:arr = [-9,78,0,23,-567-70]

  • 設置兩個變量,左下標L = 0,右下標R = 數(shù)組大小 - 1,選數(shù)組中間的值為基準值,pivot = arr[(L + R )/ 2] ,基準值為 0。再用兩個變量保存 左下標 和 右下標,left = Lright = R,用于后面的排序做準備。

可以看到,pivot把數(shù)組分成了兩組

  • 左邊一組,從左到右,挨個與 基準值 比較,找出比基準值大的值,跳出查找循環(huán)

  • 右邊一組,從右到左,挨個與 基準值 比較,找出比基準值小的值,跳出查找循環(huán)

  • 可以看到左右兩組各找到一個對應的值,那么就讓他們進行交換

  • 然后繼續(xù)找,直到左右兩邊碰到,

可以看到左邊的組已經(jīng)找完了,L指向了基準值,那就跳出查找循環(huán),右邊組開始查找,

但是我們可以看到右邊的數(shù)也都符合規(guī)則,所以R也循環(huán)遍歷到了基準值的位置,此時LR 已經(jīng)碰到一起了,這一輪就結(jié)束。這一輪就稱為快速排序。

繼續(xù)對分出來的小組,進行上述的快速排序操作,直到組內(nèi)只剩下一個數(shù)時,則排序完成。

  • LR 分別前進一步和后退一步,

如圖,就可以再次利用這兩個變量,對兩組進行快速排序了。

  • 先對左邊的組進行快速排序,同樣進行上面的操作,

這里就用到了上一次快速循環(huán)所保存的leftright(上圖沒有畫出來)了。

  • 因為這里 left 直接就指向了 pivot ,所以不用進行移動查找了。Rpivot進行比較 ,-567 < -9 ,Rleft進行交換,得到如下圖,注:pivot 是一個值,不是引用類型

因為 Rleft 沒有碰頭,所以還得進行一次循環(huán)比較。因為 R 就在基準點這,所以不移動,Rpivot 比較,-9 > -567 , 所以left 前進一步,

此時Rleft 已經(jīng)碰到一起了,這一輪就結(jié)束了。

  • 右邊的組也是同樣的道理,這里就不作過多的解析了

l ------------ pivot --------------- r
一組從左往右找               一組從右往左找

可以看到,分組后,可以使用遞歸,對這一組繼續(xù)分組,然后對他們進行快速排序。

代碼實現(xiàn)

推導實現(xiàn)

推導法先實現(xiàn)第一輪

    @Test
    public void processDemo() {
        int arr[] = {-9, 78, 0, 23, -567, 70};
        System.out.println("原始數(shù)組:" + Arrays.toString(arr));
        processQuickSort(arr, 0, arr.length - 1);
    }

    /**
     * @param arr
     * @param left  左邊這一組的下標起始點,到中間值,則為一組
     * @param right 右邊這一組的下標結(jié)束點,到中間值,則為一組
     */
    public void processQuickSort(int[] arr, int left, int right) {
        /*
         基本思想:選擇一個基準值,將基準值小分成一組,比基準值大的分成一組。
         這里的實現(xiàn)思路:
            1. 挑選的基準值為數(shù)組中間的值
            2. 中間值就把數(shù)組分成了兩組
            3. 左邊一組,從左到右,挨個與 基準值 比較,找出比基準值大的值
            4. 右邊一組,從右到左,挨個與 基準值 比較,找出比基準值小的值
            5. 左右兩邊各找到一個值,那么就讓這兩個值進行交換
            6. 然后繼續(xù)找,直到左右兩邊碰到,這一輪就結(jié)束。這一輪就稱為快速排序
            7. 繼續(xù)對分出來的小組,進行上述的快速排序操作,直到組內(nèi)只剩下一個數(shù)時,則排序完成

            l ------------ pivot --------------- r
            一組從左往右找               一組從右往左找
         */
        int l = left;
        int r = right;
        // 中心點,讓這個點作為基準值
        int pivot = arr[(left + right) / 2];
        // 當他們沒有碰到的時候,說明還這一輪還可以繼續(xù)找
        while (l < r) {
            // 左邊組:當找到大于基準值時,退出循環(huán),則表示該值需要交換到右側(cè)去: arr[l] > pivot
            //      也就是說,如果 arr[l] < pivot,則表示還沒有找到比基準值大的數(shù)
            //      注意:不能等于 pivort,因為最差的情況沒有找到,則最后 arr[l] 就是 pivot 這個值,也同樣退出循環(huán)
            while (arr[l] < pivot) {
                l++; // 所以讓左邊這一組繼續(xù)找
            }

            // 右邊組:當找到小于基準值時,退出循環(huán),則表示該值需要交換到左側(cè)去:arr[r] < pivot
            //      也就是說,如果 arr[l] > pivot,則表示還沒有找到比基準值小的數(shù)
            //注意:這里也同樣跟上面一樣,不能等于 pivort
            while (arr[r] > pivot) {
                r--;//讓右邊組繼續(xù)找
            }

            // 當左側(cè)與右側(cè)相碰時,說明兩邊都沒有找到,這一輪不用進行交換
            // 等于表示,找到了中間的下標
            if (l >= r) {
                break;
            }

            // 當找到時,則兩數(shù)進行交換。
            //注意:這里可能會出現(xiàn)有一組已經(jīng)找完了,或者沒有找到,但是另一組找到了,所以一個是指向 pivot 的,
            //另一個則是指向要交換的數(shù),交換后 pivot的值在數(shù)組中的位置會發(fā)生改變,下次的交換方式就會發(fā)生變化。這個地方要動腦筋想想。
            int temp = arr[l];
            arr[l] = arr[r];
            arr[r] = temp;

            // 當交換后,
            // 當數(shù)組是: {-9, 78, 0, -23, 0, 70}  時(pivot的值在數(shù)組中有多個),就可以驗證這里的邏輯
            // 如果沒有這個判定,將會導致,l 永遠 小于 r。循環(huán)不能退出來的情況,就出現(xiàn)死循環(huán)了
            if (arr[l] == pivot) {
                /*
                  l 自己不能往前移動 一步,因為當交換完成后為:{-9, 0, 0, -23, 78, 70}
                  l = 1,arr[l] = 0
                  r = 4,arr[r] = 78
                  再經(jīng)過一次循環(huán)后
                  l = 1,arr[l] = 0
                  r = 3,arr[r] = -23
                  交換后數(shù)組為:{-9,-23,0,0,78,70}
                  此時 l = 1,arr[l] = -23;r = 3,arr[r] = 0
                  又經(jīng)過一次循環(huán)后
                  l = 2,arr[l] = 0
                  r = 3,arr[r] = 0
                  數(shù)組為:{-9,-23,0,0,78,70}
                  進入了死循環(huán)
                  這里好好動腦子想想
                  
                  這里為什么是用r-=1呢?是因為if里面的條件是arr[l] == pivot,如果要排序的數(shù)組中不存在多個和基準值相等的值,
                  那么用l+=1的話,l就會跑過分界線(基準值),跑到另一組去,這個算法也就失敗了,
                  還有一個原因是,r 是剛剛交換過的,一定比 基準值大,所以沒有必要再和基準值比較了
                 */
                r -= 1;
            }
            // 這里和上面一致,如果說,先走了上面的 r-=1
            // 這里也滿足(也就是說有多個值和基準值相等),那么說明,下一次是相同的兩個值,一個是 r == 一個是基準值
            // 但是他們是相同的值,r后退一步 l前進一步,不影響。但是再走這完這里邏輯時,就會導致  l > r,退出整個循環(huán)
            if (arr[r] == pivot) {
                l += 1;
            }
        }
        System.out.println("第 1 輪排序后:" + Arrays.toString(arr));
    }

注意:上述的算法特別是邊界判定,就是上面「當交換后」對 r-=1 的這個邊界判定時,有點難以理解,但是一定要理解為什么要這樣寫。

測試信息輸出

原始數(shù)組:[-9, 78, 0, 23, -567, 70]
第 1 輪排序后:[-9, -567, 0, 23, 78, 70]

那么如何向左遞歸和右遞歸呢?在上面的代碼后面接著實現(xiàn)如下

        System.out.println("第 1 輪排序后:" + Arrays.toString(arr));

		/*
		if (l >= r) {
                break;
            }
            循環(huán)從這條代碼中跳出就會l = r
		*/
        // 如果 l = r,會出現(xiàn)死循環(huán),出現(xiàn)棧溢出
		//這里也要動腦子想想
        if (l == r) {
            l++;
            r--;
        }

        // 開始左遞歸
        // 上面算法是 r--,l++ ,往兩組的中間走,當 left < r 時,表示還可以繼續(xù)分組
        if (left < r) {
            processQuickSort(arr, left, r);
        }

        if (right > l) {
            processQuickSort(arr, l, right);
        }

完整實現(xiàn)

完整實現(xiàn)和推導實現(xiàn)其實差不多了,為了加深記憶,自己按照基本思想和思路分析,默寫。

  /**
     * 快速排序默寫實現(xiàn)
     * 
     *     基本思想:通過一趟將要排序的數(shù)據(jù),分隔成獨立的兩個部分,一部分的所有數(shù)據(jù)都比另一部分的所有數(shù)據(jù)要小。
     *     思路分析:
     *      {-9, 78, 0, 23, -567, 70};  length=6
     *      1. 挑選中間的值作為 基準值:(0 + (6 -1))/2= [2] = 0
     *      2. 左側(cè) left 部分,從 0 開始到中間值 -1: 0,1: -9, 78,找出一個比基準值大的數(shù)
     *      3. 右側(cè) right 部分,從中間值 + 1 到數(shù)組大小-1:3,5:23,-567, 70,找出一個比基準值小的數(shù)
     *      4. 如果找到,則將他們進行交換,這樣一輪下來,就完成了一次快速排序:一部分的所有數(shù)據(jù)都比另一部分的所有數(shù)據(jù)要小。
     *      4. 如果左側(cè)部分還可以分組,則進行左側(cè)遞歸調(diào)用
     *      5. 如果右側(cè)部分還可以分組,則進行右側(cè)遞歸調(diào)用
     *
     *    簡單說:一輪快速排序示意圖如下:
     *                   中間的基準值
     *      l ------------ pivot --------------- r
     *      一組從左往右找               一組從右往左找
     *      找到比基準值大的數(shù)          找出一個比基準值小的數(shù)
     *                    然后進行交換
     * 
     */
    @Test
    public void quickSortTest() {
        int arr[] = {-9, 78, 0, 23, -567, 70};
//        int arr[] = {-9, 78, 0, -23, 0, 70}; // 在推導過程中,將會導致交換異常的數(shù)組,在這里不會出現(xiàn)那種情況
        int left = 0;
        int right = arr.length - 1;
        System.out.println("原始數(shù)組:" + Arrays.toString(arr));
        quickSort(arr, left, right);
        System.out.println("排序后:" + Arrays.toString(arr));
    }

    public void quickSort(int[] arr, int left, int right) {
        // 找到中間值
        int pivotIndex = (left + right) / 2;
        int pivot = arr[pivotIndex];
        int l = left;
        int r = right;
        while (l < r) {
            // 從左往右找,直到找到一個數(shù),比基準值大的數(shù)
            while (arr[l] < pivot) {
                l++;
            }
            // 從右往左找,知道找到一個數(shù),比基準值小的數(shù)
            while (arr[r] > pivot) {
                r--;
            }
            // 表示未找到
            if (l >= r) {
                break;
            }
            // 進行交換
            int temp = arr[l];
            arr[l] = arr[r];
            arr[r] = temp;

            // 那么下一輪,左側(cè)的這個值將不再參與排序,因為剛交換過,一定比基準值小
            // 那么下一輪,右側(cè)的這個值將不再參與排序,因為剛交換過,一定比基準值大
            r--;
            l++;
        }

        // 當一輪找完后,沒有找到,則是中間值時,
        // 需要讓他們擦肩而過,也就是重新分組,中間值不再參與分組
        // 否則,在某些情況下,會進入死循環(huán)
        if (l == r) {
            l++;
            r--;
        }
        // 如果左側(cè)還可以繼續(xù)分組,則繼續(xù)快排
        // 由于擦肩而過了,那么左側(cè)的組值,則是最初的開始與中間值的前一個,也就是這里得到的 r
        if (left < r) {
            quickSort(arr, left, r);
        }
        if (right > l) {
            quickSort(arr, l, right);
        }
    }

另外,在實現(xiàn)的過程中,將某些代碼為什么要那樣判斷邊界,進行了梳理。你會發(fā)現(xiàn)上述代碼和推導的代碼有一個地方不一樣。這個是我自己按邏輯進行的改進,更容易看明白一些。目前未發(fā)現(xiàn) bug,如果有錯請評論指出,畢竟這個算法還是有點難的。

大數(shù)據(jù)量耗時測試

    /**
     * 大量數(shù)據(jù)排序時間測試
     */
    @Test
    public void bulkDataSort() {
        int max = 80000;
//        int max = 8;
        int[] arr = new int[max];
        for (int i = 0; i < max; i++) {
            arr[i] = (int) (Math.random() * 80000);
        }
        if (arr.length < 10) {
            System.out.println("原始數(shù)組:" + Arrays.toString(arr));
        }
        Instant startTime = Instant.now();
//        processQuickSort(arr, 0, arr.length - 1);  // 和老師的原版代碼對比,結(jié)果是一樣的
        quickSort(arr, 0, arr.length - 1);
        if (arr.length < 10) {
            System.out.println("排序后:" + Arrays.toString(arr));
        }
        Instant endTime = Instant.now();
        System.out.println("共耗時:" + Duration.between(startTime, endTime).toMillis() + " 毫秒");
    }

多次運行輸出

共耗時:40 毫秒
共耗時:52 毫秒
共耗時:36 毫秒
共耗時:31 毫秒

性能分析

快速排序的一次劃分算法從兩頭交替搜索,直到low和hight重合,因此其時間復雜度是O(n);而整個快速排序算法的時間復雜度與劃分的趟數(shù)有關。

理想的情況是,每次劃分所選擇的中間數(shù)恰好將當前序列幾乎等分,經(jīng)過log2n趟劃分,便可得到長度為1的子表。這樣,整個算法的時間復雜度為O(nlog2n)。

最壞的情況是,每次所選的中間數(shù)是當前序列中的最大或最小元素,這使得每次劃分所得的子表中一個為空表,另一子表的長度為原表的長度-1。這樣,長度為n的數(shù)據(jù)表的快速排序需要經(jīng)過n趟劃分,使得整個排序算法的時間復雜度為O(n2)。

為改善最壞情況下的時間性能,可采用其他方法選取中間數(shù)。通常采用“三者值取中”方法,即比較H->r[low].key、H->r[high].key與H->r[(low+high)/2].key,取三者中關鍵字為中值的元素為中間數(shù)。

可以證明,快速排序的平均時間復雜度也是O(nlog2n)。因此,該排序方法被認為是目前最好的一種內(nèi)部排序方法。

從空間性能上看,盡管快速排序只需要一個元素的輔助空間,但快速排序需要一個??臻g來實現(xiàn)遞歸。最好的情況下,即快速排序的每一趟排序都將元素序列均勻地分割成長度相近的兩個子表,所需棧的最大深度為log2(n+1);但最壞的情況下,棧的最大深度為n。這樣,快速排序的空間復雜度為O(log2n)。

以上就是java 排序算法之快速排序的詳細內(nèi)容,更多關于java 快速排序的資料請關注腳本之家其它相關文章!

相關文章

  • Java版超大整數(shù)階乘算法代碼詳解-10,0000級

    Java版超大整數(shù)階乘算法代碼詳解-10,0000級

    這篇文章主要介紹了Java版超大整數(shù)階乘算法代碼詳解-10,0000級,具有一定借鑒價值,需要的朋友可以參考下
    2018-01-01
  • Java利用蒙特卡洛方法求解圓周率π值

    Java利用蒙特卡洛方法求解圓周率π值

    蒙特·卡羅方法(Monte Carlo method),也稱統(tǒng)計模擬方法,是一種以概率統(tǒng)計理論為基礎的數(shù)值計算方法。本文將利用該方法實現(xiàn)圓周率的計算,需要的可以參考一下
    2022-08-08
  • SpringCloud-Gateway轉(zhuǎn)發(fā)WebSocket失敗問題及解決

    SpringCloud-Gateway轉(zhuǎn)發(fā)WebSocket失敗問題及解決

    這篇文章主要介紹了SpringCloud-Gateway轉(zhuǎn)發(fā)WebSocket失敗問題及解決方案,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2023-09-09
  • Lombok?安裝和使用小技巧

    Lombok?安裝和使用小技巧

    這篇文章主要介紹了Lombok?安裝和使用指南,文章圍繞主題展開詳細的內(nèi)容介紹,具有一定的參考價值,需要的小伙伴可以參考一下
    2022-05-05
  • Java 時間轉(zhuǎn)換的實例代碼

    Java 時間轉(zhuǎn)換的實例代碼

    下面小編就為大家?guī)硪黄狫ava 時間轉(zhuǎn)換的實例代碼。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2016-07-07
  • 在SpringBoot項目中利用maven的generate插件

    在SpringBoot項目中利用maven的generate插件

    今天小編就為大家分享一篇關于在SpringBoot項目中利用maven的generate插件,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧
    2019-01-01
  • Java有效處理異常的三個原則

    Java有效處理異常的三個原則

    Java中異常提供了一種識別及響應錯誤情況的一致性機制,有效地異常處理能使程序更加健壯、易于調(diào)試。那么這篇文章總結(jié)了Java有效處理異常的三個原則,有需要的朋友們可以參考借鑒。
    2016-09-09
  • Java基于堆結(jié)構(gòu)實現(xiàn)優(yōu)先隊列功能示例

    Java基于堆結(jié)構(gòu)實現(xiàn)優(yōu)先隊列功能示例

    這篇文章主要介紹了Java基于堆結(jié)構(gòu)實現(xiàn)優(yōu)先隊列功能,結(jié)合實例形式分析了java優(yōu)先隊列的簡單定義與使用方法,需要的朋友可以參考下
    2017-11-11
  • SpringIntegration消息路由之Router的條件路由與過濾功能

    SpringIntegration消息路由之Router的條件路由與過濾功能

    本文詳細介紹了Router的基礎概念、條件路由實現(xiàn)、基于消息頭的路由、動態(tài)路由與路由表、消息過濾與選擇性路由以及錯誤處理與路由等方面的內(nèi)容,提高了系統(tǒng)的可維護性和可擴展性,感興趣的朋友一起看看吧
    2025-04-04
  • Spring事務annotation原理詳解

    Spring事務annotation原理詳解

    這篇文章主要介紹了Spring事務annotation原理詳解,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2020-02-02

最新評論

朝阳区| 翁牛特旗| 尤溪县| 容城县| 盱眙县| 连江县| 弋阳县| 江门市| 龙州县| 祁阳县| 揭西县| 衡阳县| 三门县| 胶州市| 安阳市| 嵊州市| 武安市| 封开县| 松江区| 栾城县| 德保县| 缙云县| 宣汉县| 崇义县| 邢台县| 怀化市| 江华| 白玉县| 安远县| 德庆县| 盱眙县| 楚雄市| 淄博市| 合作市| 南通市| 天门市| 宜宾县| 静海县| 梨树县| 台州市| 衢州市|