算法分類
常見算法可以分為兩大類:
非線性時間比較類排序:通過比較來決定元素間的相對次序,由於其時間復雜度不能突破O(nlogn),因此稱為非線性時間比較類排序。
線性時間非比較類排序:不通過比較來決定元素間的相對次序,它可以突破基於比較排序的時間下界,以線性時間運行,因此稱為線性時間非比較類排序。
算法復雜度:
1、冒泡排序
思路:外層循環從1到n-1,內循環從當前外層的元素的下一個位置開始,依次和外層的元素比較,出現逆序就交換,通過與相鄰元素的比較和交換來把小的數交換到最前面。
1 for(int i=0;i<arr.length-1;i++){//外層循環控制排序趟數 2 for(int j=0;j<arr.length-1-i;j++){//內層循環控制每一趟排序多少次 3 if(arr[j]>arr[j+1]){ 4 int temp=arr[j]; 5 arr[j]=arr[j+1]; 6 arr[j+1]=temp; 7 } 8 } 9 }
2、選擇排序
思路:冒泡排序是通過相鄰的比較和交換,每次找個最小值。選擇排序是:首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再從剩余未排序元素中繼續尋找最小(大)元素,然后放到已排序序列的末尾。以此類推,直到所有元素均排序完畢。
1 private static void sort(int[] array) { 2 int n = array.length; 3 for (int i = 0; i < n-1; i++) { 4 int min = i; 5 for (int j = i+1; j < n; j++) { 6 if (array[j] < array[min]){//尋找最小數 7 min = j; //將最小數的索引賦值 8 } 9 } 10 int temp = array[i]; 11 array[i] = array[min]; 12 array[min] = temp; 13 14 } 15 } 16
3、插入排序
思路:通過構建有序序列,對於未排序數據,在已排序序列中從后向前掃描,找到相應位置並插入。可以理解為玩撲克牌時的理牌;
1 private static void sort(int[] array) { 2 int n = array.length; 3 /** 4 *從第二位數字開始,每一個數字都試圖跟它的前一個比較並交換,並重復;直到前一個數字不存在或者比它小或相等時停下來 5 **/ 6 for (int i = 1; i < n; i++) {//從第二個數開始 7 int key = array[i]; 8 int j = i -1; 9 while (j >= 0 && array[j]>key) { 10 array[j + 1] = array[j]; //交換 11 j--; //下標向前移動 12 } 13 array[j+1] = key; 14 } 15 }
4、希爾排序
思路:希爾排序是插入排序的一種高效率的實現,也叫縮小增量排序。先將整個待排記錄序列分割成為若干子序列分別進行直接插入排序,待整個序列中的記錄基本有序時再對全體記錄進行一次直接插入排序。
問題:增量的序列取法?
關於取法,沒有統一標准,但最后一步必須是1;因為不同的取法涉及時間復雜度不一樣,具體了解可以參考《數據結構與算法分析》;一般以length/2為算法。(再此以gap=gap*3+1為公式)
1 private static void sort(int[] array) { 2 int n = array.length; 3 int h = 1; 4 while (h<n/3) { //動態定義間隔序列 5 h = 3*h +1; 6 } 7 while (h >= 1) { 8 for (int i = h; i < n; i++) { 9 for (int j = i; j >= h && (array[j] < array[j - h]); j -= h) { 10 int temp = array[j]; 11 array[j] = array[j - h]; 12 array[j-h]= temp; 13 } 14 } 15 h /=3; 16 } 17 }
5、歸並排序
思路:將已有序的子序列合並,得到完全有序的序列;即先使每個子序列有序,再使子序列段間有序。若將兩個有序表合並成一個有序表,稱為2-路歸並。它使用了遞歸分治的思想;相當於:左半邊用盡,則取右半邊元素;右半邊用盡,則取左半邊元素;右半邊的當前元素小於左半邊的當前元素,則取右半邊元素;右半邊的當前元素大於左半邊的當前元素,則取左半邊的元素。
自頂向下:
1 private static void mergeSort(int[] array) { 2 int[] aux = new int[array.length]; 3 sort(array, aux, 0, array.length - 1); 4 } 5 6 private static void sort(int[] array, int[] aux, int lo, int hi) { 7 if (hi<=lo) return; 8 int mid = lo + (hi - lo)/2; 9 sort(array, aux, lo, mid); 10 sort(array, aux, mid + 1, hi); 11 merge(array, aux, lo, mid, hi); 12 } 13 14 private static void merge(int[] array, int[] aux, int lo, int mid, int hi) { 15 System.arraycopy(array,0,aux,0,array.length); 16 int i = lo, j = mid + 1; 17 for (int k = lo; k <= hi; k++) { 18 if (i>mid) array[k] = aux[j++]; 19 else if (j > hi) array[k] = aux[i++]; 20 else if (aux[j]<aux[i]) array[k] = aux[j++]; 21 else array[k] = aux[i++]; 22 } 23 }
自底向上:
1 public static void sort(int[] array) { 2 int N = a.length; 3 int[] aux = new int[N]; 4 for (int n = 1; n < N; n = n+n) { 5 for (int i = 0; i < N-n; i += n+n) { 6 int lo = i; 7 int m = i+n-1; 8 int hi = Math.min(i+n+n-1, N-1); 9 merge(array, aux, lo, m, hi); 10 } 11 } 12 } 13 14 private static void merge(int[] array, int[] aux, int lo, int mid, int hi) { 15 for (int k = lo; k <= hi; k++) { 16 aux[k] = array[k]; 17 } 18 // merge back to a[] 19 int i = lo, j = mid+1; 20 for (int k = lo; k <= hi; k++) { 21 if (i > mid) array[k] = aux[j++]; // this copying is unneccessary 22 else if (j > hi) array[k] = aux[i++]; 23 else if (aux[j]<aux[i]) array[k] = aux[j++]; 24 else array[k] = aux[i++]; 25 } 26 }
缺點:因為是Out-place sort,因此相比快排,需要很多額外的空間。
為什么歸並排序比快速排序慢?
答:雖然漸近復雜度一樣,但是歸並排序的系數比快排大。
對於歸並排序有什么改進?
答:就是在數組長度為k時,用插入排序,因為插入排序適合對小數組排序。在算法導論思考題2-1中介紹了。復雜度為O(nk+nlg(n/k)) ,當k=O(lgn)時,復雜度為O(nlgn)
例子:
1 private static int mark = 0; 2 /** 3 * 歸並排序 4 */ 5 private static int[] sort(int[] array, int low, int high) { 6 int mid = (low + high) / 2; 7 if (low < high) { 8 mark++; 9 System.out.println("正在進行第" + mark + "次分隔,得到"); 10 System.out.println("[" + low + "-" + mid + "] [" + (mid + 1) + "-" + high + "]"); 11 // 左邊數組 12 sort(array, low, mid); 13 // 右邊數組 14 sort(array, mid + 1, high); 15 // 左右歸並 16 merge(array, low, mid, high); 17 } 18 return array; 19 } 20 21 /** 22 * 對數組進行歸並 23 * 24 * @param array 25 * @param low 26 * @param mid 27 * @param high 28 */ 29 private static void merge(int[] array, int low, int mid, int high) { 30 System.out.println("合並:[" + low + "-" + mid + "] 和 [" + (mid + 1) + "-" + high + "]"); 31 int[] temp = new int[high - low + 1]; 32 int i = low;// 左指針 33 int j = mid + 1;// 右指針 34 int k = 0; 35 // 把較小的數先移到新數組中 36 while (i <= mid && j <= high) { 37 if (array[i] < array[j]) { 38 temp[k++] = array[i++]; 39 } else { 40 temp[k++] = array[j++]; 41 } 42 } 43 // 兩個數組之一可能存在剩余的元素 44 // 把左邊剩余的數移入數組 45 while (i <= mid) { 46 temp[k++] = array[i++]; 47 } 48 // 把右邊邊剩余的數移入數組 49 while (j <= high) { 50 temp[k++] = array[j++]; 51 } 52 // 把新數組中的數覆蓋array數組 53 for (int m = 0; m < temp.length; m++) { 54 array[m + low] = temp[m]; 55 } 56 } 57 58 /** 59 * 歸並排序 60 */ 61 public static int[] sort(int[] array) { 62 return sort(array, 0, array.length - 1); 63 } 64 65 public static void main(String[] args) { 66 int[] array = { 3, 5, 2, 6, 2 }; 67 int[] sorted = sort(array); 68 System.out.println("最終結果"); 69 for (int i : sorted) { 70 System.out.print(i + " "); 71 } 72 }
6、快速排序
思路:通過一趟排序將待排記錄分隔成獨立的兩部分,其中一部分記錄的關鍵字均比另一部分的關鍵字小,則可分別對這兩部分記錄繼續進行排序,以達到整個序列有序。
private static void sort(int[] array) { shuffle(array); sort(array, 0, array.length - 1); } private static void sort(int[] array, int lo, int hi) { if(hi<=lo+M) { Insert.sort(a,lo,hi); return; } int lt = lo, gt = hi; int v = array[lo]; int i = lo; while (i <= gt) { if (array[i]<v) exch(array, lt++, i++); else if (array[i]>v) exch(array, i, gt--); else i++; } // a[lo..lt-1] < v = a[lt..gt] < a[gt+1..hi]. sort(array, lo, lt-1); sort(array, gt+1, hi); } private static void exch(int[] a, int i, int j) { int swap = a[i]; a[i] = a[j]; a[j] = swap; } /** *打亂數組 */ private static void shuffle(int[] array) { Random random = new Random(System.currentTimeMillis()); if (array == null) throw new NullPointerException("argument array is null"); int n = array.length; for (int i = 0; i < n; i++) { int r = i + random.nextInt(n-i); // between i and n-1 int temp = array[i]; array[i] = array[r]; array[r] = temp; } }
代碼例子:
1 package test; 2 3 public class s { 4 public static void main(String[] args) { 5 int[] arr = { 5,2,4,9,7 }; 6 sort(arr, 0, arr.length - 1); 7 } 8 public static void sort(int arr[], int low, int high) { 9 int l = low; 10 int h = high; 11 int k = arr[low]; 12 while (l < h) { 13 // 從后往前比較 14 while (l < h && arr[h] >= k ){ // 如果沒有比關鍵值小的,比較下一個,直到有比關鍵值小的交換位置,然后又從前往后比較 15 h--;// h=6 16 } 17 if (l < h) { 18 int temp = arr[h]; 19 arr[h] = arr[l]; 20 arr[l] = temp; 21 //進行過一次替換后,沒必要將替換后的兩值再次比較,所以i++直接下一位與k對比 22 l++; 23 } 24 // 從前往后比較 25 while (l < h && arr[l] <= k) { // 如果沒有比關鍵值大的,比較下一個,直到有比關鍵值大的交換位置 26 l++; 27 } 28 if (l < h) { 29 int temp = arr[h]; 30 arr[h] = arr[l]; 31 arr[l] = temp; 32 h--; 33 } 34 // 此時第一次循環比較結束,關鍵值的位置已經確定了。左邊的值都比關鍵值小,右邊的值都比關鍵值大,但是兩邊的順序還有可能是不一樣的,進行下面的遞歸調用 35 } 36 print(arr); 37 System.out.print("l=" + (l + 1) + "h=" + (h + 1) + "k=" + k + "\n"); 38 // 遞歸 39 if (l > low)//先判斷l>low再次經行左邊排序 40 sort(arr, low, l - 1);// 左邊序列。第一個索引位置到關鍵值索引-1 41 if (h < high)//左邊依次排序執行完遞歸后,彈棧進行右邊排序 42 sort(arr, l + 1, high);// 右邊序列。從關鍵值索引+1到最后一個 43 } 44 // 打印數組的方法 45 private static void print(int[] arr) { 46 System.out.print("["); 47 for (int i = 0; i < arr.length; i++) { 48 if (i != (arr.length - 1)) { 49 System.out.print(arr[i] + ","); 50 } else { 51 System.out.print(arr[i] + "]"); 52 System.out.println(); 53 } 54 } 55 } 56 }

7、堆排序
思路:堆積是一個近似完全二叉樹的結構,並同時滿足堆積的性質:即子結點的鍵值或索引總是小於(或者大於)它的父節點。
1 public static void sort(int[] a){ 2 int N = a.length; 3 int[] keys = new int[N+1]; 4 //注意,堆的數據結構是從1開始的,0不用 5 for (int i = 1; i < keys.length; i++) { 6 keys[i] = a[i-1]; 7 } 8 // //構造堆,使得堆是有序的 9 for(int k = N/2;k>=1;k--) sink(keys,k,N); 10 //排序,相當於毀掉堆 11 while(N>1){ 12 exch(keys,1,N--); 13 sink(keys,1,N); 14 } 15 //重新寫回數組 16 for (int i = 0; i < a.length; i++) { 17 a[i] = keys[i+1]; 18 } 19 } 20 21 private static void sink(int[] a, int k, int N) { 22 // TODO Auto-generated method stub 23 while(2*k<=N){ 24 int j = 2*k; 25 if (j < N && less(a[j], a[j+1])) j++; 26 if (less(a[j], a[k])) break; 27 exch(a, k, j); 28 k = j; 29 } 30 } 31 32 private static boolean less(int k, int j) { 33 // TODO Auto-generated method stub 34 return k < j; 35 } 36 37 private static void exch(int[] a, int i, int n) { 38 // TODO Auto-generated method stub 39 int temp = a[i]; 40 a[i] = a[n]; 41 a[n] = temp; 42 }
代碼例子:
1 package test; 2 3 public class dui { 4 /** 5 * 調整為小頂堆(排序后結果為從大到小) 6 * 7 * @param array是待調整的堆數組 8 * @param s是待調整的數組元素的位置 9 * @param length是數組的長度 10 * 11 */ 12 public static void heapAdjustS(int[] array, int s, int length) { 13 int tmp = array[s]; 14 int child = 2 * s + 1;// 左孩子結點的位置 15 System.out.println("待調整結點為:array[" + s + "] = " + tmp); 16 while (child < length) { 17 // child + 1 是當前調整結點的右孩子 18 // 如果有右孩子且小於左孩子,使用右孩子與結點進行比較,否則使用左孩子 19 if (child + 1 < length && array[child] > array[child + 1]) { 20 child++; 21 } 22 System.out.println("將與子孩子 array[" + child + "] = " + array[child] + " 進行比較"); 23 // 如果較小的子孩子比此結點小 24 if (array[s] > array[child]) { 25 System.out.println("子孩子比其小,交換位置"); 26 array[s] = array[child];// 把較小的子孩子向上移動,替換當前待調整結點 27 s = child;// 待調整結點移動到較小子孩子原來的位置 28 array[child] = tmp; 29 child = 2 * s + 1;// 繼續判斷待調整結點是否需要繼續調整 30 31 if (child >= length) { 32 System.out.println("沒有子孩子了,調整結束"); 33 } else { 34 System.out.println("繼續與新的子孩子進行比較"); 35 } 36 // continue; 37 } else { 38 System.out.println("子孩子均比其大,調整結束"); 39 break;// 當前待調整結點小於它的左右孩子,不需調整,直接退出 40 } 41 } 42 } 43 44 /** 45 * 調整為大頂堆(排序后結果為從小到大) 46 * 47 * @param array是待調整的堆數組 48 * @param s是待調整的數組元素的位置 49 * @param length是數組的長度 50 * 51 */ 52 public static void heapAdjustB(int[] array, int s, int length) { 53 int tmp = array[s]; 54 int child = 2 * s + 1;// 左孩子結點的位置 55 System.out.println("待調整結點為:array[" + s + "] = " + tmp); 56 while (child < length) { 57 // child + 1 是當前調整結點的右孩子 58 // 如果有右孩子且大於左孩子,使用右孩子與結點進行比較,否則使用左孩子 59 if (child + 1 < length && array[child] < array[child + 1]) { 60 child++; 61 } 62 System.out.println("將與子孩子 array[" + child + "] = " + array[child] + " 進行比較"); 63 // 如果較大的子孩子比此結點大 64 if (array[s] < array[child]) { 65 System.out.println("子孩子比其大,交換位置"); 66 array[s] = array[child];// 把較大的子孩子向上移動,替換當前待調整結點 67 s = child;// 待調整結點移動到較大子孩子原來的位置 68 array[child] = tmp; 69 child = 2 * s + 1;// 繼續判斷待調整結點是否需要繼續調整 70 71 if (child >= length) { 72 System.out.println("沒有子孩子了,調整結束"); 73 } else { 74 System.out.println("繼續與新的子孩子進行比較"); 75 } 76 // continue; 77 } else { 78 System.out.println("子孩子均比其小,調整結束"); 79 break;// 當前待調整結點大於它的左右孩子,不需調整,直接退出 80 } 81 } 82 } 83 84 /** 85 * 堆排序算法 86 * 87 * @param array 88 * @param inverse true 為倒序排列,false 為正序排列 89 */ 90 public static void heapSort(int[] array, boolean inverse) { 91 // 初始堆 92 // 最后一個有孩子的結點位置 i = (length - 1) / 2, 以此向上調整各結點使其符合堆 93 System.out.println("初始堆開始"); 94 for (int i = (array.length - 1) / 2; i >= 0; i--) { 95 if (inverse) { 96 heapAdjustS(array, i, array.length); 97 } else { 98 heapAdjustB(array, i, array.length); 99 } 100 } 101 System.out.println("初始堆結束"); 102 for (int i = array.length - 1; i > 0; i--) { 103 // 交換堆頂元素H[0]和堆中最后一個元素 104 int tmp = array[i]; 105 array[i] = array[0]; 106 array[0] = tmp; 107 // 每次交換堆頂元素和堆中最后一個元素之后,都要對堆進行調整 108 if (inverse) { 109 heapAdjustS(array, 0, i); 110 } else { 111 heapAdjustB(array, 0, i); 112 } 113 } 114 } 115 116 public static void main(String[] args) { 117 int[] array = { 49, 38, 65, 97, 76, 13, 27, 49 }; 118 heapSort(array, false); 119 for (int i : array) { 120 System.out.print(i + " "); 121 } 122 } 123 124 }
8、計數排序
思路:將輸入的數據值轉化為鍵存儲在額外開辟的數組空間中。 作為一種線性時間復雜度的排序,計數排序要求輸入的數據必須是有確定范圍的整數。
找出待排序的數組中最大和最小的元素;
統計數組中每個值為i的元素出現的次數,存入數組C的第i項;
對所有的計數累加(從C中的第一個元素開始,每一項和前一項相加);
反向填充目標數組:將每個元素i放在新數組的第C(i)項,每放一個元素就將C(i)減去1。
1 /** 2 * 輸入數組的元素都是介於0..k之間的 3 * @param data 待排序數組 4 * @param k 最大元素 5 * @return 排序結果 6 */ 7 public static int[] sort(int[] data, int k) { 8 // 存放臨時數據的數組tmp,初始元素都是0;k為數組中最大元素 9 int[] tmp = new int[k + 1]; 10 11 // 計算數組中每個元素i出現的次數,存入數組tmp中的第i項,即原數組中的元素值為tmp數組中的下標 12 for (int i = 0; i <= data.length - 1; i++) { 13 tmp[data[i]]++; 14 } 15 // 計算數組中小於等於每個元素的個數,即從tmp中的第一個元素開始,每一項和前一項相加 16 for (int j = 1; j <= k; j++) { 17 tmp[j] = tmp[j] + tmp[j - 1]; 18 } 19 // result數組用來來存放排序結果 20 int[] result = new int[data.length]; 21 for (int i = data.length - 1; i >= 0; i--) { 22 result[tmp[data[i]] - 1] = data[i]; 23 tmp[data[i]]--; 24 } 25 return result; 26 }
代碼例子:
1 package test; 2 3 public class jishu { 4 public static int[] countingSort(int[] theArray) { 5 int[] lastArray = new int[theArray.length]; 6 for(int i = 0; i < theArray.length; i++) { 7 int count = 0; 8 for(int j = 0; j < theArray.length; j++) { 9 if(theArray[i] > theArray[j]) { 10 count++; 11 } 12 } 13 lastArray[count] = theArray[i]; 14 } 15 return lastArray; 16 } 17 public static void main(String[] args) { 18 int []theArray = {6, 4, 5, 1, 8, 7, 2, 3}; 19 System.out.print("之前的排序:"); 20 for(int i = 0; i < theArray.length; i++) { 21 System.out.print(theArray[i] + " "); 22 } 23 24 int []resultArray = countingSort(theArray); 25 26 System.out.print("計數排序:"); 27 for(int i = 0; i < resultArray.length; i++) { 28 System.out.print(resultArray[i] + " "); 29 } 30 } 31 32 }
9、桶排序
思路:桶排序是計數排序的升級版。它利用了函數的映射關系,高效與否的關鍵就在於這個映射函數的確定。桶排序 (Bucket sort)的工作的原理:假設輸入數據服從均勻分布,將數據分到有限數量的桶里,每個桶再分別排序(有可能再使用別的排序算法或是以遞歸方式繼續使用桶排序進行排)。
設置一個定量的數組當作空桶;
遍歷輸入數據,並且把數據一個一個放到對應的桶里去;
對每個不是空的桶進行排序;
從不是空的桶里把排好序的數據拼接起來。
1 public static void bucketSort(double array[]) { 2 int length = array.length; 3 ArrayList arrList[] = new ArrayList[length]; 4 for (int i = 0; i < length; i++) { 5 //0.7到0.79放在第8個桶里,編號7;第一個桶放0到0.09 6 int temp = (int) Math.floor(10 * array[i]); 7 if (null == arrList[temp]) 8 arrList[temp] = new ArrayList(); 9 arrList[temp].add(array[i]); 10 } 11 // 對每個桶中的數進行插入排序 12 for (int i = 0; i < length; i++) { 13 if (null != arrList[i]) { 14 Collections.sort(arrList[i]); 15 } 16 } 17 int count = 0; 18 for (int i = 0; i < length; i++) { 19 if (null != arrList[i]) { 20 Iterator iter = arrList[i].iterator(); 21 while (iter.hasNext()) { 22 Double d = (Double) iter.next(); 23 array[count] = d; 24 count++; 25 } 26 } 27 } 28 }
代碼例子:
1 package test; 2 3 public class tong { 4 private int[] buckets; 5 private int[] array; 6 7 public tong(int range,int[] array){ 8 this.buckets = new int[range]; 9 this.array = array; 10 } 11 12 /*排序*/ 13 public void sort(){ 14 if(array!=null && array.length>1){ 15 for(int i=0;i<array.length;i++){ 16 buckets[array[i]]++; 17 } 18 } 19 } 20 21 /*排序輸出*/ 22 public void sortOut(){ 23 //倒序輸出數據 24 for (int i=buckets.length-1; i>=0; i--){ 25 for(int j=0;j<buckets[i];j++){ 26 System.out.print(i+"\t"); 27 } 28 } 29 } 30 31 32 public static void main(String[] args) { 33 testBucketsSort(); 34 } 35 36 private static void testBucketsSort(){ 37 int[] array = {5,7,3,5,4,8,6,4,1,2}; 38 tong bs = new tong(10, array); 39 bs.sort(); 40 bs.sortOut();//輸出打印排序 41 } 42 43 }
10、基數排序
思路:基數排序是按照低位先排序,然后收集;再按照高位排序,然后再收集;依次類推,直到最高位。有時候有些屬性是有優先級順序的,先按低優先級排序,再按高優先級排序。最后的次序就是高優先級高的在前,高優先級相同的低優先級高的在前。
取得數組中的最大數,並取得位數;
arr為原始數組,從最低位開始取每個位組成radix數組;
對radix進行計數排序(利用計數排序適用於小范圍數的特點);
1 private static void radixSort(int[] array,int radix, int distance) { 2 int length = array.length; 3 int[] temp = new int[length]; 4 int[] count = new int[radix]; 5 int divide = 1; 6 7 for (int i = 0; i < distance; i++) { 8 9 System.arraycopy(array, 0,temp, 0, length); 10 Arrays.fill(count, 0); 11 12 for (int j = 0; j < length; j++) { 13 int tempKey = (temp[j]/divide)%radix; 14 count[tempKey]++; 15 } 16 17 for (int j = 1; j < radix; j++) { 18 count [j] = count[j] + count[j-1]; 19 } 20 for (int j = length - 1; j >= 0; j--) { 21 int tempKey = (temp[j]/divide)%radix; 22 count[tempKey]--; 23 array[count[tempKey]] = temp[j]; 24 } 25 divide = divide * radix; 26 } 27 }
代碼例子:
1 package test; 2 3 /** 4 * 基數排序 5 * 平均O(d(n+r)),最好O(d(n+r)),最壞O(d(n+r));空間復雜度O(n+r);穩定;較復雜 6 * d為位數,r為分配后鏈表的個數 7 * 8 * 9 */ 10 public class ji_shu { 11 //pos=1表示個位,pos=2表示十位 12 public static int getNumInPos(int num, int pos) { 13 int tmp = 1; 14 for (int i = 0; i < pos - 1; i++) { 15 tmp *= 10; 16 } 17 return (num / tmp) % 10; 18 } 19 //求得最大位數d 20 public static int getMaxWeishu(int[] a) { 21 int max = a[0]; 22 for (int i = 0; i < a.length; i++) { 23 if (a[i] > max) 24 max = a[i]; 25 } 26 int tmp = 1, d = 1; 27 while (true) { 28 tmp *= 10; 29 if (max / tmp != 0) { 30 d++; 31 } else 32 break; 33 } 34 return d; 35 } 36 public static void radixSort(int[] a, int d) { 37 int[][] array = new int[10][a.length + 1]; 38 for (int i = 0; i < 10; i++) { 39 array[i][0] = 0; 40 // array[i][0]記錄第i行數據的個數 41 } 42 for (int pos = 1; pos <= d; pos++) { 43 for (int i = 0; i < a.length; i++) { 44 // 分配過程 45 int row = getNumInPos(a[i], pos); 46 int col = ++array[row][0]; 47 array[row][col] = a[i]; 48 } 49 for (int row = 0, i = 0; row < 10; row++) { 50 // 收集過程 51 for (int col = 1; col <= array[row][0]; col++) { 52 a[i++] = array[row][col]; 53 } 54 array[row][0] = 0; 55 // 復位,下一個pos時還需使用 56 } 57 } 58 } 59 public static void main(String[] args) { 60 int[] a = { 49, 38, 65, 197, 76, 213, 27, 50 }; 61 radixSort(a, getMaxWeishu(a)); 62 for (int i : a) 63 System.out.print(i + " "); 64 } 65 }
小結
排序算法要么簡單有效,要么是利用簡單排序的特點加以改進,要么是以空間換取時間在特定情況下的高效排序。但是這些排序方法都不是固定不變的,需要結合具體的需求和場景來選擇甚至組合使用。才能達到高效穩定的目的。沒有最好的排序,只有最適合的排序。
下面就總結一下排序算法的各自的使用場景和適用場合。
1. 從平均時間來看,快速排序是效率最高的,但快速排序在最壞情況下的時間性能不如堆排序和歸並排序。而后者相比較的結果是,在n較大時歸並排序使用時間較少,但使用輔助空間較多。
2. 上面說的簡單排序包括除希爾排序之外的所有冒泡排序、插入排序、簡單選擇排序。其中直接插入排序最簡單,但序列基本有序或者n較小時,直接插入排序是好的方法,因此常將它和其他的排序方法,如快速排序、歸並排序等結合在一起使用。
3. 基數排序的時間復雜度也可以寫成O(d*n)。因此它最使用於n值很大而關鍵字較小的的序列。若關鍵字也很大,而序列中大多數記錄的最高關鍵字均不同,則亦可先按最高關鍵字不同,將序列分成若干小的子序列,而后進行直接插入排序。
4. 從方法的穩定性來比較,基數排序是穩定的內排方法,所有時間復雜度為O(n^2)的簡單排序也是穩定的。但是快速排序、堆排序、希爾排序等時間性能較好的排序方法都是不穩定的。穩定性需要根據具體需求選擇。
5. 上面的算法實現大多數是使用線性存儲結構,像插入排序這種算法用鏈表實現更好,省去了移動元素的時間。具體的存儲結構在具體的實現版本中也是不同的。