归并排序(递归与非递归方式)
// 递归方法实现
public static void mergeSort1(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
process(arr, 0, arr.length - 1);
}
// arr[L...R]范围上,请让这个范围上的数,有序!
public static void process(int[] arr, int L, int R) {
if (L == R) {
return;
}
// int mid = (L + R) / 2
int mid = L + ((R - L) >> 1);
//保证左边部分有序
process(arr, L, mid);
//保证右边部分有序
process(arr, mid + 1, R);
//一起有序
merge(arr, L, mid, R);
}
public static void merge(int[] arr, int L, int M, int R) {
//L到R上有多少个数
int[] help = new int[R - L + 1];
int i = 0;
int p1 = L;
int p2 = M + 1;
//p1和p2都没有越界的时候
while (p1 <= M && p2 <= R) {
help[i++] = arr[p1] <= arr[p2] ? arr[p1++] : arr[p2++];
}
// 要么p1越界,要么p2越界
// 不可能出现:共同越界
while (p1 <= M) {
help[i++] = arr[p1++];
}
while (p2 <= R) {
help[i++] = arr[p2++];
}
//将顺序拷贝到arr
for (i = 0; i < help.length; i++) {
arr[L + i] = help[i];
}
}
说明:
process(arr, L, R)指:把arr[L…R]上排好序,定义如此!
merge(arr, L, M, R)指:如果L…M已经有序,并且M+1…R上已经有序。那么通过merge过程,让L…R上整体有序。定义如此!
举个例子:
数组:3 1 4 2
位置:0 1 2 3
主函数调用process(arr, 0, 3)意思是让0…3范围有序
首先调用process(arr, 0, 1) :arr[0…1]变成:1,3
其次调用process(arr, 2, 3) :arr[2…3]变成:2,4
最后调用merge(arr, 0, 1, 3): 1,3 和 2,4 merge:1,2,3,4
搞定
至于怎么搞定的?那你需要把所有的递归图画出来,追代码一层一层去看看arr到底怎么变化的
去追process(arr, 0, 1):
首先调用process(arr, 0, 0) :arr[0…0]本身就是:3
其次调用process(arr, 1, 1) :arr[1…1]本身就是:1
最后调用merge(arr, 0, 0, 1): 3 和 1 merge:1,3
搞定
去追process(arr, 2, 3):
首先调用process(arr, 2, 2) :arr[2…2]本身就是:4
其次调用process(arr, 3, 3) :arr[3…3]本身就是:2
最后调用merge(arr, 2, 2, 3): 4 和 2 merge:2,4
搞定
以上展示了,递归调用的所有细节。你刚开始学,肯定需要多画递归图。自己多拆几次。
然后你熟悉了递归这件事情以后,就可以相对“宏观”一点的来用递归实现过程。
只需要关注递归的含义,它能完成的工作。然后组合起来。
也就是所谓“黑盒”。
至于到底怎么实现的,画递归图,去拆解黑盒。
归并排序非递归写法
说明非递归的逻辑:设置一个步长初始化步长为1
有以下数组
3,1,5,6,9,10,7,8,6
首先步长为1
1,3,5,6,9,10,7,8,6
再更新步长为2
1,3,5,6,7,8,9,10,6
再更新步长为4
1,3,5,6,7,8,9,10,6
再更新步长为8:
1,3,5,6,6,7,8,9,10
得到最终的结果。
代码如下:
public static void mergeSort2(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
int step = 1;
int N = arr.length;
while (step < N) {
int L = 0;
while (L < N) {
//此方式会造成M越界,如果凑不齐的话
// int M = L + step - 1;
//同样的此种写法会造成L+step-1越界(如数组凑不满的情况,或者数组下标很大的情况)
// int M = Math.min(N - 1, L + step - 1);
int M = 0;
//L...N-1 length = N-L
if (N - L >= step) {
M = L + step - 1;
} else {
M = N - 1;
}
//没有右组
if (M == N - 1) {
break;
}
int R = 0;
//右组的个数 M+1-R的个数
if (N - 1 - M >= step) {
// R = M + 1 + step - 1;
R = M + step;
} else {
R = N - 1;
}
// L..M M..R
merge(arr, L, M, R);
//下一个左组
if (R == N - 1) {
break;
} else {
L = R + 1;
}
}
//此处是为了防止step溢出,假如当数组的长度趋近于2的30次方的时候
//如果step乘以2肯定会导致step的int溢出的,所以此处当step*2>=n
//的时候则结束当前循环
if (step > (N / 2)) {
break;
} else {
step *= 2;
}
}
}
时间复杂度估算:
由于步长每次都是乘以2,所以首先估算出一个logn,由于每次merge的长度为step长度,
所以最后的时间复杂度为n*logn
快速排序
荷兰国旗问题
给定一个数组arr,和一个数num,请把小于等于num的数放在数组的左边,大于num的数放在数组的右边。要求额外空间复杂度O(1),时间复杂度 O(N)。
思路:
首先划分区域:
1、当前数<=划分值**,当前数和小于等于区域的下一个数做交换**,然后小于等于区域向右扩,当前数跳下一个
2、如果当前数>划分值,当前数直接跳下一个
以越界为结束条件

代码如下:
public static void splitNum(int[] arr) {
//小于等于区域初始位置
int lessEqualR = -1;
int index = 0;
int mostR = arr.length - 1;
while (index < arr.length) {
if (arr[index] <= arr[mostR]) {
//当前数和小于等于区域的下一个位置做交换,小于等于区域往右阔
// swap(arr,lessEqualR+1,index);
// lessEqualR++;
// index++;
swap(arr, ++lessEqualR, index++);
} else {
index++;
}
}
}
public static void swap(int[] arr, int i, int j) {
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
荷兰国旗升级
设置一个数(这里设置的是最后一个数),让小于那个数的放左边,等于那个数的放中间,大于那个数的放右边
划分两个区域一个小于等于区域和大于区域:
1、当前数小于划分值,当前数和小于区域的下一个数交换,小于区域右扩,当前数移动到下一个
2、当前数大于划分值的时候,当前数和大于区域前一个数做交换,大于区域往左扩,当前数不懂
3、当前数等于划分值直接往后跳
4、当前数和大于区域撞上的时候,则将最后一个数和大于区域的第一个数做交换

public static void splitNum2(int[] arr) {
int N = arr.length;
//小于区域的起始值
int lessR = -1;
//大于区域的起始值
int moreL = N - 1;
int index = 0;
while (index < moreL) {
if (arr[index] < arr[N - 1]) {
swap(arr, ++lessR, index++);
} else if (arr[index] > arr[N - 1]) {
swap(arr, --moreL, index);
} else {
index++;
}
swap(arr, moreL, N - 1);
}
}
public static void swap(int[] arr, int i, int j) {
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
快排递归方式
//arr[L...R]范围上,拿arr[R]做划分,让小于的在左边,等于的在中间,大于的在右边,
//返回等于区域的左边界和右边界(index)
public static int[] partition(int[] arr, int L, int R) {
int lessR = L - 1;
int moreL = R;
int index = L;
while (index < moreL) {
if (arr[index] < arr[R]) {
swap(arr, ++lessR, index++);
} else if (arr[index] > arr[R]) {
swap(arr, --moreL, index);
} else {
index++;
}
}
swap(arr, moreL, R);
return new int[]{lessR + 1, moreL};
}
public static void quickSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
process(arr, 0, arr.length - 1);
}
public static void process(int[] arr, int L, int R) {
if (L >= R) {
return;
}
//L<R
int[] equalE = partition(arr, L, R);
//equalE[0] 等于区域第一个数
//equalE[1] 等于区域最后一个数
process(arr, L, equalE[0] - 1);
process(arr, equalE[1] + 1, R);
}
public static void swap(int[] arr, int i, int j) {
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
快排非递归方式
public static void swap(int[] arr, int i, int j) {
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
//arr[L...R]范围上,拿arr[R]做划分,让小于的在左边,等于的在中间,大于的在右边,
//返回等于区域的左边界和右边界(index)
public static int[] partition(int[] arr, int L, int R) {
int lessR = L - 1;
int moreL = R;
int index = L;
while (index < moreL) {
if (arr[index] < arr[R]) {
swap(arr, ++lessR, index++);
} else if (arr[index] > arr[R]) {
swap(arr, --moreL, index);
} else {
index++;
}
}
swap(arr, moreL, R);
return new int[]{lessR + 1, moreL};
}
public static void quickSort2(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
Stack<Job> stack = new Stack<>();
stack.push(new Job(0,arr.length-1));
while(!stack.isEmpty()) {
Job cur = stack.pop();
int L = cur.L;
int R = cur.R;
int[] equals = partition(arr, cur.L, cur.R);
if(equals[0]> cur.L) {
//有小于区域
stack.push(new Job(cur.L, equals[0]-1));
}
if(equals[1]<R) {
//有大于区域
stack.push(new Job(equals[1]+1,cur.R));
}
}
}
4162

被折叠的 条评论
为什么被折叠?



