4、归并排序法
- 思想:分治思想
- 具体实现细节:如果要排序一个数组,先把数组从中间分成前后两部分,然后对前后两部分分别进行排序,再将排好序的两部分合并在一起,这样整个数组就有序了;
- 下面看一下我自己画的简单示意图:
- 这里解释一下图示,先分再合,合的时候排序;小数组排序,然后一步一步合成大数组的时候再排序,直到合并成一个大数组;
代码展示:
首先是递归函数
private static void mergeInternal(int[] array,int low,int high){
if (low >= high){
return;
}
int mid = (low + high) / 2;
//左边小数组
mergeInternal(array,low,mid);
//右边小数组
mergeInternal(array,mid+1,high);
//合并两个小数组
merge(array,low,mid,high);
}
然后是具体合并函数,详细注解请看代码注解
/**
*
* @param array 分开之前的大数组
* @param p 两个小数组最左边的下标
* @param q 两个小数组分割点元素
* @param r 两个小数组最右边的下标
*/
private static void merge(int[] array,int p,int m,int r){
//把两个有序的数组合成大的有序数组,需要借助临时空间来解决
//临时空间的大小等于两个小数组的长度之和;
//i和j分别是两个小数组的起始点下标;
int i = p;
int j = m + 1;
int[] temp = new int[r-p+1];
int k = 0;
//此时两个数组中均有元素
while (i <= m && j <= r){
if (array[i] <= array[j]){
//此处加上等于号是为了保证稳定性;
temp[k++] = array[i++];
}else{
temp[k++] = array[j++];
}
}
//判断当前还有哪个数组元素没有走完;
int start = i;
int end = m;
if (j <= r){
start = j;
end = r;
}
//把剩余元素直接放在temp数组后即可
while (start <= end){
temp[k++] = array[start++];
}
//然后将临时空间中已经合并好的元素拷贝回原数组;
for (i = 0;i < r-p+1;i++){
array[p+i] = temp[i];
}
}
}
- 用的是递归的方法来解决这个问题;
代码解释:
- 合并函数中会创建一个临时数组来保存合并后的有序数组,当前两个数组合并完了之后呢,就将临时数组的数据拷贝到原数组array当中取;
- 为什么合并方法中要传入mid这个下标呢?因为合并的是两个数组,你必须直到两个数组的起始下标和终止下标,而mid是左边小数组的重点下标;
合并merge函数中的过程(两个小数组的合并过程)示意图
4.3、归并小总结:
- 首先说一下稳定性:这个主要看merge方法里面while循环里 if (array[i] <= array[j]) ,括号里加不加等于号,如果加上了,若是两个小数组中有相同的数组,放进临时数组中的肯定会先是左边小数组中的数,这是也就保证了稳定性;
- 时间复杂度:O(n log n) ;
- 空间复杂度:O(n) ; 尽管每次合并都需要额外申请一个临时数组,但是在合并完成之后,临时开辟的数组就被释放了,所以说在任意时刻CPU都只会有一个函数在执行,也就是说只有一个临时的内存空间在使用,临时空间的大小最大也不会超过要排序的数组的大小;