文章目录
- 两个有序数组排序
- 一个局部有序数组排序
- 分治法
- 归并排序
两个有序数组排序
先来一个场景假设,先有两个有序数组{1,3,5,9}、{2,4,6,8},要求合并成一个有序数组。
我们先上一段简单的处理代码
public static int[] merge(int[] leftArr, int[] rightArr) {
int[] res = new int[leftArr.length + rightArr.length];
//定义a、b、res数组的初始下标位
int i=0, j=0, k=0;
while(i < leftArr.length && j < rightArr.length) {
if (leftArr[i] > rightArr[j]) {
res[k] = rightArr[j];
j++;
}else {
res[k] = leftArr[i];
i++;
}
k++;
System.out.println("第"+k+"次比较后当前数组:"+Arrays.toString(res));
}
if (i < leftArr.length) {
res[k] = leftArr[i];
}
if (j < rightArr.length) {
res[k] = rightArr[j];
}
System.out.println("最后数组:"+Arrays.toString(res));
return res;
}
输出结果:
第1次比较后当前数组:[1, 0, 0, 0, 0, 0, 0, 0]
第2次比较后当前数组:[1, 2, 0, 0, 0, 0, 0, 0]
第3次比较后当前数组:[1, 2, 3, 0, 0, 0, 0, 0]
第4次比较后当前数组:[1, 2, 3, 4, 0, 0, 0, 0]
第5次比较后当前数组:[1, 2, 3, 4, 5, 0, 0, 0]
第6次比较后当前数组:[1, 2, 3, 4, 5, 6, 0, 0]
第7次比较后当前数组:[1, 2, 3, 4, 5, 6, 8, 0]
最后数组:[1, 2, 3, 4, 5, 6, 8, 9]
乍一看好像没有问题,牛逼666。如果把数组变成{1,3,5,9,10}、{2,4,6,8},我们再运行一下
第1次比较后当前数组:[1, 0, 0, 0, 0, 0, 0, 0, 0]
第2次比较后当前数组:[1, 2, 0, 0, 0, 0, 0, 0, 0]
第3次比较后当前数组:[1, 2, 3, 0, 0, 0, 0, 0, 0]
第4次比较后当前数组:[1, 2, 3, 4, 0, 0, 0, 0, 0]
第5次比较后当前数组:[1, 2, 3, 4, 5, 0, 0, 0, 0]
第6次比较后当前数组:[1, 2, 3, 4, 5, 6, 0, 0, 0]
第7次比较后当前数组:[1, 2, 3, 4, 5, 6, 8, 0, 0]
最后数组:[1, 2, 3, 4, 5, 6, 8, 9, 0]
right数组最后一位:10没赋值进去。while循环了7次,最后i=3,j=3时:判断 9 > 8 将res[6] = 8,j++后j=4不满足循环条件,在 if (i < leftArr.length) 处 res[7] = 9,结束了,怎么处理?看清题目条件:两个“有序”数组,leftArr[i] > rightArr[j],那么leftArr[i+1] > rightArr[j]。ok问题解决,那就把if改成while,如下:
public static int[] merge(int[] leftArr, int[] rightArr) {
int[] res = new int[leftArr.length + rightArr.length];
//定义a、b、res数组的初始下标位
int i=0, j=0, k=0;
while(i < leftArr.length && j < rightArr.length) {
if (leftArr[i] > rightArr[j]) {
res[k] = rightArr[j];
j++;
}else {
res[k] = leftArr[i];
i++;
}
k++;
System.out.println("第"+k+"次比较后当前数组:"+Arrays.toString(res));
}
//将if条件改成while
while (i < leftArr.length) {
res[k] = leftArr[i];
k++;
i++;
}
while (j < rightArr.length) {
res[k] = rightArr[j];
k++;
j++;
}
System.out.println("最后数组:"+Arrays.toString(res));
return res;
}
输出
第1次比较后当前数组:[1, 0, 0, 0, 0, 0, 0, 0, 0]
第2次比较后当前数组:[1, 2, 0, 0, 0, 0, 0, 0, 0]
第3次比较后当前数组:[1, 2, 3, 0, 0, 0, 0, 0, 0]
第4次比较后当前数组:[1, 2, 3, 4, 0, 0, 0, 0, 0]
第5次比较后当前数组:[1, 2, 3, 4, 5, 0, 0, 0, 0]
第6次比较后当前数组:[1, 2, 3, 4, 5, 6, 0, 0, 0]
第7次比较后当前数组:[1, 2, 3, 4, 5, 6, 8, 0, 0]
最后数组:[1, 2, 3, 4, 5, 6, 8, 9, 10]
一个局部有序数组排序
两个有序数组合并解决了,我们再提出下一个问题,按照上面的思路把一个数组{1,3,5,9,10,2,4,6,8},变成有序数组。
代码如下:
public static void merge(int[] arr) {
//将数组拆分成两个
int mid = (arr.length - 1) / 2;
int[] leftArr = Arrays.copyOfRange(arr,0,mid+1);
int[] rightArr = Arrays.copyOfRange(arr,mid+1,arr.length);
//定义左右数组初始位
int i=0,j=0,k=0;
while(i < leftArr.length && j < rightArr.length) {
if (leftArr[i] > rightArr[j]) {
arr[k] = rightArr[j];
j++;
}else {
arr[k] = leftArr[i];
i++;
}
k++;
System.out.println("第"+k+"次比较后当前数组:"+Arrays.toString(arr));
}
//将if条件改成while条件
while (i < leftArr.length) {
arr[k] = leftArr[i];
k++;
i++;
}
while (j < rightArr.length) {
arr[k] = rightArr[j];
k++;
j++;
}
System.out.println("最后数组:"+Arrays.toString(arr));
}
输出:
第1次比较后当前数组:[1, 3, 5, 9, 10, 2, 4, 6, 8]
第2次比较后当前数组:[1, 2, 5, 9, 10, 2, 4, 6, 8]
第3次比较后当前数组:[1, 2, 3, 9, 10, 2, 4, 6, 8]
第4次比较后当前数组:[1, 2, 3, 4, 10, 2, 4, 6, 8]
第5次比较后当前数组:[1, 2, 3, 4, 5, 2, 4, 6, 8]
第6次比较后当前数组:[1, 2, 3, 4, 5, 6, 4, 6, 8]
第7次比较后当前数组:[1, 2, 3, 4, 5, 6, 8, 6, 8]
最后数组:[1, 2, 3, 4, 5, 6, 8, 9, 10]
我们将原数组拆分后,将左右两个数组作为输入源,原arr数组变成输出源,运行正常没有问题。
分治法
通过上面代码的铺垫,下面简单说一下分治法。分治法顾名思义:分而治之,因为许多算法在结构上是具有递归性的,解决一个给定的问题,算法一次或者多次递归调用其自身,将问题变成类似于原问题的子问题,递归的求解这些子问题,然后再合并这些子问题的解来建立原问题的解。
分治法每层递归都有三个步骤:
分解: 将原问题分解成若干子问题
解决: 解决这些子问题,子问题规模大就再递归求解,规模小直接求解。
合并: 合并这些子问题的解成为原问题的解。
归并排序
归并排序完全遵循分治法,以刚才的问题为例{1,3,5,9,10,2,4,6,8},想要使用以上代码求解,需要保证{1,3,5,9,10}、{2,4,6,8}有序,那{1,3,5,9,10}有序的前提需要保证{1,3,5}、{9,10}有序。现在将数组打乱:{3,1,9,6,2,10,8,4,5},归并排序的处理思想,如下图所示:
理解上面这张图,加上之前的铺垫,差不多就可以手搓代码解决问题了。
public static void main(String[] args) {
int[] arr = new int[]{3,1,9,6,2,10,8,4,5};
mergeSort(arr,0,arr.length-1);
System.out.println(Arrays.toString(arr));
}
public static void mergeSort(int[] arr, int low, int high) {
if (low < high) {
int mid = low + (high - low) / 2;
mergeSort(arr,low,mid);
mergeSort(arr,mid+1,high);
merge(arr,low,mid,high);
}
}
public static void merge(int[] arr, int low, int mid,int high) {
//将数组拆分成两个
int[] leftArr = Arrays.copyOfRange(arr,low,mid+1);
int[] rightArr = Arrays.copyOfRange(arr,mid+1,high+1);
//定义左右数组初始位,注意k的值要从low开始
int i=0,j=0,k=low;
while(i < leftArr.length && j < rightArr.length) {
if (leftArr[i] > rightArr[j]) {
arr[k] = rightArr[j];
j++;
}else {
arr[k] = leftArr[i];
i++;
}
k++;
}
//将if条件改成while条件
while (i < leftArr.length) {
arr[k] = leftArr[i];
k++;
i++;
}
while (j < rightArr.length) {
arr[k] = rightArr[j];
k++;
j++;
}
}
当然merge方法中的数组拆分可以物理分,也可以逻辑分,为了便于循序渐进,便于理解。下面我把逻辑分代码附上,顺便缩减一下代码。
public static void merge2(int[] arr, int low, int mid,int high) {
int[] res = new int[high - low + 1];
//定义左右数组初始位
int i=low, j=mid+1, k=0;
while(i <= mid && j <= high) {
res[k++] = arr[i] > arr[j] ? arr[j++] : arr[i++];
}
while (i <= mid) {
res[k++] = arr[i++];
}
while (j <= high) {
res[k++] = arr[j++];
}
//赋值到原数组
int r=0,s=low;
while(s <= high) {
arr[s++] = res[r++];
}
}