归并排序
本页面将介绍归并排序及其计算逆序对的应用.
定义
归并排序(merge sort)是高效的基于比较的稳定排序算法.
性质
归并排序基于分治思想将数组分段排序后合并,时间复杂度在最优、最坏与平均情况下均为 Θ(𝑛log𝑛)
,空间复杂度为 Θ(𝑛)
.
归并排序可以只使用 Θ(1)
的辅助空间,但为便捷通常使用与原数组等长的辅助数组.
过程
合并
归并排序最核心的部分是合并(merge)过程:将两个有序的数组 a 和 b 合并为一个有序数组 c.
从左往右枚举 a[i] 和 b[j],每次将较小元素写入 c[k];当一个数组遍历完毕时,将另一个数组的剩余元素追加到 c 中.
为保证排序的稳定性,前段首元素小于或等于后段首元素时(a[i] <= b[j])而非小于时(a[i] < b[j])就要作为最小值放入 c[k].
实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 | void merge(const int* a, size_t aLen, const int* b, size_t bLen, int* c) {
size_t i = 0, j = 0, k = 0;
while (i < aLen && j < bLen) {
if (b[j] < a[i]) { // <!> 先判断 b[j] < a[i],保证稳定性
c[k] = b[j];
++j;
} else {
c[k] = a[i];
++i;
}
++k;
}
// 此时一个数组已空,另一个数组非空,将非空的数组并入 c 中
for (; i < aLen; ++i, ++k) c[k] = a[i];
for (; j < bLen; ++j, ++k) c[k] = b[j];
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 | void merge(const int* aBegin, const int* aEnd, const int* bBegin,
const int* bEnd, int* c) {
while (aBegin != aEnd && bBegin != bEnd) {
if (*bBegin < *aBegin) {
*c = *bBegin;
++bBegin;
} else {
*c = *aBegin;
++aBegin;
}
++c;
}
for (; aBegin != aEnd; ++aBegin, ++c) *c = *aBegin;
for (; bBegin != bEnd; ++bBegin, ++c) *c = *bBegin;
}
|
也可使用 <algorithm> 库的 std::merge 函数,用法与上述指针式写法相同.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 | def merge(a, b):
i, j = 0, 0
c = []
while i < len(a) and j < len(b):
# <!> 先判断 b[j] < a[i],保证稳定性
if b[j] < a[i]:
c.append(b[j])
j += 1
else:
c.append(a[i])
i += 1
# 此时一个数组已空,另一个数组非空,将非空的数组并入 c 中
c.extend(a[i:])
c.extend(b[j:])
return c
|
分治法实现归并排序
- 当数组长度为 1
时,该数组就已经是有序的,不用再分解.
- 当数组长度大于 1
时,该数组很可能不是有序的.此时将该数组分为两段,再分别检查两个数组是否有序(用第一条).如果有序,则将它们合并为一个有序数组;否则对尚未有序的数组重复第二条,再合并.
用数学归纳法可以证明该流程可以将一个数组转变为有序数组.
为保证排序的复杂度,通常将数组分为尽量等长的两段(𝑚𝑖𝑑 =⌊𝑙+𝑟2⌋
).
实现
注意下面的代码所表示的区间分别是 [𝑙,𝑟)
,[𝑙,𝑚𝑖𝑑)
,[𝑚𝑖𝑑,𝑟)
.
| void merge_sort(int* a, int l, int r) {
if (r - l <= 1) return;
// 分解
int mid = l + ((r - l) >> 1);
merge_sort(a, l, mid);
merge_sort(a, mid, r);
// 合并
std::vector<int> tmp(r - l); // 只为本次合并的区间分配缓冲区
merge(a + l, a + mid, a + mid, a + r, tmp.data());
for (int i = l; i < r; ++i) a[i] = tmp[i - l];
}
|
| def merge_sort(a, ll, rr):
if rr - ll <= 1:
return
# 分解
mid = (rr + ll) // 2
merge_sort(a, ll, mid)
merge_sort(a, mid, rr)
# 合并
a[ll:rr] = merge(a[ll:mid], a[mid:rr])
|
倍增法实现归并排序
已知当数组长度为 1
时,该数组就已经是有序的.
将数组全部切成长度为 1
的段.
从左往右依次合并两个长度为 1
的有序段,得到一系列长度 ≤2
的有序段;
从左往右依次合并两个长度 ≤2
的有序段,得到一系列长度 ≤4
的有序段;
从左往右依次合并两个长度 ≤4
的有序段,得到一系列长度 ≤8
的有序段;
……
重复上述过程直至数组只剩一个有序段,该段就是排好序的原数组.
为什么是 ≤
而不是 =
数组的长度很可能不是 2𝑥
,此时在最后就可能出现长度不完整的段,可能出现最后一个段是独立的情况.
实现
逆序对
相关阅读和参考实现:逆序对
逆序对是 𝑖 <𝑗
且 𝑎𝑖 >𝑎𝑗
的有序数对 (𝑖,𝑗)
.
排序后的数组无逆序对.归并排序的合并操作中,每次后段首元素被作为当前最小值取出时,前段剩余元素个数之和即是合并操作减少的逆序对数量;故归并排序计算逆序对数量的时间复杂度为 Θ(𝑛log𝑛)
.此外,逆序对计数还可以通过树状数组或线段树解决,时间复杂度也是 𝑂(𝑛log𝑛)
;这一算法的详细解释参见 树状数组 相应描述.两种算法的参考实现都在 逆序对 章节.
外部链接
本页面最近更新:2026/9/28 01:09:54,更新历史
发现错误?想一起完善? 在 GitHub 上编辑此页!
本页面贡献者:NachtgeistW, Tiphereth-A, Enter-tainer, sshwy, c-forrest, ksyx, Xeonacid, iamtwz, Great-designer, H-J-Granger, invalid-email-address, Ir1d, IronSublimate, Junyan721113, lychees, mcendu, megakite, Menci, minghu6, ouuan, partychicken, SaisycJiang, shawlleyw, untitledunrevised, weisi, zexpp5, zirnc, ZnPdCo
本页面的全部内容在 CC BY-SA 4.0 和 SATA 协议之条款下提供,附加条款亦可能应用