归并排序也属于分治策略的实现之一,它完全遵循了分治的模式。直观上的操作如下:
1、分解:分解待排序的n个元素的序列成各自包含n/2个元素的两个子序列;
2、解决:使用递归的方式排序两个子序列;
3、合并:合并两个已排序的子序列以产生排序的答案。
分治都是有边界的,在归并中递归的边界就是只有一个元素,在只有一个元素的时候,本身就是排序好的。
归并排序的重点不在于分解,而在于合并,如何将两个已经排序好的子序列进行合并,产生新的排序好的序列呢?
我们可以通过下面的代码实现,这个算法也是比较典型的算法面试题之一:如何合并两个已经排序好的数组(有的时候数组会变成链表)。
public class MergeSort { public static void main(String[] args) { int[] nums1 = {1, 4, 5}; int[] nums2 = {2, 7, 9}; int[] nums = mergeArray(nums1, nums2); for (int i : nums) { System.out.println(i); } } private static int[] mergeArray(int[] first, int[] second) { if (first == null) { return second; } if (second == null) { return first; } int[] answers = new int[first.length + second.length]; // 合并两个数组的方式比较简单 // 1、同时遍历两个数组,对两个数组分别定义两个指针开始跑 // 2、比较两个指针对应大小的值,然后插入到新的数组(注意新数组也需要一个指针) int i = 0, j = 0, k = 0; while (i < first.length && j < second.length) { if (first[i] < second[j]) { answers[k++] = first[i++]; } else { answers[k++] = second[j++]; } } // 此时可能还存在问题需要处理:例如first还剩下或者second还剩下 if (i == first.length) // first已经遍历到头了,剩下的都是second { while (j < second.length) { answers[k++] = second[j++]; } } else { while (i < first.length) { answers[k++] = first[i++]; } } return answers; } }
但是在归并排序中我们不能直接使用上述方式,还需要做一下转换,输入不是两个数组,而是一个数组,只是通过指针方式进行合并,原理是一样的:
/** * 其中low是起始,middle是第一个数组的终止,middle+1是下个数组的起始,high是下个数组的结束 * 即有两个排好序的数组nums[low...middle]和nums[middle+1...high] * @param nums * @param low * @param middle * @param high */ private static void mergeArray(int[] nums, int low, int middle, int high) { int[] temp = new int[high - low + 1]; // 创建一个临时的数组,等填充完此数组之后再将结果赋给nums int i = low, j = middle + 1, k = 0; while (i <= middle && j <= high) { if (nums[i] < nums[j]) { temp[k++] = nums[i++]; } else { temp[k++] = nums[j++]; } } // 此时可能还存在问题需要处理:例如first还剩下或者second还剩下 if (i == middle + 1) // first已经遍历到头了,剩下的都是second { while (j <= high) { temp[k++] = nums[j++]; } } else { while (i <= middle) { temp[k++] = nums[i++]; } } // 重新赋给nums数组 for (int n = 0; n < temp.length; n++) { nums[n + low] = temp[n]; } }
然后我们利用分解递归的方式进行处理,代码如下:
public class MergeSort2 { public static void main(String[] args) { int[] nums = {9, 1, 5, 7, 2, 6, 3, 8, 10, 4}; nums = mergeSort(nums, 0, nums.length - 1); for (int i : nums) { System.out.print(i + " "); } } private static int[] mergeSort(int[] nums, int low, int high) { int middle = (low + high) / 2; if (low < high) // 边界就是两个相等,相等的时候意味着只有一个元素,直接返回即可 { // 排序左侧的部分 mergeSort(nums, low, middle); // 排序右侧的部分 mergeSort(nums, middle + 1, high); // 经过两次排序之后,nums是一个有序的两个数组了,需要将其进行合并 mergeArray(nums, low, middle, high); } return nums; } /** * 其中low是起始,middle是第一个数组的终止,middle+1是下个数组的起始,high是下个数组的结束 * 即有两个排好序的数组nums[low...middle]和nums[middle+1...high] * @param nums * @param low * @param middle * @param high */ private static void mergeArray(int[] nums, int low, int middle, int high) { int[] temp = new int[high - low + 1]; // 创建一个临时的数组,等填充完此数组之后再将结果赋给nums int i = low, j = middle + 1, k = 0; while (i <= middle && j <= high) { if (nums[i] < nums[j]) { temp[k++] = nums[i++]; } else { temp[k++] = nums[j++]; } } // 此时可能还存在问题需要处理:例如first还剩下或者second还剩下 if (i == middle + 1) // first已经遍历到头了,剩下的都是second { while (j <= high) { temp[k++] = nums[j++]; } } else { while (i <= middle) { temp[k++] = nums[i++]; } } // 重新赋给nums数组 for (int n = 0; n < temp.length; n++) { nums[n + low] = temp[n]; } } }