归并排序
归并排序是分治思想的典型应用,先分再合
1 算法过程
(从小到大排序)
分治:
1. 将原数列对半分为两个子数列,分别对两个子数列进行排序;
2. 对子数列进行排序时,重复第一步;
合并:
1. 申请空间,大小为两个`已排序的`子数列之和,该空间用来存放合并后的数列;
2. 设定两个指针,最初位置分别为两个子数列的起始位置;
3. 比较两个指针所指向的元素,选择较小的元素放入第一步申请的空间,并移动该指针到下一位;
4. 重复第三步,直至有一个子数列的指针超出数列尾;
5. 将另一个子数列的元素直接添加到合并数列的尾部;
2 排序演示
对下面的数列进行归并排序
49, 38, 65, 97, 76, 13, 27
将原数列对半分为两个子数列[49, 38, 65]
和[97, 76, 13, 27]
,分别对两个子数列进行归并排序
49, 38, 65, | 97, 76, 13, 27
重复上面的分割步骤,可以得到
|
49, | 38, 65, | 97, 76, | 13, 27
继续往下分,得到
| | |
49, | 38, | 65, | 97, | 76, | 13, | 27
|
此时每个子数列都只有一个元素,开始合并,合并时确保有序,第一次合并后得到
|
49, | 38, 65, | 76, 97, | 13, 27
第二次合并后得到
38, 49, 65, | 13, 27, 76, 97
第三次合并后得到
13, 27, 38, 49, 65, 76, 97
即为排序后的数组.
3 代码实现
const arr = [3, 4, 6, 11, 9, 5, 7, 8, 1, 2, 10];
// 归并排序主函数
function mergeSort(arr) {
// 缓存数组长度
const len = arr.length;
// 如果待排数组为空数组或只有一个元素,直接返回原数组
if (len <= 1) return arr;
// 选取对半分的分界值索引
const middle = Math.floor(len / 2);
// 拿到左右两个子数组
const left = arr.slice(0, middle);
const right = arr.slice(middle);
// 递归 对子数组进行归并排序 最后将两个子数组合并
return merge(mergeSort(left), mergeSort(right));
}
// 合并方法
function merge(left, right) {
// 申请新空间,存放排序后的数组
const res = [];
// 两个子数组都有元素时,通过比较出较小的数存放入res
while (left.length && right.length) {
if (left[0] <= right[0]) {
res.push(left.shift());
} else {
res.push(right.shift());
}
}
// 两个子数组有任一数组元素为空时,将另一数组直接添加至res末尾
while (left.length) {
res.push(left.shift());
}
while (right.length) {
res.push(right.shift());
}
return res;
}
4 时间复杂度
来推导一下归并排序的时间复杂度
参考自:,在这篇博客的基础上进行了修改(因为我认为最后那里他写的不对)
归并排序总时间 = 划分子数列时间 + 子数列归并排序时间 + 合并子数列时间
由于划分子数列都是对半划分,划分时间为常数,可忽略不计,因此
归并排序总时间 = 子数列归并排序时间 + 合并子数列时间
假设一个含有n
项的数列,归并排序所需时间为T(n)
,那么,将该数列划分为两个子数列,可得
T(n) = 2*T(n/2) + 合并时间
由于合并时,两个子数列经过归并排序后,是有序的,因此合并时时间复杂度为n
,到这里可得
T(n) = 2*T(n/2) + n (式子1)
现在来考虑子数列的归并排序,将两个n/2
的子数列分为四个n/4
的数列
求T(n/2)
.很简单,由于是递归定义,只要将(n/2)
带入上面的式子1
,可得
T(n/2) = 2*T(n/4) + n/2 (式子2)
将式子2
代入式子1
,可得
T(n) = 2 * (2*T(n/4) + n/2) + n
= 4*T(n/4) + 2n (式子3)
同理,将四个n/4
的子数列可以分为八个n/8
的数列
求T(n/4)
,将(n/4)
代入式子1
可得
T(n/4) = 2*T(n/8) + n/4 (式子4)
将式子4
代入式子3
,可得
T(n) = 4 * (2*T(n/8) + n/4) + 2n
= 8*T(n/8) + 3n
至此,我们得到了这样的一个结果
总结规律,划分到最后,子数列的元素都只有一个时,此时共有n
组子数列
T(n) = n * T(n/n) + nlogn
= n * T(1) + nlogn
由于最后一次划分,每组子数列只有一个元素,不需要进行组内排序,时间复杂度为0,因此
T(n/n) = T(1) = 0;
可得
T(n) = nlogn
综上,归并排序的时间复杂度为O(nlogn)
.
5 稳定性
归并排序是稳定的排序,这个很好理解.