归并排序
定义
归并排序(merge sort)是高效的基于比较的稳定排序算法。
性质
归并排序基于分治思想将数组分段排序后合并,时间复杂度在最优、最坏与平均情况下均为 \(\Theta (n \log n)\),空间复杂度为 \(\Theta (n)\)。
归并排序可以只使用 \(\Theta (1)\) 的辅助空间,但为便捷通常使用与原数组等长的辅助数组。
过程
合并
归并排序最核心的部分是合并(merge)过程:将两个有序的数组 a[i]
和 b[j]
合并为一个有序数组 c[k]
。
从左往右枚举 a[i]
和 b[j]
,找出最小的值并放入数组 c[k]
;重复上述过程直到 a[i]
和 b[j]
有一个为空时,将另一个数组剩下的元素放入 c[k]
。
为保证排序的稳定性,前段首元素小于或等于后段首元素时(a[i] <= b[j]
)而非小于时(a[i] < b[j]
)就要作为最小值放入 c[k]
。
实现
C++ | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
C++ | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
也可使用 <algorithm>
库的 merge
函数,用法与上述指针式写法的相同。
Python | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
分治法实现归并排序
-
当数组长度为 \(1\) 时,该数组就已经是有序的,不用再分解。
-
当数组长度大于 \(1\) 时,该数组很可能不是有序的。此时将该数组分为两段,再分别检查两个数组是否有序(用第 1 条)。如果有序,则将它们合并为一个有序数组;否则对不有序的数组重复第 2 条,再合并。
用数学归纳法可以证明该流程可以将一个数组转变为有序数组。
为保证排序的复杂度,通常将数组分为尽量等长的两段(\(mid = \left\lfloor \dfrac{l + r}{2} \right\rfloor\))。
实现
注意下面的代码所表示的区间分别是 \([l, r)\),\([l, mid)\),\([mid, r)\)。
C++ | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 |
|
Python | |
---|---|
1 2 3 4 5 6 7 8 9 |
|
倍增法实现归并排序
已知当数组长度为 \(1\) 时,该数组就已经是有序的。
将数组全部切成长度为 \(1\) 的段。
从左往右依次合并两个长度为 \(1\) 的有序段,得到一系列长度 \(\le 2\) 的有序段;
从左往右依次合并两个长度 \(\le 2\) 的有序段,得到一系列长度 \(\le 4\) 的有序段;
从左往右依次合并两个长度 \(\le 4\) 的有序段,得到一系列长度 \(\le 8\) 的有序段;
……
重复上述过程直至数组只剩一个有序段,该段就是排好序的原数组。
为什么是 \(\le n\) 而不是 \(= n\)
数组的长度很可能不是 \(2^x\),此时在最后就可能出现长度不完整的段,可能出现最后一个段是独立的情况。
实现
C++ | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
Python | |
---|---|
1 2 3 4 5 6 7 8 9 |
|
逆序对
逆序对是 \(i < j\) 且 \(a_i > a_j\) 的有序数对 \((i, j)\)。
排序后的数组无逆序对,归并排序的合并操作中,每次后段首元素被作为当前最小值取出时,前段剩余元素个数之和即是合并操作减少的逆序对数量;故归并排序计算逆序对数量的额外时间复杂度为 \(\Theta (n \log n)\),对于 C/C++ 代码将 merge
过程的 if(b[j] < a[i])
部分加上 cnt += aLen - i
或 cnt += aEnd - aBegin
即可,对于 Python 代码将 merge
过程的 if(b[j] < a[i]):
部分加上 cnt += len(a) - i
即可。
此外,逆序对计数即是将元素依次加入数组时统计当前大于其的元素数量,将数组离散化后即是区间求和问题,使用树状数组或线段树解决的时间复杂度为 \(\operatorname{O} (n \log n)\) 且空间复杂度为 \(\Theta (n)\)。
外部链接
本页面的全部内容在 小熊老师 - 莆田青少年编程俱乐部 0594codes.cn 协议之条款下提供,附加条款亦可能应用