1、經常使用的排序算法有以下幾種:html
一、冒泡排序java
二、選擇排序git
三、插入排序算法
四、快速排序數組
五、堆排序數據結構
六、歸併排序dom
七、計數排序測試
八、基數排序動畫
接下來從原理、代碼實現和測試三步對它們進行分析。ui
2、算法分析、實現、測試
一、基類BaseSort.java
該類包含各個排序類的共用部分和生成測試數據功能,其餘全部排序算法類都繼承該類。具體代碼以下:
package test.algorithm; import java.io.BufferedWriter; import java.io.File; import java.io.FileWriter; import java.util.Random; public class BaseSort { public static int length = 500000; // 測試數據量 public static int[] data = new int[length]; // 測試數據集 public static int lengthOfDigit = 8; // 測試數據的最大位數 public static int maxValue = (int) Math.pow(10, lengthOfDigit); // 測試數據的最大值 /** * 輸出數據到控制檯 * * @param data */ protected static void printArr(int[] data) { for (int i = 0; i < data.length; i++) { System.out.print(data[i] + " "); } System.out.println(); } /** * 輸出數據到文件 * * @param data * @param path */ protected static void printToFile(int[] data, String path) { try { File f = new File(path); if (!f.exists() && !f.createNewFile()) { System.out.println("文件建立失敗!"); return; } BufferedWriter output = new BufferedWriter(new FileWriter(f)); for (int i = 0; i < data.length; i++) { output.write(data[i] + "\r\n"); } output.close(); } catch (Exception e) { e.printStackTrace(); } } /** * 交換兩個數據 * * @param data * @param i * @param j */ protected static void swap(int[] data, int i, int j) { data[i] = data[i] + data[j]; data[j] = data[i] - data[j]; data[i] = data[i] - data[j]; } /** * 生成測試數據集 */ protected static void genData() { Random r = new Random(); for (int i = 0; i < length; i++) { data[i] = r.nextInt(maxValue); } } }
二、冒泡排序
(1)算法原理:冒泡排序簡單的說就是重複比較相鄰的兩個元素,若是順序錯誤就交換使之順序正確。時間複雜度Ο(n2) ,空間複雜度爲Ο(1)。
(2)動畫演示
(3)代碼實現
package test.algorithm; public class BubbleSort extends BaseSort { public static void bubblesort(int[] data, int start, int end) throws Exception { if (data == null || start < 0 || end < 0) { throw new Exception("參數錯誤"); } for (int i = start; i < end; i++) { for (int j = end; j > i; j--) { if (data[j] < data[j - 1]) { swap(data, j, j - 1); } } } } public static void main(String[] args) throws Exception { genData(); long startTime = System.currentTimeMillis(); bubblesort(data, 0, length - 1); long endTime = System.currentTimeMillis(); System.out.println("冒泡排序,測試" + length + "個隨機數據,耗時:" + (endTime - startTime) + "毫秒"); } }
(4)測試結果
50萬數據,時間太長。
3、選擇排序
(1)算法原理:選擇排序每次從未排序的數據中選擇最小的,而後將該元素放到已經排好序的數據末尾,循環直到結束。時間複雜度Ο(n2) ,空間複雜度爲Ο(1)。
(2)動畫演示
(3)代碼實現
package test.algorithm; public class SelectSort extends BaseSort { public static void selectsort(int[] data, int start, int end) throws Exception { if (data == null || start < 0 || end < 0) { throw new Exception("參數錯誤"); } for (int i = start; i < end; i++) { int minIndex = i; for (int j = i + 1; j <= end; j++) { if (data[j] < data[minIndex]) { minIndex = j; } } if (i != minIndex) { swap(data, i, minIndex); } } } public static void main(String[] args) throws Exception { genData(); long startTime = System.currentTimeMillis(); selectsort(data, 0, length - 1); long endTime = System.currentTimeMillis(); System.out.println("選擇排序,測試" + length + "個隨機數據,耗時:" + (endTime - startTime) + "毫秒"); } }
(4)測試結果
(最大8位)
四、插入排序
(1)算法原理:插入排序每次從未排序的數據中選取第一個,而後在已經排好序的數據中找到該元素的正確位置,將該元素放到該正確位置,循環直到結束。時間複雜度Ο(n2) ,空間複雜度爲Ο(1)。
(2)動畫演示
(3)代碼實現
package test.algorithm; public class InsertSort extends BaseSort { public static void insertsort(int[] data, int start, int end) throws Exception { if (data == null || start < 0 || end < 0) { throw new Exception("參數錯誤"); } for (int i = start; i < end; i++) { int curr = data[i + 1]; int j = i + 1; while (j > start && data[j - 1] > curr) { data[j] = data[j - 1]; j--; } if (j != i + 1) { data[j] = curr; } } } public static void main(String[] args) throws Exception { genData(); long startTime = System.currentTimeMillis(); insertsort(data, 0, length - 1); long endTime = System.currentTimeMillis(); System.out.println("插入排序,測試" + length + "個隨機數據,耗時:" + (endTime - startTime) + "毫秒"); } }
(4)測試結果
(最大8位)
五、快速排序
(1)算法原理:快速排序每次使用一個元素做爲基準,將比基準小的放到前面,比基準大的放到後面,再分別對先後兩部分使用快速排序。基準的選擇能夠任意(下面的代碼使用第一個元素)。平均狀況下時間複雜度爲Ο(n log n), 最壞狀況下時間複雜度爲Ο(n2) ;空間複雜度爲Ο(n)。
(2)動畫演示
(3)代碼實現
package test.algorithm; public class QuickSort extends BaseSort { public static int partition(int[] data, int start, int end) throws Exception { int lastOne = data[start]; // 使用第一個元素做爲基準 int i = start, j = end; while (i < j) { while (i < j && data[j] >= lastOne) { j--; } data[i] = data[j]; while (i < j && data[i] <= lastOne) { i++; } data[j] = data[i]; } data[i] = lastOne; return i; } public static void quicksort(int[] data, int start, int end) throws Exception { if (data == null || data.length <= 0 || data.length < end || data.length < start) { throw new Exception("數據爲空或者索引超出範圍"); } int index = partition(data, start, end); if (index > start) { quicksort(data, start, index - 1); } if (index < end) { quicksort(data, index + 1, end); } } public static void main(String[] args) throws Exception { genData(); long startTime = System.currentTimeMillis(); quicksort(data, 0, length - 1); long endTime = System.currentTimeMillis(); System.out.println("快速排序,測試" + length + "個隨機數據,耗時:" + (endTime - startTime) + "毫秒"); } }
(4)測試結果
(最大8位)
六、堆排序
(1)算法原理:堆排序使用堆這種數據結構進行排序。堆是一個徹底二叉樹,知足:大根堆的任意節點的左右子節點(若是有)的值都比自己節點值小,小根堆反之。首先將全部數據生成堆,而後每次將第一個元素和未排好序的數據的最後一個交換,再將除最後一個元素外的數據調整成一個堆。時間複雜度爲Ο(n log n), 且空間複雜度爲Ο(1)。
(2)動畫演示
(3)代碼實現
package test.algorithm; public class HeapSort extends BaseSort { private static void adjust(int[] data, int start, int end) { int remain = data[start]; int i = start * 2; int j = start; while (i <= end) { if (i + 1 <= end && data[i] < data[i + 1]) { i++; } if (data[i] < remain) { break; } data[j] = data[i]; j = i; i *= 2; } data[j] = remain; } // 使用大根堆排序 public static void heapsort(int[] data, int start, int end) throws Exception { if (data == null || data.length <= 0 || data.length < end || data.length < start) { throw new Exception("數據爲空或者索引超出範圍"); } for (int i = end / 2; i >= start; i--) { adjust(data, i, end); } for (int i = end; i > start; i--) { swap(data, start, i); adjust(data, start, i - 1); } } public static void main(String[] args) throws Exception { genData(); long startTime = System.currentTimeMillis(); heapsort(data, 1, length - 1); long endTime = System.currentTimeMillis(); System.out.println("堆排序,測試" + length + "個隨機數據,耗時:" + (endTime - startTime) + "毫秒"); } }
(4)測試結果
(最大8位)
七、歸併排序
(1)算法原理:歸併排序是分治法的一個典型應用。將待排序數據分紅兩部分,先將這兩部分排好序,而後將兩部分組合成一個有序的總體。時間複雜度爲Ο(n log n),空間複雜度爲Ο(n)。
(2)動畫演示
(3)代碼實現
package test.algorithm; public class MergeSort extends BaseSort { public static void mergesort(int[] data, int start, int end) throws Exception { if (data == null || start < 0 || end < 0) { throw new Exception("參數錯誤"); } if (start < end) { int mid = (start + end) / 2; mergesort(data, start, mid); mergesort(data, mid + 1, end); int[] tmp = new int[end - start + 1]; int i = start, j = mid + 1, k = 0; while (i <= mid && j <= end) { if (data[i] < data[j]) { tmp[k++] = data[i++]; } else { tmp[k++] = data[j++]; } } while (i <= mid) { tmp[k++] = data[i++]; } while (j <= end) { tmp[k++] = data[j++]; } i = start; j = 0; while (i <= end) { data[i++] = tmp[j++]; } } } public static void main(String[] args) throws Exception { genData(); long startTime = System.currentTimeMillis(); mergesort(data, 0, length - 1); long endTime = System.currentTimeMillis(); System.out.println("歸併排序,測試" + length + "個隨機數據,耗時:" + (endTime - startTime) + "毫秒"); } }
(4)測試結果
(最大8位)
八、計數排序
(1)算法原理:計數排序是惟一一個不經過比較的排序算法。時間複雜度爲Ο(n),空間複雜度爲Ο(m)。(m爲排序的數據範圍)
主要思想是待排序的數據大小必須在必定範圍內(最大值爲m),開一個m的數組,記錄每一個元素出現的次數。基於每一個元素出現的次數,而後將每一個元素以前的元素出現的次數累加(包括自己的值)做爲新值,獲得的是不比本身小的元素個數,該數值就是該元素應該在的位置。具體下方的動畫演示連接。
(2)動畫演示:請參考http://www.cs.usfca.edu/~galles/visualization/flash.html
(3)代碼實現
package test.algorithm; public class CountSort extends BaseSort { public static void countsort(int[] data, int start, int end) throws Exception { if (data == null || start < 0 || end < 0) { throw new Exception("參數錯誤"); } int[] countArray = new int[maxValue]; // 清零 for (int i = 0; i < maxValue; i++) { countArray[i] = 0; } // 計數 for (int i = start; i <= end; i++) { countArray[data[i]]++; } // 累加 for (int i = 1; i < maxValue; i++) { countArray[i] += countArray[i - 1]; } int[] sortedArray = new int[length]; // 排序 for (int i = start; i <= end; i++) { countArray[data[i]]--; sortedArray[countArray[data[i]]] = data[i]; } // 複製 for (int i = start; i <= end; i++) { data[i] = sortedArray[i]; } } public static void main(String[] args) throws Exception { genData(); long startTime = System.currentTimeMillis(); countsort(data, 0, length - 1); long endTime = System.currentTimeMillis(); System.out.println("計數排序,測試" + length + "個隨機數據,耗時:" + (endTime - startTime) + "毫秒"); } }
(4)測試結果
(最大8位)
九、基數排序
(1)算法原理:基數排序是從右到左一位一位的比較排序的算法。待排序元素須要有已知最大位數,先按照個位排序(個位相同的以出現順序爲準),而後按照十位進行排序......時間複雜度爲Ο(nm),空間複雜度爲Ο(n)。(n爲元素個數,m爲元素的位數)
(2)動畫演示:請參考http://www.cs.usfca.edu/~galles/visualization/flash.html
(3)代碼實現
package test.algorithm; public class RadixSort extends BaseSort { private static int digitOfIndex(int data, int index) throws Exception { if (index < 1) { throw new Exception("位數不能小於1"); } data = data / ((int) Math.pow(10, index - 1)); int digit = data % 10; return digit; } public static void radixsort(int[] data, int start, int end, int lengthOfDigit) throws Exception { if (data == null || start < 0 || end < 0) { throw new Exception("參數錯誤"); } for (int i = 1; i <= lengthOfDigit; i++) { int[][] list = new int[10][length]; // list[x][0]保留該組數據個數 for (int j = start; j <= end; j++) { int digit = digitOfIndex(data[j], i); list[digit][++list[digit][0]] = data[j]; } int k = start; for (int j = 0; j <= 9; j++) { for (int l = 1; l <= list[j][0]; l++) { data[k++] = list[j][l]; } } } } public static void main(String[] args) throws Exception { genData(); long startTime = System.currentTimeMillis(); radixsort(data, 0, length - 1, lengthOfDigit); long endTime = System.currentTimeMillis(); System.out.println("基數排序,測試" + length + "個隨機數據,耗時:" + (endTime - startTime) + "毫秒"); } }
(4)測試結果
(最大8位)
3、各算法對比
以下表格對上述各算法進行了簡要對比,表格中n爲元素數量。
時間複雜度 | 空間複雜度 | 測試500 | 測試5千 | 測試5萬 | 測試50萬,6位 | 測試50萬,8位 | 備註 | |
冒泡排序 | Ο(n2) | Ο(1) | 5ms | 32ms | 3722ms | 時間太長 | 時間太長 | |
選擇排序 | Ο(n2) | Ο(1) | 2ms | 14ms | 932ms | 92339ms | 92540ms | |
插入排序 | Ο(n2) | Ο(1) | 1ms | 11ms | 614ms | 60390ms | 59402ms | |
快速排序 | Ο(n log n) | Ο(n) | 0ms | 4ms | 12ms | 58ms | 59ms | |
堆排序 | Ο(n log n) | Ο(1) | 0ms | 4ms | 7ms | 59ms | 59ms | |
歸併排序 | Ο(n log n) | Ο(n) | 0ms | 6ms | 31ms | 98ms | 98ms | |
計數排序 | Ο(n) | Ο(m) | 8ms | 8ms | 9ms | 25ms | 256ms | m爲排序的數據範圍 |
基數排序 | Ο(nm) | Ο(n) | 1ms | 4ms | 21ms | 53ms | 65ms | m爲元素的最大位數 |
從對比結果能夠看出:
一、最簡單的冒泡排序毫無疑問效率是最差的,並且與其餘算法差異很明顯;
二、選擇排序和插入排序算法實現難度差很少,效率也相差不大;
三、快速排序、堆排序、歸併排序、計數排序、基數排序從實際測試來看,時間消耗上差異都在同一個數量級;
四、除了計數排序是基於數據的位數,對數據位數(如8位數比6位數就相差一個數量級)比較敏感,其餘排序算法基本上沒有影響。