package class08; /** * 归并排序 * <p> * 时间复杂度:O(N*logN) * 描述: * 1.数组长度N,步长step去追N,1变2,2变4,4变8。。。 * 所以step变了几次?logN次。 * 2.step每变一次的,时间复杂度是多少呢? * 每一次左组和右组merge,左组和右组merge,左组和右组merge。。。 * 时间复杂度为:O(N) * <p> * 所以总的时间复杂度为O(N*logN)。 */ public class Code02_MergeSort { //1.递归方式 public static void mergeSort1(int[] arr) { if (arr == null || arr.length < 2) { return; } process(arr, 0, arr.length - 1); } /** * 递归,使需要被排序的数组arr,左半边有序,再使右半边有序。再合并。 * * @param arr 需要排序的int类型的数组 * @param L arr的最小的索引 * @param R arr的最大的索引 */ public static void process(int[] arr, int L, int R) { if (L == R) {//如果只有一个数,那么就是有序的,直接返回。 return; } //mid等于(L+R)/2。因为当L和R很大时,这种写法会越界。所以使用下边的,位运算的写法,不会越界。 int mid = L + ((R - L) >> 1); process(arr, L, mid);//使整个数组的左半边有序 process(arr, mid + 1, R);//使整个数组的右半边有序 merge(arr, L, mid, R);//将两个各自有序的数组,进行合并。 } /** * @param arr 需要被排序的数组 * @param L 数组的最小的索引 * @param M 数组的中间位置的索引 * @param R 数组的最大的索引 */ private static void merge(int[] arr, int L, int M, int R) { //定义一个辅助数组help,长度等于L到R的长度。 int[] help = new int[R - L + 1]; int i = 0;//定义辅助数组的指针。 int p1 = L;//定义左半边数组的指针p1,初始值是L。 int p2 = M + 1;//定义右半边数组的指针p2,初始值是M+1。 //只要p1没有越界,并且p2没有越界。 while (p1 <= M && p2 <= R) { //如果左半边数组中,指针p1所指的数字,小于等于,右半边数组中,指针p2所指的数字。 //那么将arr[p1]的值,赋值给help[i]。p1++,i++。 //反之,如果左半边数组中,指针p1所指的数字,大于,右半边数组中,指针p2所指的数字。 //那么将数组arr[p2]的值,赋值给help[i]。p2++,i++。 help[i++] = (arr[p1] <= arr[p2]) ? arr[p1++] : arr[p2++]; } //当程序走到这里,即跳出第一个while循环时,说明p1和p2,有且只有一个越界了。 //当p1没有越界时 while (p1 <= M) { //将arr[p1]的值,赋值给help[i]。p1++,i++。 //也就是当右半边数组,p2越界时,将左半边数组剩余的所有元素,照抄到辅助数组help中。 // help[i++] = arr[p1++]; help[i] = arr[p1]; p1++; i++; } //当p2没有越界时 while (p2 <= R) { //将arr[p2]的值,赋值给help[i]。p2++,i++。 //也就是当左半边数组,p1越界时,将右半边数组剩余的所有元素,照抄到辅助数组help中。 help[i++] = arr[p2++]; } for (i = 0; i < help.length; i++) { //将辅助数组help中的每一个元素,按照索引位置,原封不动地,覆盖掉原来的数组arr。 arr[L + i] = help[i]; } } //2.非递归方式。(迭代的方式) public static void mergeSort2(int[] arr) { if (arr == null || arr.length <= 1) { return; } int step = 1;//初始化步长为1 int N = arr.length; //只要步长小于数组的长度,就循环下去。 //是小于,而不是小于等于。为什么没有等于? //因为当步长step等于数组长度N的时候,整体的左组已经有序了,右组为空。 //既然右组为空,那么就没必要费那一下劲了。即把右组的空,拿出来,和左组合并,并且排序。(即merge和sort) while (step < N) { int L = 0;//L代表每一个左组的第一个索引。 //这个内层while循环的作用是: //每将step乘2一次,将左组和右组调一遍。再 每将step乘2一次,再 将左组和右组调一遍。循环。 while (L < N) { //1.先看左组,即从L到M范围上。 int M = 0;//每一个左组的最后一个索引。 //L到N-1上,一共有几个数呢?(N-1)-L再+1。 //((N - 1) - L) + 1 >= step if (N - L >= step) {//如果L到N-1上,数字的个数,大于等于步长step,则M等于L+step-1。 M = L + step - 1; } else { M = N - 1;//否则,M等于整个数组arr的最后一个索引,即N-1。也就是说,L到N-1的数字的个数不够step个了,那么M就等于N-1了。 } //上方代码的三元写法:M = N - L >= step ? (L + step - 2) : (N - 1); //如果左组的最后一个索引M,等于数组arr的最后一个索引N-1。 //则表示右组为空。所以此时break。这个break,是跳出内层循环,直接去增加步长。即step*=2; if (M == N - 1) { break; } //至此左组有序了,即L到M上有序了。 //======================================================================================= //2.再看右组,即M+1到N-1范围上。 int R = 0;//R表示右组的最后一个索引。 //R可能等于M+1 + step - 1,也可能等于数组arr的最后一个索引,即N-1。 //(N - 1) - (M + 1) + 1 >= step if (N - 1 - M >= step) {//如果M+1到N-1上,数字的个数,大于等于步长step,则R=M+step。 R = M + step;//R = M + 1 + step - 1; } else { R = N - 1;//否则,R等于N-1。 } //三元写法:R = (N - 1 - M) >= step ? (M + step) : (N - 1); //至此,左组和右组,都已经有序了;开始merge吧。 merge(arr, L, M, R); //如果R来到数组arr的最后一个索引,直接break。直接去增加步长,即step*=2。 if (R == N - 1) { break; } else {//否则,L来到第二个整体的,左组的第一个索引位置。即R+1位置。 L = R + 1; } } //如果步长大于数组arr长度的一半,就break,不要将步长乘2了。因为会有溢出的风险。 //不能写step >= N /2。不能加上等于。 //因为当step刚好等于N的一半时,如果此时break,也就是没有将step乘2。也就没有继续将数组的右半部分,进行归并和排序。 //导致整个数组arr,左组有序(N/2长度),右组也有序(N/2长度)。但是由于没有合并,所以整体并不是有序的。 //故此处break的条件中,不能加等于。 if (step > N / 2) { break; } step *= 2;//步长乘2 } } //方式2的简化。非递归方式 public static void mergeSort3(int[] arr) { if (arr == null || arr.length <= 1) { return; } int mergeStep = 1; int N = arr.length; while (mergeStep < N) { //将L归零的这一句,一定要写在外层循环内,内层循环外。 //因为当步长mergeStep翻倍以后,L需要归零。否则,mergeStep >= N - L的结果就是true了;导致直接break了,最后的结果也就是错误的了。 int L = 0; //只要左组的第一个索引,小于数组arr的长度,就循环。 while (L < N) { //L到N-1的个数,就是((N-1)-L)+1 //如果L到N-1的个数,小于等于步长mergeStep,那么L+mergeStep就有溢出的风险。 if (mergeStep >= N - L) { break; } //M = L+步长再-1。 int M = L + mergeStep - 1; /*int R; //如果[M+1, N-1]上的数字个数(即N-1-M),大于步长mergeStep。则右组的最后一个为索引R,等于左右组整体的中点M,再加上mergeStep(M+mergeStep)。 if (mergeStep < N - 1 - M) { R = M + mergeStep; } else {//反之,如果[M+1, N-1]上的数字个数(即N-1-M),小等于步长mergeStep,也就是M(不含)后边的数,不够一个mergeStep时。则R等于最后一个索引,即N-1。 R = N - 1; }*/ //三元表达式写法 /*int R = (mergeStep >= N - 1 - M) ? N - 1 : M + mergeStep;*/ int R = M + Math.min(mergeStep, N - 1 - M); merge(arr, L, M, R); L = R + 1;//将L重新定位 } if (mergeStep > N / 2) {//解释同上一个"if (mergeStep > N / 2)" break; } mergeStep <<= 1;//mergeStep翻倍 } } //生成随机数组 public static int[] generateRandomArray(int maxSize, int maxValue) { int[] arr = new int[(int) ((maxSize + 1) * Math.random())]; for (int i = 0; i < arr.length; i++) { arr[i] = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random()); } return arr; } //复制数组 public static int[] copyArray(int[] arr) { if (arr == null) { return null; } int[] arr2 = new int[arr.length]; for (int i = 0; i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } //判断两个数组是否相等 public static boolean isEquals(int[] arr1, int[] arr2) { if ((arr1 != null && arr2 == null) || (arr2 != null && arr1 == null)) { return false; } if (arr1 == null && arr2 == null) { return true; } if (arr1.length != arr2.length) { return false; } for (int i = 0; i < arr1.length; i++) { if (arr1[i] != arr2[i]) { return false; } } return true; } //打印数组 public static void printArr(int[] arr) { for (int i = 0; i < arr.length; i++) { System.out.print(arr[i] + " "); } System.out.println(); } public static void main(String[] args) { int maxSize = 100; int maxValue = 1000; int testTimes = 100000; System.out.println("test start!"); for (int i = 0; i < testTimes; i++) { int[] arr1 = generateRandomArray(maxSize, maxValue); // int[] arr1 = {2, 3, 6, 4, 3, 1, 0}; // printArr(arr1); int[] arr2 = copyArray(arr1); mergeSort1(arr1); // printArr(arr1); // Arrays.sort(arr2); // mergeSort2(arr2); mergeSort3(arr2); // printArr(arr2); if (!isEquals(arr1, arr2)) { System.out.println("出错了!"); printArr(arr1); printArr(arr2); break; } } System.out.println("test end!"); } }
标签:归并,步长,int,arr,step,mergeStep,数组,排序 From: https://www.cnblogs.com/TheFloorIsNotTooHot/p/16614499.html