1. 程式人生 > >排序算法系列——八大排序演算法對比分析

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

本系列最後一篇,綜合分析下前面介紹的八種排序演算法的效率,以及各自的適用情況。
下面先看看八種排序演算法的時間複雜度表格:
八種排序演算法時間複雜度
圖中八種排序被分成了兩組,一組時間複雜度為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。
以上測試可能會有偏差,

相關推薦

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

本系列最後一篇,綜合分析下前面介紹的八種排序演算法的效率,以及各自的適用情況。 下面先看看八種排序演算法的時間複雜度表格: 圖中八種排序被分成了兩組,一組時間複雜度為O(n^2),另一組相對高效些。 下面先對第一組O(n^2)的四種排序演算法進行對比,

資料結構與系列10--排序演算法(歸併、快排)

歸併排序 思想: 歸併排序的核心思想還是蠻簡單的。如果要排序一個數組,我們先把陣列從中間分成前後兩部分,然後對前後兩部分分別排序,再將排好序的兩部分合並在一起,這樣整個陣列就都有序了。 #歸併排序 def merge_sort(a): n=len(a) merge_

資料結構與系列9--排序演算法(冒泡、插入、選擇)

氣泡排序 思想: 氣泡排序只會操作相鄰的兩個資料。每次冒泡操作都會對相鄰的兩個元素進行比較,看是否滿足大小關係要求。如果不滿足就讓它倆互換。一次冒泡會讓至少一個元素移動到它應該在的位置,重複n次,就完成了n個數據的排序工作。 #優化版本 def bubbleSort(data_lis

基礎系列排序演算法-3. 直接插入排序 並用其解決HDU 1106 排序

      我們之前已經學習了氣泡排序和二分查詢兩種基本演算法,本篇文章我們將一起學習下一個基礎演算法——直接插入排序演算法。  直接插入排序      直接插入排序,從這個名字來看,是不是讓你想到了什麼場景?!對了,就是打撲克牌的場景,我們每摸一張牌,是不是按照一定的次

基礎系列排序演算法-5. 快速排序

我們之前學習了氣泡排序演算法,我們知道,在氣泡排序過程中,只對相鄰的兩個元素進行比較,因此每次交換兩個相鄰的元素時只能消除一個逆序。如果能通過兩個(不相鄰)元素的一次交換,消除多個逆序,則會大大加快排序的速度。而這就是本篇文章講述的另一種基本排序演算法——快速排序演算法。

基礎系列排序演算法-6.折半插入排序 並用之解決hdu 1412問題

       我們之前已經瞭解了5種基礎演算法,是否自己找了一些題練練手呢~話不多說,讓我們進入第6中基礎演算法的學習吧。本篇我們將學習又一種排序演算法——折半插入排序演算法,跟上篇我們所學習的快速排序有點像,都是建立在我們之前學習的演算法的基礎上改進而來的。從這個演算法的名

基礎系列排序演算法-7.希爾排序 並解決hdu 1425問題(java實現)

       我們從最初的氣泡排序演算法,到上篇文章的折半插入排序演算法,我們一共學習了5種排序演算法,相信以大家的聰明才智肯定都消化了^_^。在本篇文章中,我們又將學習第6種排序演算法——希爾排序演算法。那就讓我們直奔主題吧。 希爾排序  讓我們回想一下直接插入排序演算

排序系列:歸併排序演算法

概述 上一篇我們說了一個非常簡單的排序演算法——選擇排序。其複雜程式完全是冒泡級的,甚至比冒泡還要簡單。今天要說的是一個相對比較複雜的排序演算法——歸併排序。複雜的原因不僅在於歸併排序分成了兩個部分進行解決問題,而是在於,你需要一些演算法的思想支撐。 歸併排

排序系列:Shell 排序演算法

概述 希爾排序(Shell Sort)是 D.L.Shell 於 1959 年提出來的一種排序演算法,在這之前排序演算法的時間複雜度基本都是 O(n2) 的,希爾排序演算法是突破這個時間複雜度的第一批演算法之一。希爾排序是一種插入排序演算法。 版權說

排序系列:快速排序演算法

概述  在前面說到了兩個關於交換排序的演算法:氣泡排序與奇偶排序。  本文就來說說交換排序的最後一拍:快速排序演算法。之所以說它是快速的原因,不是因為它比其他的排序演算法都要快。而是從實踐中證明了快速排序在平均效能上的確是比其他演算法要快一些,不然快速一說豈

排序系列:奇偶排序演算法

概述   在上一篇中我們說到了氣泡排序的原理及實現詳解。氣泡排序是一種交換排序,本文還是接著上一講,說說另一種交換排序演算法——奇偶排序。 版權說明 目錄 奇偶排序演算法 奇偶排序實際上在多處理器環境中很有用,處

八大排序之基數排序

輸出 bsp 交換 and del 當前 print [] radixsort 設計思想   它是根據關鍵字中各位的值,通過對排序的N個元素進行若幹趟“分配”與“收集”來實現排序的。它不要比較關鍵字的大小。   假設:R {50, 123, 543, 187, 49, 30

八大排序之插入排序

代碼實現 == 記錄 分析 tro return dom span col 算法思想:每一趟將一個待排序的記錄,按照其關鍵字的大小插入到有序隊列的合適位置裏,知道全部插入完成。 設計步驟:   假設有一組無序序列 R0, R1, ... , RN-1。   (1) 我們先

07-看圖理解資料結構與系列(選擇排序)

選擇排序 選擇排序是一種很簡單直觀的排序演算法,主要思想就是每次從待排序的元素中選擇出最大或最小的那個元素,然後將其放至已排序序列的末尾,直到全部待排序序列都排序完畢。 排序要點 初始狀態時,待排序序列為a1,a2,...an,已排序序列為空。 第一趟排序,從

12-看圖理解資料結構與系列(氣泡排序)

氣泡排序 氣泡排序是一種很簡單的排序演算法,主要思想就是不斷走訪待排序序列,每次只比較兩個相鄰元素,如果這倆元素順序不符合要求則對換它們,不斷重複知道沒有相鄰元素需要對換。在不斷走訪比較過程中,越大的元素經過交換會慢慢走到數列頂端,所以看起來它就像氣泡一樣不斷往上冒,於是就叫冒泡。

16-看圖理解資料結構與系列(快速排序)

快速排序 快速排序由C.A.R.Hoare在1962年提出,是氣泡排序的一種改進。其基本思想為:通過一趟排序將待排序資料分割成獨立的兩部分,其中一部分的所有值都比另一部分的所有值都小,然後再對分割的兩部分分別進行快速排序,整個過程可以遞迴進行,最終所有資料變為有序序列。 排序要點

資料結構與系列12--排序優化

如何選擇合適的排序演算法?** 這裡有前面講過的各種演算法是時間複雜度的對比圖,大家可以看看下。 如果我們要選擇一個通用的排序演算法,前面講到的線性排序演算法顯然是不合適的,因為它的適用場景非常有限,儘管他是速度最快的。 如果對於小規模資料可以選擇時間複雜度O(n^2的演算法),但是對

系列——選擇排序(Selection Sort)

選擇排序(Selection sort)是一種簡單直觀的排序演算法。它的工作原理是每一次從待排序的資料元素中選出最小(或最大)的一個元素,存放在序列的起始位置,直到全部待排序的資料元素排完。 選擇排序是不穩定的排序方法。時間複雜度是O(n^2)。 在完全隨機陣列的條件下

排序系列---希爾排序(C++)

希爾排序:明白它的原理之後,希爾演算法其實就是更加先進的一種插入排序,之前的直接插入排序是逐個比較,找到合適的位置就插入,希爾排序是跳躍式的插入,拿當前的數和n(自己定義的跳躍數)個之前的數比較,較小的數位置提前。希爾演算法相當於先把整個序列分割成幾塊序列,塊和

排序系列:歸併排序(Merge sort)(C語言)

通俗理解:運用分而治之的思想,編寫遞迴函式,將大陣列排序轉化為小陣列排序,最後再將其合併。void merge_sort(int*p,int low,int high) { int mid = (low+high)/2; if (low <high) { m