好的,我们来详细、系统地解释归并算法(Merge Algorithm)及其核心逻辑。
- 基本概念
归并算法(Merge Algorithm)是将两个或两个以上已经有序的序列合并成一个新的有序序列的过程。它是归并排序(Merge Sort)算法的核心组成部分。
一个关键点:归并算法本身不负责排序,它负责合并。它假设输入的子序列已经是排好序的,然后高效地将它们合并。
- 核心逻辑(以合并两个有序数组为例)
我们用一个具体的例子来说明。假设有两个已排序的数组:
A = [3, 8, 15, 20]
B = [1, 5, 10, 18, 25]
目标:合并成一个有序数组 C。
算法步骤(逻辑):
- 初始化指针:用三个指针(或索引):
· i -> 指向数组A的当前元素(初始为0)
· j -> 指向数组B的当前元素(初始为0)
· k -> 指向结果数组C的当前位置(初始为0) - 比较与选取:
· 比较 A[i] 和 B[j]。
· 将较小的元素复制到结果数组 C[k]。
· 移动指针:被选中的数组指针(i 或 j)和 k 都向前移动一位。 - 处理剩余元素:
· 当其中一个数组(比如A)的所有元素都被取完后,另一个数组(B)中可能还有剩余元素。
· 由于B本身是有序的,直接将B中剩余的所有元素按顺序复制到C的末尾。 - 终止:当两个数组的所有元素都处理完毕时,合并完成。
图文逻辑推演(针对上面例子):
步骤 | 比较 A[i] 和 B[j] | 较小值 | C (结果数组) | i, j 变化
-----|------------------|--------|------------------------|-----------
初始 | | | [] | i=0, j=0
1 | A[0]=3 vs B[0]=1 | 1 | [1] | j=1
2 | 3 vs 5 | 3 | [1, 3] | i=1
3 | 8 vs 5 | 5 | [1, 3, 5] | j=2
4 | 8 vs 10 | 8 | [1, 3, 5, 8] | i=2
5 | 15 vs 10 | 10 | [1, 3, 5, 8, 10] | j=3
6 | 15 vs 18 | 15 | [1, 3, 5, 8, 10, 15] | i=3
7 | 20 vs 18 | 18 | [1, 3, 5, 8, 10, 15, 18] | j=4
8 | 20 vs 25 | 20 | [1, 3, 5, 8, 10, 15, 18, 20] | i=4 (A耗尽)
9 | A已空,复制B剩余 | 25 | [1,3,5,8,10,15,18,20,25] | 完成
- 伪代码
pseudocode
function merge(arr1, arr2):
i = 0, j = 0
result = []
while i < length(arr1) and j < length(arr2):
if arr1[i] <= arr2[j]:
result.append(arr1[i])
i = i + 1
else:
result.append(arr2[j])
j = j + 1
// 将剩余部分追加到结果(两个while只有一个会执行)
while i < length(arr1):
result.append(arr1[i])
i = i + 1
while j < length(arr2):
result.append(arr2[j])
j = j + 1
return result
- 时间复杂度与空间复杂度
· 时间复杂度:O(n + m),其中n和m是两个输入序列的长度。因为每个元素只需比较和复制一次。
· 空间复杂度:O(n + m),需要额外的空间来存储合并后的结果(如果不在原数组上操作)。在归并排序中,这是导致其不是"原地排序"的主要原因。
- 归并排序中的角色
归并算法是归并排序的"合并"步骤。归并排序采用分治法:
-
分解:递归地将数组分成两半,直到每个子数组只有一个元素(单个元素自然有序)。
-
解决:递归地排序子数组(实际上分解到底层就自然有序了)。
-
合并:使用归并算法将两个已排序的子数组合并成一个大的有序数组。
-
关键特性与优势
-
稳定性:在比较时如果使用 <=,可以保持相等元素的原始相对顺序,因此是稳定的。
-
高效性:对于已排序的数据,合并操作非常高效,是外排序(如处理磁盘大文件)的基础。
-
可预测性:无论输入数据如何,时间复杂度都稳定为线性。
-
应用场景
· 归并排序:核心合并步骤。
· 外部排序:当数据量大到无法全部加载到内存时,将数据分成多个有序块,然后多路归并。
· 合并多个有序链表/数组:算法题的常见问题。
· 数据库操作:合并多个已排序的中间结果。
总结
归并算法的核心逻辑是:利用两个有序序列的特性,通过逐对比较首部元素,每次只取较小的一个,从而在线性时间内完成合并,生成一个新的有序序列。 它巧妙地将两个有序信息的合并效率提升到了O(n),是许多高效算法和系统设计的基石。
好嘞!咱们把 merge 函数也补上,让这个归并排序完整跑起来 ✅
我会保持原来逻辑不变,只用对应语言的自然写法来实现 ------ 而且加点小注释,方便你一眼看懂。
✅ Kotlin 完整实现
kotlin
fun mergeSort(arr: IntArray) {
if (arr.size > 1) {
val mid = arr.size / 2
val left = arr.copyOfRange(0, mid)
val right = arr.copyOfRange(mid, arr.size)
mergeSort(left)
mergeSort(right)
merge(arr, left, right)
}
}
fun merge(result: IntArray, left: IntArray, right: IntArray) {
var i = 0 // left 的索引
var j = 0 // right 的索引
var k = 0 // result 的索引
while (i < left.size && j < right.size) {
if (left[i] <= right[j]) {
result[k] = left[i]
i++
} else {
result[k] = right[j]
j++
}
k++
}
// 处理剩余元素
while (i < left.size) {
result[k] = left[i]
i++; k++
}
while (j < right.size) {
result[k] = right[j]
j++; k++
}
}
✅ C++ 完整实现
cpp
#include <vector>
using namespace std;
void mergeSort(vector<int>& arr) {
if (arr.size() <= 1) return;
int mid = arr.size() / 2;
vector<int> left(arr.begin(), arr.begin() + mid);
vector<int> right(arr.begin() + mid, arr.end());
mergeSort(left);
mergeSort(right);
merge(arr, left, right);
}
void merge(vector<int>& arr, const vector<int>& left, const vector<int>& right) {
int i = 0, j = 0, k = 0;
while (i < left.size() && j < right.size()) {
if (left[i] <= right[j]) {
arr[k++] = left[i++];
} else {
arr[k++] = right[j++];
}
}
// 剩余部分拷贝
while (i < left.size()) arr[k++] = left[i++];
while (j < right.size()) arr[k++] = right[j++];
}
✅ Python 完整实现
python
def merge_sort(arr):
if len(arr) <= 1:
return
mid = len(arr) // 2
left = arr[:mid]
right = arr[mid:]
merge_sort(left)
merge_sort(right)
merge(arr, left, right)
def merge(arr, left, right):
i = j = k = 0
while i < len(left) and j < len(right):
if left[i] <= right[j]:
arr[k] = left[i]
i += 1
else:
arr[k] = right[j]
j += 1
k += 1
# 拷贝剩余项
while i < len(left):
arr[k] = left[i]
i += 1
k += 1
while j < len(right):
arr[k] = right[j]
j += 1
k += 1
🎯 测试小例子(Python):
python
nums = [38, 27, 43, 3, 9, 82, 10]
merge_sort(nums)
print(nums) # 输出:[3, 9, 10, 27, 38, 43, 82]
💡 小贴士:
- 这三个版本都遵循了"分治 + 合并"的原始逻辑,没有引入额外优化。
- 时间复杂度:O(n log n),空间复杂度:O(n) ------ 因为每次拆分都新建了数组(更简洁但略耗内存)。
- 如果你想改成原地排序减少内存开销,也可以继续优化,不过代码会复杂一些~要不要我给你来个"省内存加强版"?😉