初识归并排序
归并排序采用分治的思想,大致分为两个步骤:
1. 分组
2. 归并
此外,归并分为三个步骤:
示例,以两个长度为 4 的数组为例:
1. 创建一个额外的大数组,用于存储归并的结果,长度是两个小数组之和
创建 p1、p2、p3 指针用于记录当前操作位置
2. 逐一比较两个小数组中的元素,把较小的元素优先放入大数组中,
由于 1 < 2,所以把 1 放入大数组中,p1、p 各向右移动一位
由于 2 < 3,所以把 3 放入大数组中,p2、p各向右移动一位
...
直到左侧小数组没有元素可用
3. 从另外一个还有剩余元素的数组中,把剩余的元素按顺序复制到大数组尾部
这样一来两个有序小的数组就合并成为了一个有序的大数组
代码实现
function merge(arr:Array<number>, start:number, end:number, middle:number) {
// 新建合并数组,设置指针
let tempArray = new Array(end - start + 1);
let p1 = start;
let p2 = middle + 1;
let p = 0;
while(p1 <= middle && p2 <= end) {
if(arr[p1] <= arr[p2] ) {
tempArray[p++] = arr[p1++]
} else {
tempArray[p++] = arr[p2++]
}
}
// 如果数组中还有剩余 则依次放入大数组中
while(p1 <= middle) {
tempArray[p++] = arr[p1++]
}
while(p2 <= end) {
tempArray[p++] = arr[p2++]
}
// 合并数组放入原数组
for (let index = 0; index < tempArray.length; index++) {
arr[start + index] = tempArray[index]
}
}
function sort(arr:Array<number>, start:number, end:number) {
if(start < end) {
/*
这里找到middle为 start 和 end 的 "中位数"
即 start = 5; end = 10; 则 middle = 7;
需注意的是 这里采取的是向下舍入即:
5,6,7,8,9,10 共 六个元素,则 middle=7; 5,6,7为一组;8,9,10为一组
6,7,8,9,10 共五个元素,则 middle=8; 6,7,8为一组;9,10为一组
公式为: Math.floor((end-start)/2) + start
转换位运算符公式为:Math.floor(A / Math.pow(2, B)) => Math.floor(A / (2 ** B)) => (A >> B)
所以:Math.floor((end-start)/2) + start === ((end - start)>>1) + start
*/
// 折半分为两个小数组 并递归
let middle = ((end - start)>>1) + start;
sort(arr, start, middle);
sort(arr, middle + 1, end);
// 合并两个小数组
merge(arr, start, end, middle)
}
return arr
}
function main() {
const arr = [5, 8, 6, 3, 9, 2, 1, 7];
console.log(sort(arr, 0, arr.length - 1))
}
main();
小结:
时间复杂度:
归并排序把数组一层一层折半分组,如果数组长度为 n,则折半层数为 logn,每一层运算量为 n,
所以时间复杂度为 O(nlogn)
空间复杂度:
创建最大额外空间为 n(每次归并所创建的空间都会随着方法结束而被释放,所以只取最大空间即可),递归次数为 logn,即 n + logn,所以空间复杂度为 O(n)
归并排序为稳定排序,示例如下:
摘要总结公众号:程序员小灰 文章