通過一個多星期的學習、收集、整理,又對數據結構的八大排序算法進行了一個回顧,在測試過程當中也遇到了不少問題,解決了不少問題。代碼全都是通過小弟運行的,若是有問題,但願能給小弟提出來,共同進步。java
參考:數據結構(c語言版 第2版)、小甲魚數據結構視頻。算法
package 八大排序算法; import java.util.Arrays; import org.junit.Test; /** * 一、插入排序 直接插入排序、希爾排序 折半插入排序 * 二、交換排序 冒泡排序、快速排序 * 三、選擇排序 直接選擇排序、堆排序 * 四、歸併排序 * 五、分配排序 基數排序 箱排序 * * * * 八大排序算法。 * * * @author 劉陽陽 * * 2017年2月25日 */ public class InsertSort { // 此類用到的排序數組 int[] a = { 0, 9, 5, 6, 12, 31, 23, 15, 100 }; // int[] b = {0,9,5,6,12,31,23,15,100}; /** * * ============================================================== * 一、插入排序之->直接插入排序 * 特色: * 一、穩定排序 * 二、算法簡便,且容易實現 三、可適用於鏈式存儲結構 * 四、更適合於初始記錄基本有序(正序),當初始記錄無序,n較大時,此算法時間複雜度較高,不宜採用。 * ================================================================ */ @Test public void IInsertSort() { System.out.println("一、插入排序之->直接插入排序"); int j = 0; for (int i = 2; i < a.length; i++) { // 此處i=2,是由於直接插入排序默認1爲拍好的序列,i!=0 // 是由於預留0的空間來暫存每次的結果 if (a[i] < a[i - 1]) {// 當a[i] < a[i-1]時才須要操做,不然由於原本就是好的序列,直接跳過 a[0] = a[i]; a[i] = a[i - 1]; // 開始挪窩 for (j = i - 2; a[0] < a[j]; j--) { // j=i-2 爲何-2 // 是由於-2以後當前就是從後往前遍歷了,-》a[i] // = a[i-1] 與 j=i-2 是本題一個關鍵 a[j + 1] = a[j]; } a[j + 1] = a[0]; } } print(a); } /** * * ====================================== 一、插入排序之->折半插入排序 特色: 一、穩定排序 * 二、由於要進行折半查找,因此只能用於順序結構,不能用於鏈式結構 三、適合初始記錄無序,且n較大的狀況 * ====================================== */ @Test public void BInsertSort() { int low; int height; int j; int m; for (int i = 2; i < a.length; i++) { // i=2,表明從2開始 a[0] = a[i];// 先把當前值存到a[0] low = 1; height = i - 1; // 給low和height賦值,low從1開始,height從比 當前值i小1,開始 while (low <= height) { // 一直循環的條件是 low<=height,在這以後的第3行的 height = // m-1,能夠改變結束循環的條件。 m = (low + height) / 2; // 首先算出來m; if (a[0] < a[m]) { // 若是a[0]<a[m] 表明a[0]比中間的值小 說明插入點應該在前半段, height = m - 1; // 全部把中間m-1點複製給height,爲什麼是m-1,由於m已經用過了,全部是m-1; } else { low = m + 1; // 不然表明 插入點後半段,則把m+1複製給low,來繼續去搜索後半段 } } // 開始挪窩 for (j = i - 1; j >= height + 1; j--) { // 和直接插入排序相比,此處j=j-1 // 仍是從後往前遍歷 a[j + 1] = a[j]; // 把當前j給j+1 } a[j + 1] = a[0]; // 最後把a[0]給j+1,由於j是全局變量,因此j的值會隨着開始挪窩循環的變化減少,直至見到最佳 } System.out.println("一、插入排序之->折半插入排序"); print(a); } /** * * ====================================== * 一、插入排序之->希爾排序方法一 * 特色: * 一、記錄跳躍式移動致使排序方法是不穩定的 * 二、只能用於順序結構,不能用於鏈式結構 * 三、綜合來講,n越大,效果越明顯,因此適合初始記錄無序,n較大時的狀況~。 * ====================================== */ @Test public void shellInsertSort() { System.out.println("一、插入排序之->希爾排序"); int[] a = { 26, 53, 67, 48, 57, 13, 48, 32, 60, 50 }; System.out.println("最初結果"); printXiEr(a); int j = 0; int temp; // 初始化兩個值 // j時爲了第二層循環,temp爲了存儲當前值,與其餘兩種插入排序不一樣的時,本處使用temp,上面兩處使用數組的第0個位置存儲 for (int gap = a.length / 2; gap > 0; gap /= 2) {// 本次循環的是增量的值 5 2 1 System.out.println("本次循環增量爲" + gap); for (int i = gap; i < a.length; i++) {// 記錄每次的變化,i=gap // 至關於第一遍先拿a[5]也就是13 來進行 temp = a[i]; // temp存儲當前的值,與其餘兩種插入排序不一樣的時,本處使用temp,上面兩處使用數組的第0個位置存儲 for (j = i - gap; j >= 0; j -= gap) { // 本處循環是最重要的循環,也就是移動位置的循環 // j=i-gap,第一遍j就等於0 // 也就是a【0】=26 if (temp < a[j]) { // temp = a[5] = 13 // ,temp確定是小於13的,因此執行下邊語句 a[j + gap] = a[j]; // 將a[j] = 26 放到 j+gap的位置,也就是 0+5 // a[5]的位置 } else { // 不然跳過本層循環,記錄執行 i=gap的循環 break; } } a[j + gap] = temp; // 最後把temp的值還原 } printXiEr(a); } // 輸出結果 System.out.println("最終結果:"); printXiEr(a); } /** * * ====================================== * 一、插入排序之->希爾排序 方法二 * * 方法二修改方法一的存儲元素的方案,和插入排序前兩個同樣,採用a[0]來存儲。 * ====================================== */ @Test public void shellInsertSort2() { System.out.println("一、插入排序之->希爾排序2"); int[] a = { 0, 26, 53, 67, 48, 57, 13, 48, 32, 60, 50 }; System.out.println("最初結果"); print(a); System.out.println(); int j = 0; // int temp; //初始化兩個值 // j時爲了第二層循環,temp爲了存儲當前值,與其餘兩種插入排序不一樣的時,本處使用temp,上面兩處使用數組的第0個位置存儲 for (int gap = a.length / 2; gap > 0; gap /= 2) {// 本次循環的是增量的值 5 2 1 System.out.println("本次循環增量爲" + gap); for (int i = gap; i < a.length; i++) {// 記錄每次的變化,i=gap // 至關於第一遍先拿a[5]也就是13 來進行 a[0] = a[i]; // a[0]存儲當前的值 for (j = i - gap; j > 0; j -= gap) { // 本處循環是最重要的循環,也就是移動位置的循環 // j=i-gap,第一遍j就等於0 // 也就是a【0】=26 注意此處改成>0 if (a[0] < a[j]) { // temp = a[5] = 13 // ,temp確定是小於13的,因此執行下邊語句 a[j + gap] = a[j]; // 將a[j] = 26 放到 j+gap的位置,也就是 0+5 // a[5]的位置 } else { // 不然跳過本層循環,記錄執行 i=gap的循環 break; } } a[j + gap] = a[0]; // 最後把a[0]的值還原 } printXiEr(a); } // 輸出結果 System.out.println("最終結果:"); print(a); } /** * * ====================================== * 二、交換排序->冒泡排序 * 冒泡排序最爲一種經典的排序算法,是咱們應該隨後都能寫出來的。 * 特色: 一、穩定排序 * 二、可用於鏈式存儲結構 * 三、移動記錄次數較多,算法平均時間性能比直接插入排序查。 * 四、當記錄無序,n較大時,此算法不宜採用。 * * ====================================== */ @Test public void BulleSort() { System.out.println("二、交換排序->冒泡排序"); printXiEr(a); for (int i = 0; i < a.length - 1; i++) { // 採用第一層循環來控制循環的次數,一共循環a.length-1次 // 這樣會循環到倒數第二個元素 for (int j = i + 1; j < a.length; j++) {// 第二層循環來交換位置,j在i的基礎上+1是由於當前的值要和他身後的元素比較大小,直至最後一個。( // 由於第二次循環直至最後一個因此第一層循環纔會a.length-1) if (a[i] > a[j]) { int temp = a[i]; a[i] = a[j]; a[j] = temp; } } } printXiEr(a); } /** * * ====================================== * 二、交換排序->快速排序 * 特色: 一、屬於不穩定排序 * 二、排序過程當中須要肯定上界和下屆,因此只能用於順序結構,很難用於鏈式 * 三、在n較大時,在平均狀況下快速排序是全部內部排序中速度最快的一種,因此適合初始記錄無序,n較大的狀況 * ====================================== */ @Test public void QuickSort() { System.out.println("待排序序列"); print(a); Qsort(a, 1, a.length - 1); System.out.println(); print(a); } private void Qsort(int[] a, int low, int height) { if (low < height) { int point; point = Partition(a, low, height);// 查找中間點 Qsort(a, low, point - 1);// 遞歸排序左邊 Qsort(a, point + 1, height);// 遞歸排序右邊 } } private int Partition(int[] a, int low, int height) { a[0] = a[low];// 將中間點保存在a[0]這個位置中。 int point = a[low];// 把中間點保存在point中 while (low < height) {// 循環條件是隻要low<height // 如下兩個while就是核心之處 while (low < height && a[height] >= point) {// low<height就不說了,原本是a[height]<poin就移動, // 如今改變一下 // 當》=的時候就--; height--; } a[low] = a[height]; // 循環結束後,交換位置,把右邊小的,交換到中間點的左邊 while (low < height && a[low] <= point) { // 相反同上, 原本是a[low]>=point // 改變寫法 改爲a<=point就跳過++; low++; } a[height] = a[low]; } a[low] = a[0]; return low; } // 三、選擇排序 直接選擇排序、堆排序、 樹形排序 /** * * ====================================== * 三、選擇排序->直接選擇排序 * 特色: * 一、是一種穩定的排序算法。 * 二、可用於鏈式存儲結構 ====================================== */ @Test public void selectSort() { System.out.println("待排序序列"); print(a); for (int i = 1; i < a.length; i++) { int k = i; for (int j = i + 1; j < a.length; j++) { if (a[k] > a[j]) { k = j; } } if (k != i) { int temp = a[i]; a[i] = a[k]; a[k] = temp; } } System.out.println(); print(a); } /** * * ====================================== * 三、選擇排序->堆排序 * 特色: * 一、不穩定排序 * 二、只能用於順序存儲結構 * ====================================== */ @Test public void HeapSort() { CreateHeap();// 首先須要建立根堆 for (int i = a.length - 1; i > 1; --i) {// 這個循環是把最大值a[1]放到末尾 , int temp = a[1]; a[1] = a[i]; // 此時i表明最後一個元素 a[i] = temp; HeapAdjust(a, 1, i - 1);// 調整一次後繼續,把數組,第一個位置,和最後一個位置 此處爲什麼i-1, // 是由於循環已經把最大值放到最後一個了,因此下次就方法最後一個-1的位置 } print(a); } private void CreateHeap() { int n = a.length - 1; // 獲得數組的最大值 for (int i = n / 2; i > 0; i--) { // 從最後一個非終端結點開始,而後一次-- HeapAdjust(a, i, n); } } // 調整堆 private void HeapAdjust(int[] a, int s, int m) {// s表明當前 m表明最後 int temp = a[s]; // 先把a[s]的值賦給temp保存起來 for (int j = 2 * s; j <= m; j *= 2) { if (j < m && a[j] < a[j + 1]) { // 判斷是s大仍是s+1大,若是s+1大 就++j,把j換成當前最大 ++j; } if (temp >= a[j]) { // 若是temp中比最大值還大,表明自己就是一個根堆,break break;// 若是大於,就表明當前爲大跟對,退出 } a[s] = a[j];// 不然就把最大給[s] s = j;// 而後把最大下標給s,繼續循環,檢查是否由於調整根堆而破壞了子樹 } a[s] = temp; } /** * * ====================================== * 四、歸併排序 * 特色: * 一、屬於穩定排序 * 二、可用於鏈式存儲 * ====================================== */ @Test public void MSortM1() { print(a); System.out.println(); Msort(a, 1, a.length - 1); print(a); } private void Msort(int[] nums, int low, int hight) { int mid = (low + hight) / 2; // 求得中間點 if (low < hight) { // 判斷條件 若是low<hight就繼續進行 Msort(nums, low, mid); // 遞歸左 Msort(nums, mid + 1, hight); // 遞歸又 Merge(nums, low, mid, hight); // 最後合併 } } private void Merge(int[] nums, int low, int mid, int hight) { int[] temp = new int[hight - low + 1]; // 臨時數組,存放本次排序的序列 int i = low; // i表明作序列開頭 j表明又序列開頭 k的做用是針對temp數組使用的 int j = mid + 1; int k = 0; while (i <= mid && j <= hight) { // while條件必須知足兩個&&條件,只要有一個不知足就退出 // 本次循環的最後結果就是 左右兩個序列,其中一個序列徹底賦值給temp,而後結束 if (nums[i] < nums[j]) { // 比較 temp[k++] = nums[i++]; // 賦值 } else { temp[k++] = nums[j++]; // 賦值 } } // 如下兩個while是針對上面的while,由於上面的while最後結束的結果爲 // 左右兩個序列,其中一個序列徹底賦值給temp,而後結束,這樣其中一個序列還有值沒有賦給temp // 若是是左序列 就對左序列進行賦值 while (i <= mid) { temp[k++] = nums[i++]; } // 若是是又序列 就對又序列進行賦值 while (j <= hight) { temp[k++] = nums[j++]; } // 最後把本次排好序的temp,按照合併前的其實位置開始,從新賦值給nums; 這種處理方法比數據結構書上給的demo處理方式要好。 for (int m = 0; m < temp.length; m++) { nums[low + m] = temp[m]; } } /** * * 基數排序 * */ @Test public void radixSortTest(){ radixSort(a,4,3); print(a); } private static void radixSort(int[] array, int radix, int distance) { // radix,表明基數 // distance表明排序元素的位數 也就是進行幾回 分配 收集,,由於待排序序列中最大值爲100 三位數 因此distance爲3 int length = array.length; int[] temp = new int[length];// 用於暫存元素 int[] count = new int[radix];// 用於統計基數內元素個數 int divide = 1; for (int i = 0; i < distance; i++) { System.arraycopy(array, 0, temp, 0, length); Arrays.fill(count, 0); for (int j = 0; j < length; j++) { int tempKey = (temp[j] / divide) % radix; count[tempKey]++; // 基數選中計數 } for (int j = 1; j < radix; j++) { count[j] = count[j] + count[j - 1];// 累計計數 } for (int j = length - 1; j >= 0; j--) { int tempKey = (temp[j] / divide) % radix; count[tempKey]--;// 從後往前賦值 array[count[tempKey]] = temp[j]; } divide = divide * radix; } } /** * 輔助方法,輸出當前數組的值。 * * @param temp * 接受傳過來的數組 */ void print(int[] temp) { System.out.println("排序結果爲:"); for (int i = 1; i < temp.length; i++) { System.out.print(temp[i] + " "); } } /** * 第一種希爾排序的專用抽出方法 * * @param temp */ void printXiEr(int[] temp) { for (int i = 0; i < temp.length; i++) { System.out.print(temp[i] + " "); } System.out.println(); } /** * 小測試,測試return,break,continue的區別 */ @Test public void aaa() { for (int i = 1; i <= 3; i++) { for (int j = 1; j <= 3; j++) { if (i == 2) { break; } System.out.println(i + " " + j); } } } }