核心思想:分治&递归
递归大家应该都知道,那么分治是什么意思呢?分治即分而治之:先将问题分成一些小的问题然后递归求解,再将分的阶段得到的各答案"修补"在一起。
来看下怎么把分治的思想应用到排序上:
先将数组按照len / 2 的长度递归划分为2个数组,直到数组长度=1(分),然后再讲划分后的小数组合并成有序的数组(治)。
javascript代码实现:
const merge_sort = (arr) => {
const tempArr = [];
const left_i = 0;
const right_i = arr.length - 1;
divide(tempArr, arr, left_i, right_i)
return arr;
}
const divide = (tempArr, arr, left, right) => {
// 如果只有一个元素.则不需要继续被划分了
if (left < right) {
// 找到中间点
const mid = Math.floor((right + left) / 2);
// 递归左半区域
divide(tempArr, arr, left, mid);
// 递归右半区域
divide(tempArr, arr, mid + 1, right);
// 排序并合并
merge(tempArr, arr, left, mid, right);
}
}
const merge = (tempArr, arr, left, mid, right) => {
// 左半区域第一个未排序的数的下标
let l_pos = left;
// 左半区域第一个未排序的数的下标
let r_pos = mid + 1;
// 临时数组元素的下标
let pos = left;
// 合并
while (l_pos <= mid && r_pos <= right) {
if (arr[l_pos] < arr[r_pos]) {
// 左半区的第一个元素更小,则交换
// tempArr[pos++] = arr[l_pos++]
// 等同于
tempArr[pos] = arr[l_pos]
pos++;
l_pos++;
} else {
tempArr[pos++] = arr[r_pos++];
}
}
// 这里要么只有左半区域有剩余的元素,要么只有右半区域有剩余的元素,不会存在同时进入的情况
//(即下面两个while只可能会进去一个)
// 合并左半区域剩余的元素
while(l_pos <= mid) {
tempArr[pos++] = arr[l_pos++]
}
// 合并右半区域剩余的元素
while(r_pos <= right) {
tempArr[pos++] = arr[r_pos++]
}
// 把临时数组中合并后的元素复制回原数组
while (left <= right) {
arr[left] = tempArr[left]
left++
}
}
关键点:在入口函数中就给该程序分配一个临时空间,用于在每次递归中将排好序的数组合并。
复杂度分析:
空间复杂度:因为分配了一个长度为n的临时空间,不再是原地排序,所以空间复杂度为O(n);
时间复杂度:每一层归并的时间复杂度为O(n),归并层数最大为O(
l
o
g
n
+
1
{log_{n}}+1
logn+1),两者相乘,省去常数,所以时间复杂度是O(
n
l
o
g
n
n{log_{n}}
nlogn)