歸並排序的兩種方法
遞歸(自頂向下)
-
將一個大的無序的數組分成兩個,然后拿出其中一個再分為兩個···一直這樣下取,直到最后剩下一個,那么這只包含一個元素的數組就是有序的了,然后將兩個這樣的數組通過merge方法有序的合並,一級一級,直到最開始的兩個合並了就排序完了
-
先實現一個merge用來將兩個有序的數組再有序合並在一起,然后通過mergeSort方法遞歸調用,不斷將數組分割,有序合並,最后排序完成
-
代碼實現
import java.util.Arrays; /** * @Description: 歸並排序第一種 * @Author: LinZeLiang * @Date: 2020-10-06 */ public class MergeSortFirst { public static void main(String[] args) { int[] a = {9, 8, 7, 6, 5, 4, 3, 2, 1, 0}; mergeSort(a, 0, 9); System.out.println(Arrays.toString(a)); } /** * * @param a 待排序數組 * @param left 指向第一個元素 * @param right 指向最后一個元素 */ public static void mergeSort(int[] a, int left, int right) { if (left < right) { int mid = (left + right) / 2; mergeSort(a, left, mid); mergeSort(a, mid + 1, right); merge(a, left, mid, right); } } /** * 將左右兩邊有序子數組合並 * * @param a 待排序數組 * @param left 指向第一個索引 * @param mid 指向中間的索引 * @param right 指向最后一個索引 */ public static void merge(int[] a, int left, int mid, int right) { //創建輔助數組 int[] arr = new int[right - left + 1]; //左指針 int l = left; //右指針 int r = mid + 1; //臨時數組的指針 int k = 0; //將兩個數組有序合並到輔助數組中 while (l <= mid && r <= right) { if (a[l] < a[r]) { arr[k++] = a[l++]; } else { arr[k++] = a[r++]; } } //將剩下的還沒有存入arr的數按順序存入臨時數組 while (l <= mid) { arr[k++] = a[l++]; } while (r <= right) { arr[k++] = a[r++]; } //將排序好的臨時數組替換掉原來的數組 for (int i = 0; i < k; i++) { a[left++] = arr[i]; } } }
非遞歸(自底向上)
-
非遞歸就是先從最底層開始先把數組分割成每個子數組包含兩個元素,利用merge方法,對這個子數組進行排序,等這些子數組都排序完成,然后下一輪就是將數組分割成每個子數組包含4個元素,再排序···按照1、2、4、8···順序來分割,直到最后只剩下一個數組就排序完了
-
代碼實現
import java.util.Arrays; /** * @Description: 歸並排序第二種 * @Author: LinZeLiang * @Date: 2020-10-07 */ public class MergeSortSecond { public static void main(String[] args) { int[] a = {9, 8, 7, 6, 5, 4, 3, 2, 1, 0}; mergeSort(a); System.out.println(Arrays.toString(a)); } /** * 歸並排序 * * @param a 待排序數組 */ public static void mergeSort(int[] a) { //先求出數組的長度 int len = a.length; //子數組大小分別為1、2、4、8···,剛開始是1然后2(自底向上,即先按照一個數組兩個個元素排序然后四個) //i為1時是兩個元素一個數組,2時四個元素一個數組,進行排序 for (int i = 1; i < len; i += i) { //left為0從最左端開始 int left = 0; //mid和right是固定的 int mid = left + i - 1; int right = mid + i; //進行合並,將數組兩兩有序合並 while (right < len) { //merge方法和遞歸用的一樣 merge(a, left, mid, right); //移到下一組繼續合並 left = right + 1; mid = left + i - 1; right = mid + i; } //由於並不是所有的數組大小都剛好一樣大,最后一組不一定滿了,所以對最后一組再來合並 if (left < len && mid < len) { merge(a, left, mid, len - 1); } } /* //對上面的進行優化 //這一個循環沒有變化 for (int i = 1; i < len; i += i) { //這個循環替換了上面的一大串代碼 //left為0也從最左邊開始 //left < len - 1就是循環終止條件,寫出過程推斷一下不難發現 //left += (i + i)就是跳到下一個數組繼續合並數組去(不難發現,數組的跨度大小總是當前i的兩倍) for (int left = 0; left < len - i; left += (i + i)) { //left + i - 1代表mid //Math.min(left + i -1 + i, len - 1)代表合並數組的右端right,因為最后一個可能沒填滿,所以通過min找到最右端即可 merge(a, left, left + i - 1, Math.min(left + i + i - 1, len - 1)); } } */ } /** * 將左右兩邊有序子數組合並 * * @param a 待排序數組 * @param left 指向第一個索引 * @param mid 指向中間的索引 * @param right 指向最后一個索引 */ public static void merge(int[] a, int left, int mid, int right) { int[] arr = new int[right - left + 1]; int l = left; int r = mid + 1; int k = 0; while (l <= mid && r <= right) { if (a[l] < a[r]) { arr[k++] = a[l++]; } else { arr[k++] = a[r++]; } } while (l <= mid) { arr[k++] = a[l++]; } while (r <= right) { arr[k++] = a[r++]; } k = 0; while (k < arr.length) { a[left++] = arr[k++]; } } }