归并排序的核心思想就是将几个相邻的有序表合并成一个总的有序表。
1、基本操作就是两个有序表合并成一个有序表
/**
* 两个有序序列的合并
* @param arr 待排序的序列
* @param low 左边序列起始索引
* @param mid 左边序列终止索引
* @param high 右边序列终止索引
* @param tmp 合并后的序列
*/
public void merge(Integer[] arr, Integer low, Integer mid, Integer high, Integer[] tmp){
Integer t = low;//合并后序列 的起始索引
Integer i = low;//左边序列起始索引
Integer j = mid+1;//右边序列起始索引
while(i <= mid && j <= high){//往新序列中放,从小到大
if(arr[i] < arr[j]){
tmp[t++] = arr[i++];
}else{
tmp[t++] = arr[j++];
}
}
//剩余数据放入序列中
while(i <= mid){
tmp[t++] = arr[i++];
}
while (j <= high){
tmp[t++] = arr[j++];
}
}
2、二路归并的思想:只有一个元素的列表总是有序的,所以可以将长度为n的列表看作n个长度为1的有序子表,相邻的两个有序子表两两合并,从长度为1到长度为2再到长度为4......最后生成表长为n的有序表,整个过程需要logn趟。
这里有一个问题,列表长度n不一定是2的整数幂,因此不能保证每趟合并都有偶数个有序子表,这需要在一趟归并中考虑到:
/**
* 一趟归并算法
* @param arr 排序前的数组
* @param tmp 排序后的数组
* @param len 要合并的有序子表的长度
* @param n arr的终止索引
*/
public void mergePass(Integer[] arr, Integer[] tmp, Integer len, Integer n){
int i=0;
for( ; i+2*len-1 <= n; i = i+2*len){//对两个长度为len的有序表合并
merge(arr, i, i+len-1, i+2*len-1, tmp);
}
if(i + len -1 < n){//合并时是一组半的情况
merge(arr, i, i+len-1, n, tmp);
}else if(i <= n){
while(i <= n){//合并时最后一组没有合并
tmp[i++] = arr[i++];
}
}
}
3、二路归并排序:
/**
* 二路归并
* @param arr
* @param tmp
*/
public void mergeSort(Integer[] arr, Integer[] tmp){
Integer n = arr.length;
Integer len = 1;
while(len<n){
mergePass(arr,tmp,len,n);
len = len*2;
mergePass(tmp,arr,len,n);
}
}
空间复杂度为O(n);每次归并需要移动记录n次,需要logn趟归并,所以时间复杂度为nlogn