说归并排序之前先说明一下分治的思想。何为分治?分治的思想就是将一个规模为 N 的问题分解为 K 个规模较小的子问题,这些子问题相互独立且与原问题性质相同,只要求出了这些子问题的解,就可得到原问题的解。
归并排序就是采用了这样的思想,本次要实现的是二路归并排序,就是将一组待排序的数组分成两个两个子数组,子数组又继续分为子子数组,直到每个数组中只有一个元素,然后每次将两个子数组合并成一个有序数组,直至所有子数组合并结束。
举个简单的小例子:
int arr[] = {4,1,3,2}
递归分成子数组: {4,1,3,2} –> {4,1} {3,2} –> {4} {1} {3,2}
第一轮合并成有序数组: {1,4} {2,3}
第二轮合并成有序数组: {1,2,3,4}
核心代码:
/**
*
* @param arr 要排序的数组
* @param left 数组的开始位置
* @param right 数组的结束位置
*/
public static void mergesort(int[] arr,int left,int right) {
if(left < right) { //保证数组的开始位置始终小于结束位置
int mid = (left + right) / 2; //寻找分治的中间位置
mergesort(arr,left,mid); //数组左边部分进行递归排序
mergesort(arr,mid+1,right); //数组右边部分进行递归排序
merge(arr,left,mid,right); //不可再分的时候进行数组的合并并排序
}
}
/**
* 合并两个数组,并从小到大排序
*
* @param arr 要排序的数组
* @param left 数组的开始位置
* @param mid 数组的中间位置
* @param right 数组的结束位置
*/
public static void merge(int[] arr,int left, int mid, int right) {
int i = left; //第一个数组的起始位置
int j = mid + 1; //第二个数组的起始位置
int k = left; //合并后的数组的起始位置
int[] temp = new int[arr.length]; //存储合并后的数组
while(i <= mid && j <= right) { //判断两数组下标是否越界,其中一个数组遍历完则结束循环
if(arr[i] <= arr[j]) { //判断两数组下标对应的元素大小并存入临时数组
temp[k++] = arr[i++];
}else {
temp[k++] = arr[j++];
}
}
while(i <= mid) { //如果第一个数组未全部存入临时数组则存入
temp[k++] = arr[i++];
}
while(j <= right) { //如果第二个数组未全部存入临时数组则存入
temp[k++] = arr[j++];
}
for(i = left; i <= right; i++) { //把临时数组中的元素存入原数组中
arr[i] = temp[i];
}
}
归并排序算法是一种稳定算法,时间复杂度为O(nlogn),空间复杂度为O(n).