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!");
}
}