上面是韩顺平老师的PPT中的图片
可以看到归并排序整体的思想还是挺容易理解的,先分,分到不能再分,然后再合,主要是采用递归的手段
关于递归,其实可以这样来粗略地理解它的思想:就是我当前的程序不想自己处理所有问题,而把部分问题抛给我的下一级,跟下一级说,只要你能把我交给你的解决,同时把结果返回给我,那我拿到了你的结果,我的所有问题就迎刃而解了
很多问题都用递归的手段解决,但是对于不同的具体的问题,怎么递归,依然是一个难点,不是说随便一个问题,一拿过来之后,告诉你可以用递归的方法,然后你就立马知道怎么做了
对于一个知道要用递归手段去处理的问题,我们依然要好好思考,弄清楚两个问题:
第一,递归无非就是我当前的方法调用我自己,只是参数略有调整,但是我当前的方法中还是要给出具体的执行步骤,这样,才能让我的下一级方法,所有的后面的方法,都可以沿用这样的套路,这是最大的难点,而解决这样的一个难点,我想唯一的方法就是边写代码边思考吧,这样的一个过程会加深你对该问题的理解,等你完全可以自主写出代码并且完全理解你写的代码,而不是背的,甚至可以看出别人更好的代码是在哪里做了一些优化,减少了一些执行步骤,这样就可以说你对这个问题的理解非常深了。所以说到底,递归的思想只是一个出发点,真正依赖的还是你对具体问题的理解。
第二,终止条件是什么
public static void main(String[] args) {
int[] arr = {100,2,4,3,5,7,6,1,0,8};
int[] temp = new int[arr.length];
mergeSort(arr, 0, arr.length - 1, temp);
System.out.println(Arrays.toString(arr));
}
//这个方法体现了算法整体的思想:先分再合
//从代码中可以看到,所有方法的实参都是同一个arr数组和temp数组,变的只是下标
public static void mergeSort(int[] arr,int left,int right,int[] temp){
//先分,把当前数组一分为二
int mid = (left + right) / 2;
if (left < right) {//这里符号是严格小于,即至少传过来的数组要有2个元素,才可以先分再合
//
mergeSort(arr,left,mid,temp);//当前方法把问题抛下去,只要左半部分能把它的工作完成,那我这边就可以在此基础上完成我的工作
mergeSort(arr,mid + 1,right,temp);//只要右半部分能把它的工作完成,那我这边就可以在此基础上完成我的工作
//再合,我的工作就是把左右部分合并起来
merge(arr,left,mid,right,temp);
}
}
public static void merge(int[] arr,int left,int mid,int right,int[] temp){
int i = left;
int j = mid + 1;
//因为temp只是一个临时存放arr中元素的数组,每次递归的时候它都是作为一个临时容器的,所以存放的下标每次都从0开始
int t = 0;
while (i <= mid && j <= right) {
if (arr[i] <= arr[j]) {
temp[t] = arr[i];
i++;
t++;
}else {
temp[t] = arr[j];
j++;
t++;
}
}
while (i <= mid) {
temp[t] = arr[i];
t++;
i++;
}
while (j <= right) {
temp[t] = arr[j];
t++;
j++;
}//至此,temp数组中已经临时有序存放了arr中相关部分的元素了,接下来需要把temp中的元素赋值到arr中相关部分的位置上
t = 0;//t归0,准备赋回到原数组的相关位置上
while (left <= right) {
arr[left] = temp[t];
t++;
left++;
}
}
从代码中可以看到,它其中引入了一个temp数组,过程中可以看到,temp只是一个临时存放arr中元素的数组,每次递归的时候它都是作为一个临时容器的,所以存放的下标每次都从0开始
可以理解为归并排序的实质其实就是通过二分对数组达到真正的“触底”,即分到不能再分,分的过程是一个触底的过程,然后合的动作实际上还是在对数组本身进行直接的操作,temp只是作为一个临时容器,且每一步都是临时容器,每一步合的过程都会用到,只要保证它的容量不小于数组的长度就可以了,其实没必要一定等于。
难点在于这个分分合合的过程中对于下标的精准控制,分的时候只需要left和right下标,合的时候另外给了一个mid形参,其实不引入mid形参,直接在方法体中用到的地方写(left + right) / 2也是可以的
另外一个不同的地方是代码中分了两个方法,合的方法是单独写的