前面讲到了冒泡和快排,后面也会陆陆续续的讲其它排序算法,今天的内容是:归并排序。
核心内容
归并排序的核心内容其实很简单,把数组递归二分,后将二分的数组排序好再重新组合到一起,整个排序就完成了。
听完这个介绍,你可能很懵,但不必担心,下面将带你一步步推导。
组合并排序两个数组
看以下题目:
给定两个数组,这两个数组,都是有序的,如
[1, 2, 5],[3, 4, 10],要求将两个数组合并,并依旧保持有序,即输出[1, 2, 3, 4, 5, 10]
题目中最有用的信息,无疑是两个数组都是有序的,那么且将两个数组分别称为arr1和arr2,结果数组为arr3,看看以下思路:
- 给两个数组都设置一个起始下标0
- 比较arr1[0]和arr2[0],将较小的数据放入arr3,并将这个数组的下标往后移动一位【此时结果为,arr1的下标为1,arr2的下标为0,arr3为
[1]】 - 比较arr1[1]和arr2[0],将较小的数据放入arr3,并将这个数组的下标往后移动一位【此时结果为,arr1的下标为2,arr2的下标为0,arr3为
[1, 2]】 - 比较arr[2]和arr2[0],将较小的数据放入arr3,并将这个数组的下标往后移动一位【此时结果为,arr1的下标为2,arr2的下标为1,arr3为
[1, 2, 3]】 - 重复以上动作,直到某个数组的数据都被移动到了arr3
- 将另一个数组的数据全部放入arr3
很明显,我们利用了两个数组都是有序的信息,每次,我们能依次找出最小的数据,并把它放到结果数组,直到某一个数组的所有数据都被放入了结果数组,再将另外一个数组剩余的数据全部放入结果数组,下面我们看一下代码实现:
function sortTwoArr(arr1, arr2) {
let index1 = 0;
let index2 = 0;
let result = [];
while (index1 < arr1.length && index2 < arr2.length) {
// 将较小的数据放入result,并将其数组的下标往后移动
if (arr1[index1] < arr2[index2]) {
result.push(arr1[index1]);
index1++;
} else {
result.push(arr2[index2]);
index2++;
}
}
// 会存在一个数组,还有剩余数据没被放入result,只需要按顺序放入即可
let tarArr = index1 === arr1.length ? arr2 : arr1;
let tarStartIndex = index1 === arr1.length ? index2 : index1;
for (let i = tarStartIndex; i < tarArr.length; i++) {
result.push(tarArr[i]);
}
return result;
}
回到归并排序
存在以下数组
[5, 2, 1, 4, 10, 3],现要求将整个数组排序
可以发现,这个数组,和我们上面的例子,数据很相似。
把这个数组对半分,就是[5, 2, 1],[4, 10, 3],如果我们能将其分别排序成[1, 2, 5]和[3, 4, 10],那就能通过组合两个有序数组的方式,把整个数组排序好了!
以数组[5, 2, 1]的排序任务为例,其实又可以将其拆分成两个数组[5],[2, 1]。
[2, 1]又可以拆分为[2], [1]。
梳理一下
- 最开始的任务: 排序一个大数组【
[5, 2, 1, 4, 10, 3]】 - 任务拆分,
[5, 2, 1, 4, 10, 3]拆成2个数组[5, 2, 1],[4, 10, 3],分别排序好【把[5, 2, 1]变成[1, 2, 5], 把[4, 10, 3]变成[3, 4, 10]】,再组合起来 - 任务拆分,
[5, 2, 1]拆成2个数组拆分成[5], [2, 1],分别排序好【[5]不变,把[2, 1]变成[1, 2]】,再组合起来 - 任务拆分,
[2, 1]拆分成两个数组[2],[1],分别排序好,再组合起来 - 一直细分数组,直到细分的长度为1
让我们尝试写一下代码:
function sort(source) {
if (source.length <= 1) return source;
let mid = Math.floor(source.length / 2);
let half1 = sort(source.slice(0, mid));
let half2 = sort(source.slice(mid, source.length));
return sortTwoArr(half1, half2);
}
上面的代码,就是归并排序的全部了。
注意:代码中频繁的创建了数组,如将数组一分为二时,创建了两个,将两个数组合并时,又创建了一个新的。在数据量比较大的时候,这样的操作会很浪费性能,但其实它是可以省掉的,只需要一个数组即可完成,希望你可以自己写一写。
总结
归并排序是一种分治的思想,它将一个大问题,分成了一些很小的问题,最后将小问题的“答案”组合起来以解决原始的大问题,是一种很高效的算法,后面我们也会在其它算法中接触和学习到它。
下一章
树,bfs和dfs