排序算法系列——八大排序算法對比分析

本系列最後一篇,綜合分析下前面介紹的八種排序算法的效率,以及各自的適用情況。
下面先看看八種排序算法的時間複雜度表格:
八種排序算法時間複雜度
圖中八種排序被分成了兩組,一組時間複雜度爲O(n^2),另一組相對高效些。
下面先對第一組O(n^2)的四種排序算法進行對比,分別取數組長度爲100,1000,10000,100000四個數量級,各個元素在0-10000000之間隨機獲取。下面看下結果的分析。

排序算法 長度=100 長度=1000 長度=10000 長度=100000
直接插入排序 535 2,198 135,773 16,554,053
希爾排序 308 703 3,639 39,769
直接選擇排序 382 3,219 232,495 24,201,891
冒泡排序 525 5,377 475,865 62,703,335

從上表可以看出,以上四種算法效率最高的絕對是希爾排序,其次是直接插入排序,再就是直接選擇排序,最後纔是冒泡排序。事實證明冒泡排序真的不適合實際應用,實際使用中最優先選擇希爾排序,但是如果要求穩定性的話就選擇直接插入排序,但是效率差太多了。
下面主要對另外四個排序算法進行對比,不過通過上面的結果,所以在這裏把希爾排序也加進來一起比較。由於基數排序對數組最大位數敏感,所以這裏會分別對最大值爲3位、4位、5位、6位、7位五種情況分別對應長度爲10000,100000,1000000,10000000四種情況進行比較,長度太小體現不出O(nlgn)的優勢。

排序算法 長度=10000 長度=100000 長度=1000000 長度=10000000
希爾排序 3996 36439 876530 12129001
堆排序 3451 38077 767878 10459868
快速排序 2446 37998 2566811 673399814
歸併排序 2549 23595 356314 8325651
基數排序 4162 12724 272851 10866036

上表是MaxValue=1000時的結果,從表中可以看出當長度爲10000時快速排序和歸併排序效果最好,基數排序效果最差,但是當長度達到10W和100W基數排序效果最好,不過歸併排序效率就比快速排序高很多了,同時快速排序當排序數組長度達到100W時效果變得很差,遠遠落後其他四個排序算法。所以當待排序數組最大值爲1000時,數組長度爲10000時使用快速和歸併,當長度爲1000W以內使用基數排序,或者歸併排序。

排序算法 長度=10000 長度=100000 長度=1000000 長度=10000000
希爾排序 16349 201486 2800650 53033110
堆排序 3622 130898 3091654 51502613
快速排序 2366 83221 985588 71016772
歸併排序 2544 60744 841400 20983723
基數排序 4815 42903 962442 15133291

以上是MaxValue=10000時的結果,從表中可以看出基數排序的效果最好,其次是歸併排序,然後快速排序。所以當待排序數組最大值爲10000時使用基數排序還是很不錯的,雖然長度在10000時效果不如歸併排序。當然歸併排序也是個不錯的選擇。

排序算法 長度=10000 長度=100000 長度=1000000 長度=10000000
希爾排序 5107 203601 3401635 89081661
堆排序 5900 140530 4087559 76957182
快速排序 2873 89582 1553479 30429666
歸併排序 3215 81579 1238016 21442519
基數排序 15017 65216 1116738 16308437

以上是MaxValue=100000時的結果,從表中可以看出結果同MaxValue=10000時的結果差不多。

排序算法 長度=10000 長度=100000 長度=1000000 長度=10000000
希爾排序 3701 246185 3496864 79870999
堆排序 3999 122765 3563651 69155734
快速排序 14974 45710 1351332 19350824
歸併排序 3718 47956 1375705 23364515
基數排序 15001 37974 1290274 16083427

以上是MaxValue=1000000時的結果,結果還是一樣,基數排序效果最好。

排序算法 長度=10000 長度=100000 長度=1000000 長度=10000000
希爾排序 4063 235374 3524810 92989117
堆排序 11444 155166 3461969 63201731
快速排序 7501 61635 1435866 19843452
歸併排序 3376 42739 1322604 20859039
基數排序 6159 106208 1535261 25146006

以上是MaxValue=10000000時的結果,基數排序的效率已經沒有歸併排序好了,應該由於歸併的次數增加了。
這裏有個地方很奇怪,可以從上面MaxValue=1000時的表中可以看到當長度=100W時,快速排序的時間超過其他四個排序一個數量級,但是當MaxValue=10000,甚至更大之後快速排序的時間都和其它排序是一個數據量,而且MaxValue=1000時耗時大於MaxValue=10000以上。具體原因未知,大家可以自己測試,也許重複元素太多會影響快速排序的效率?
綜合以上所有測試結果,總結各個排序算法的適用場景。
直接插入排序:直接用希爾排序替代就好,除非待排序數組本身就是部分有序
希爾排序: 效果最好,秒殺所有O(n^2)的排序算法,所在在數據量較小的場景下,如100000個元素以下都可考慮希爾排序
直接選擇排序: 直接用希爾排序替代,或者用堆排序替代
冒泡排序: 強烈推薦不使用此排序算法
堆排序: 優於希爾排序,推薦替代希爾排序,但是如果待排序數組是部分有序的那麼希爾優於堆排序
快速排序: 數組長度100W以下效率最高,100W以上可以用歸併排序替代
歸併排序: 不考慮基數排序的話,數組長度100W以上效率最高,100W以下可以用快速排序替代
基數排序: 適用場景要求較高,元素必須是整數,整數時長度10W以上,最大值100W以下效率較好,但是基數排序比其他排序好在可以適用字符串,或者其他需要根據多個條件進行排序的場景,例如日期,先排序日,再排序月,最後排序年 ,其它排序算法可是做不了的。
下面附上測試代碼:

package com.vicky.sort;

import java.util.Random;
import java.util.Scanner;

import org.junit.Test;

public class SortComparison2 {

    /**
     * 比較全部排序算法
     */
    @Test
    public void testAll() {
        Scanner scan = new Scanner(System.in);
        int num = -1;
        int maxValue = -1;
        while (true) {
            // 從命令行輸入元素數量,以及最大值,格式:10,1000,輸入quit結束
            String input = scan.next();
            if ("quit".equals(input)) {
                System.exit(1);
            }
            String[] strs = input.split(",");
            num = Integer.parseInt(strs[0]);
            maxValue = Integer.parseInt(strs[1]);
            System.out.println("Sort Data Num = " + num + ", MaxValue = " + maxValue);

            Random ran = new Random();
            Integer[] data = new Integer[num];
            Integer[] data1 = new Integer[data.length];
            Integer[] data2 = new Integer[data.length];
            Integer[] data3 = new Integer[data.length];
            Integer[] data4 = new Integer[data.length];
            Integer[] data5 = new Integer[data.length];
            Integer[] data6 = new Integer[data.length];
            Integer[] data7 = new Integer[data.length];
            Integer[] data8 = new Integer[data.length];
            for (int i = 0; i < data.length; i++) {
                data[i] = ran.nextInt(maxValue);
                data1[i] = data[i];
                data2[i] = data[i];
                data3[i] = data[i];
                data4[i] = data[i];
                data5[i] = data[i];
                data6[i] = data[i];
                data7[i] = data[i];
                data8[i] = data[i];
            }
            // 插入排序
            long insertTimes = testStraightInsertionSort(data1);
            long shellTimes = testShellSort(data2);
            // 選擇排序
            long selectTimes = testStraightSelectionSort(data3);
            long heapTimes = testHeapSort(data4);
            // 交換排序
            long bubbleTimes = testBubbleSort(data5);
            long quickTimes = testQuickSort(data6);
            // 歸併排序
            long mergeTimes = testMergeSort(data7);
            // 基數排序
            long radixTimes = testRadixSort(data8);

            System.out.println("method       \ttime(ms)");
            System.out.println("InsertionSort\t" + insertTimes);
            System.out.println("ShellSort    \t" + shellTimes);
            System.out.println("SelectionSort\t" + selectTimes);
            System.out.println("HeapSort     \t" + heapTimes);
            System.out.println("BubbleSort   \t" + bubbleTimes);
            System.out.println("QuickSort    \t" + quickTimes);
            System.out.println("MergeSort    \t" + mergeTimes);
            System.out.println("RadixSort    \t" + radixTimes);
        }
    }

    /**
     *測試時間複雜度爲O(n^2)的排序
     */
    @Test
    public void testBase() {
        Scanner scan = new Scanner(System.in);
        int num = -1;
        int maxValue = -1;
        while (true) {
            // 從命令行輸入元素數量,以及最大值,格式:10,1000,輸入quit結束
            String input = scan.next();
            if ("quit".equals(input)) {
                System.exit(1);
            }
            String[] strs = input.split(",");
            num = Integer.parseInt(strs[0]);
            maxValue = Integer.parseInt(strs[1]);
            System.out.println("Sort Data Num = " + num + ", MaxValue = " + maxValue);

            Random ran = new Random();
            Integer[] data = new Integer[num];
            Integer[] data1 = new Integer[data.length];
            Integer[] data2 = new Integer[data.length];
            Integer[] data3 = new Integer[data.length];
            Integer[] data4 = new Integer[data.length];
            for (int i = 0; i < data.length; i++) {
                data[i] = ran.nextInt(maxValue);
                data1[i] = data[i];
                data2[i] = data[i];
                data3[i] = data[i];
                data4[i] = data[i];
            }
            // 插入排序
            long insertTimes = testStraightInsertionSort(data1);
            long shellTimes = testShellSort(data2);
            // 選擇排序
            long selectTimes = testStraightSelectionSort(data3);
            // 交換排序
            long bubbleTimes = testBubbleSort(data4);

            System.out.println("method       \ttime(ms)");
            System.out.println("InsertionSort\t" + insertTimes);
            System.out.println("ShellSort    \t" + shellTimes);
            System.out.println("SelectionSort\t" + selectTimes);
            System.out.println("BubbleSort   \t" + bubbleTimes);
        }
    }

    /**
     * 比較O(nlgn)左右的排序算法
     * 
     * 這裏把希爾加上是因爲雖然希爾時間複雜度是O(n^2)但是從實際結果來看其效率還是較高的,所以拿來跟O(nlgn)一起對比
     */
    @Test
    public void testGood() {
        Scanner scan = new Scanner(System.in);
        int num = -1;
        int maxValue = -1;
        while (true) {
            // 從命令行輸入元素數量,以及最大值,格式:10,1000,輸入quit結束
            String input = scan.next();
            if ("quit".equals(input)) {
                System.exit(1);
            }
            String[] strs = input.split(",");
            num = Integer.parseInt(strs[0]);
            maxValue = Integer.parseInt(strs[1]);
            System.out.println("Sort Data Num = " + num + ", MaxValue = " + maxValue);

            Random ran = new Random();
            Integer[] data = new Integer[num];
            Integer[] data1 = new Integer[data.length];
            Integer[] data2 = new Integer[data.length];
            Integer[] data3 = new Integer[data.length];
            Integer[] data4 = new Integer[data.length];
            Integer[] data5 = new Integer[data.length];
            for (int i = 0; i < data.length; i++) {
                data[i] = ran.nextInt(maxValue);
                data1[i] = data[i];
                data2[i] = data[i];
                data3[i] = data[i];
                data4[i] = data[i];
                data5[i] = data[i];
            }
            // 插入排序
            long shellTimes = testShellSort(data1);
            // 選擇排序
            long heapTimes = testHeapSort(data2);
            // 交換排序
            long quickTimes = testQuickSort(data3);
            // 歸併排序
            long mergeTimes = testMergeSort(data4);
            // 基數排序
            long radixTimes = testRadixSort(data5);

            System.out.println("method       \ttime(ms)");
            System.out.println("ShellSort    \t" + shellTimes);
            System.out.println("HeapSort     \t" + heapTimes);
            System.out.println("QuickSort    \t" + quickTimes);
            System.out.println("MergeSort    \t" + mergeTimes);
            System.out.println("RadixSort    \t" + radixTimes);
        }
    }

    public static <T extends Comparable<T>> long testStraightInsertionSort(T[] data) {
        long start = System.nanoTime();
        StraightInsertionSort.sort(data);
        return (System.nanoTime() - start) / 1000;
    }

    public static <T extends Comparable<T>> long testShellSort(T[] data) {
        long start = System.nanoTime();
        ShellSort.sort(data);
        return (System.nanoTime() - start) / 1000;
    }

    public static <T extends Comparable<T>> long testStraightSelectionSort(T[] data) {
        long start = System.nanoTime();
        StraightSelectionSort.sort(data);
        return (System.nanoTime() - start) / 1000;
    }

    public static <T extends Comparable<T>> long testHeapSort(T[] data) {
        long start = System.nanoTime();
        HeapSort.sort(data);
        return (System.nanoTime() - start) / 1000;
    }

    public static <T extends Comparable<T>> long testBubbleSort(T[] data) {
        long start = System.nanoTime();
        BubbleSort.sort(data);
        return (System.nanoTime() - start) / 1000;
    }

    public static <T extends Comparable<T>> long testQuickSort(T[] data) {
        long start = System.nanoTime();
        QuickSort.sort(data);
        return (System.nanoTime() - start) / 1000;
    }

    public static <T extends Comparable<T>> long testMergeSort(T[] data) {
        long start = System.nanoTime();
        MergeSort.sort(data);
        return (System.nanoTime() - start) / 1000;
    }

    public static long testRadixSort(Integer[] data) {
        long start = System.nanoTime();
        RadixSort.sort(data);
        return (System.nanoTime() - start) / 1000;
    }
}

運行時需要指定JVM運行參數:-Xms1024m -Xmx1024m -Xss2048k。
以上測試可能會有偏差,

發佈了55 篇原創文章 · 獲贊 35 · 訪問量 20萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章