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