經過一個多星期的學習、收集、整理,又對數據結構的八大排序算法進行了一個回顧,在測試過程中也遇到了很多問題,解決了很多問題。代碼全都是經過小弟運行的,如果有問題,希望能給小弟提出來,共同進步。
參考:數據結構(c語言版 第2版)、小甲魚數據結構視頻。
package 八大排序算法; import java.util.Arrays; import org.junit.Test; /** * 1、插入排序 直接插入排序、希爾排序 折半插入排序 * 2、交換排序 冒泡排序、快速排序 * 3、選擇排序 直接選擇排序、堆排序 * 4、歸並排序 * 5、分配排序 基數排序 箱排序 * * * * 八大排序算法。 * * * @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}; /** * * ============================================================== * 1、插入排序之->直接插入排序 * 特點: * 1、穩定排序 * 2、算法簡便,且容易實現 3、可適用於鏈式存儲結構 * 4、更適合於初始記錄基本有序(正序),當初始記錄無序,n較大時,此算法時間復雜度較高,不宜采用。 * ================================================================ */ @Test public void IInsertSort() { System.out.println("1、插入排序之->直接插入排序"); 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); } /** * * ====================================== 1、插入排序之->折半插入排序 特點: 1、穩定排序 * 2、因為要進行折半查找,所以只能用於順序結構,不能用於鏈式結構 3、適合初始記錄無序,且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("1、插入排序之->折半插入排序"); print(a); } /** * * ====================================== * 1、插入排序之->希爾排序方法一 * 特點: * 1、記錄跳躍式移動導致排序方法是不穩定的 * 2、只能用於順序結構,不能用於鏈式結構 * 3、綜合來說,n越大,效果越明顯,所以適合初始記錄無序,n較大時的情況~。 * ====================================== */ @Test public void shellInsertSort() { System.out.println("1、插入排序之->希爾排序"); 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); } /** * * ====================================== * 1、插入排序之->希爾排序 方法二 * * 方法二修改方法一的存儲元素的方案,和插入排序前兩個一樣,采用a[0]來存儲。 * ====================================== */ @Test public void shellInsertSort2() { System.out.println("1、插入排序之->希爾排序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); } /** * * ====================================== * 2、交換排序->冒泡排序 * 冒泡排序最為一種經典的排序算法,是我們應該隨后都能寫出來的。 * 特點: 1、穩定排序 * 2、可用於鏈式存儲結構 * 3、移動記錄次數較多,算法平均時間性能比直接插入排序查。 * 4、當記錄無序,n較大時,此算法不宜采用。 * * ====================================== */ @Test public void BulleSort() { System.out.println("2、交換排序->冒泡排序"); 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); } /** * * ====================================== * 2、交換排序->快速排序 * 特點: 1、屬於不穩定排序 * 2、排序過程中需要確定上界和下屆,所以只能用於順序結構,很難用於鏈式 * 3、在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; } // 3、選擇排序 直接選擇排序、堆排序、 樹形排序 /** * * ====================================== * 3、選擇排序->直接選擇排序 * 特點: * 1、是一種穩定的排序算法。 * 2、可用於鏈式存儲結構 ====================================== */ @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); } /** * * ====================================== * 3、選擇排序->堆排序 * 特點: * 1、不穩定排序 * 2、只能用於順序存儲結構 * ====================================== */ @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; } /** * * ====================================== * 4、歸並排序 * 特點: * 1、屬於穩定排序 * 2、可用於鏈式存儲 * ====================================== */ @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); } } } }
