采用分治法的一个非常典型的应用。
将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。
1、归并操作,指的是将两个顺序序列合并成一个顺序序列的方法。
如:数组 {6,202,100,301,38,8,1}
初始状态:6,202,100,301,38,8,1
第一次归并后(即递归最底层执行完毕):{6,202},{100,301},{8,38},{1};
两个子序列进行比较,两个指针分别指向每个子序列第一位,小的放在前面,然后对应指针+1
当某个指针超过当前序列的长度后,因为都是有序序列,所以另一个指针后面的元素一定大于之前已经往前的元素,直接全部放到后面即可
如:{6,202},{100,301},两个序列对应指针p:0,q:0;
6<100; 6放在两个序列最前面,然后p++:1;
202>100; 100放在序列前面,q++:1;
202<301; 202放在序列前面,q++:2,此时q指针到最大长度,因为是有序序列,则p指针序列后所有的元素都比放进序列中的大,故将指针后的所有元素直接放入即可
第二次归并后:{6,100,202,301},{1,8,38};
第三次归并后:{1,6,8,38,100,202,301};
2、归并操作步骤如下:
(1)申请空间,大小为两个已经排好序的序列之和,该空间用来做辅助数组
(2)设定两个指针,最初位置分别为两个已经排序序列的起始位置
(3)比较两个指针所指向的元素,选择相对小的元素放入到合并空间(相等选择左组),并移动指针到下一位置。
重复步骤3直到某一指针越界。将另一序列剩下的所有元素直接复制到合并序列(辅助数组)尾,将辅助数组数据拷贝回原数组。
3、时间复杂度
T(N) = a * T(N/b) + O(N^d)(其中a、b、d都是常数)的递归函数,可以直接通过Master公式来确定时间复杂度
1)如果log(b,a) < d,时间复杂度为O(N^d)
2)如果log(b,a) > d,时间复杂度为O(N^log(b,a))
3)如果log(b,a) == d,时间复杂度为O(N^d * logN)
根据Master公式可得 T(N) =2 * T(N/2) + O(N)
因为每次都是拆分为两个子问题,每个子问题占总规模的一半,且合并过程的时间复杂度是O(N)
可得归并排序的时间复杂度是O(N*logN)。
代码示例:
/**
* 1.递归方法实现
*/
public static void mergeSort1(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
process(arr, 0, arr.length - 1);
}
private static void process(int[] arr, int l, int r) {
if (l == r) { //终止条件为序列索引相等,则递归最终会形成0-1,2-3这样的索引对应序列
return;
}
//每次二分实现各个序列
int mid = l + ((r - l) /2);
// 左组递归
process(arr, l, mid);
// 右组递归
process(arr, mid + 1, r);
// 合并
merge(arr, l, mid, r); //将0-1,2-3这样的索引对应序列合并,之后的序列便会是0-3,4-7
}
private static void merge(int[] arr, int l, int m, int r) {
// 辅助数组
int[] help = new int[r - l + 1];
int i = 0;
// 左组位置
int pL = l;
// 右组位置
int pR = m + 1;
while (pL <= m && pR <= r) {
// 谁小拷贝谁,相等的拷贝左组
help[i++] = arr[pL] <= arr[pR] ? arr[pL++] : arr[pR++];
}
// 因为都是有序序列,当其中一个指针遍历完毕时,说明另一个指针后面的元素一定比已经排好的序列大,直接放入即可
while (pL <= m) {
help[i++] = arr[pL++];
}
while (pR <= r) {
help[i++] = arr[pR++];
}
// 修改原数组
for (int j = 0; j < help.length; j++) {
arr[l + j] = help[j];
}
}
js版:
let arr = [6, 202, 100, 301, 38, 8, 1];
function mergeSort(arr, left, right) {
if (left == right) {
return;
}
let mid = (right - left) >> 1;
mid += left;
mergeSort(arr, left, mid);
mergeSort(arr, mid + 1, right);
processMerge(arr, left, mid, right);
}
function processMerge(arr, left, mid, right) {
let helper = [];
let pointL = left,
pointR = mid + 1,
i = 0;
//每次判断的左右集合都是有序的
while (pointL <= mid && pointR <= right) {
helper[i++] = arr[pointL] <= arr[pointR] ? arr[pointL++] : arr[pointR++];
}
//左边集合已放完,剩下的全是比左边大的
while (pointL <= mid) {
helper[i++] = arr[pointL++];
}
while (pointR <= right) {
helper[i++] = arr[pointR++];
}
// 修改原数组
for (let j = 0; j < helper.length; j++) {
arr[left + j] = helper[j];
}
}
mergeSort(arr, 0, arr.length - 1);
console.log(arr[1]);