归并排序(Merge Sort)是一种有效的排序算法,使用了“分治法”的策略来将一个大问题分解成更小的子问题,并通过合并这些子问题的解来得到原问题的解。归并排序具有稳定性,适用于各种数据结构。
基本思想:
归并排序,其基本思想可以分为以下几个步骤:
-
分解(Divide):
- 将待排序的数组分成两半。这个过程递归地进行,直到每个子数组只有一个元素或为空为止,因为一个单独的元素已经是有序的。
-
征服(Conquer):
- 对每个子数组递归地进行排序。这意味着将每个子数组继续分解,直到分解到基本的单元(单个元素或空数组)。
-
合并(Combine):
- 将已排序的子数组合并成一个更大的有序数组。合并操作需要将两个已排序的子数组合并成一个有序的子数组,保证最终得到的数组仍然是有序的。
过程图示:
假设我们有一个数组 [38, 27, 43, 3, 9, 82, 10]
,我们将应用归并排序:
图示说明:
-
分解:
- 将
[38, 27, 43, 3, 9, 82, 10]
分成[38, 27, 43]
和[3, 9, 82, 10]
。 - 对
[38, 27, 43]
和[3, 9, 82, 10]
分别递归排序。
- 将
-
征服:
- 对
[38, 27, 43]
进一步分解成[38]
和[27, 43]
,然后对[27, 43]
进行分解和排序。 - 对
[3, 9, 82, 10]
进一步分解成[3, 9]
和[82, 10]
,然后对每部分进行排序。
- 对
-
合并:
- 合并
[27]
和[43]
成[27, 43]
。 - 合并
[38]
和[27, 43]
成[27, 38, 43]
。 - 合并
[3]
和[9]
成[3, 9]
。 - 合并
[82]
和[10]
成[10, 82]
。 - 合并
[3, 9]
和[10, 82]
成[3, 9, 10, 82]
。 - 最后合并
[27, 38, 43]
和[3, 9, 10, 82]
成[3, 9, 10, 27, 38, 43, 82]
。
- 合并
简易代码描述:
// 合并函数
void merge(int a[], int p, int q, int s, int t) {
// 分配足够的内存来存储合并结果
int* b = (int*)malloc((t - p + 1) * sizeof(int));
if (b == NULL) {
perror("malloc fail!");
return;
}
int i = p, j = s, k = 0;// 从 0 开始填充 b
// 合并两个有序子数组到临时数组 b 中
while ((i <= q) && (j <= t)) {
if (a[i] < a[j])
b[k++] = a[i++]; // 将较小的元素添加到 b 中
else
b[k++] = a[j++]; // 将较小的元素添加到 b 中
}
// 如果左侧子数组还有剩余元素,拷贝到 b 中
while (i <= q) {
b[k++] = a[i++];
}
// 如果右侧子数组还有剩余元素,拷贝到 b 中
while (j <= t) {
b[k++] = a[j++];
}
// 将合并结果拷贝回原数组 a 中
for (i = p; i <= t; i++) {
a[i] = b[i - p];// 拷贝到原数组,注意偏移量
}
// 释放临时数组的内存
free(b);
}
// 归并排序函数
void merge_sort(int a[], int i, int j) {
if (i < j) {
// 计算中间索引
int k = (i + j) / 2;
// 对左侧子数组进行排序
merge_sort(a, i, k);
// 对右侧子数组进行排序
merge_sort(a, k + 1, j);
// 合并两个已排序的子数组
merge(a, i, k, k + 1, j);
}
}
代码解释:
merge
函数:
-
内存分配:
int* b = (int*)malloc((t - p + 1) * sizeof(int));
- 为合并结果分配一个临时数组
b
,大小为t - p + 1
。
-
合并过程:
int i = p, j = s, k = 0;
- 初始化三个指针:
i
指向左子数组的开始,j
指向右子数组的开始,k
指向临时数组b
的位置。 - 比较
a[i]
和a[j]
,将较小的元素放入b[k]
,然后相应地移动指针i
或j
,并递增k
。 - 当一个子数组中的元素被用尽后,将剩余的元素从另一个子数组拷贝到
b
中。
-
拷贝回原数组:
for (i = p; i <= t; i++) { a[i] = b[i - p]; }
- 将合并后的结果从临时数组
b
拷贝回原数组a
。
-
释放内存:
free(b);
- 释放临时数组
b
的内存。
merge_sort
函数:
-
递归分解:
if (i < j) {
- 递归地将数组分解为两个子数组,直到每个子数组只包含一个元素。
int k = (i + j) / 2;
- 计算中间索引
k
,将数组a
从i
到k
和k+1
到j
进行递归排序。
-
合并排序后的子数组:
merge(a, i, k, k + 1, j);
- 在左右子数组排序完成后,调用
merge
函数将它们合并成一个有序的数组。
归并排序特点:
- 时间复杂度:归并排序的时间复杂度是 (O(n \log n)),这是因为每次分解将问题规模减少一半,而合并两个有序子数组的时间复杂度是 (O(n))。
- 空间复杂度:归并排序需要额外的空间来存储临时数组,所以空间复杂度是 (O(n))。
- 稳定性:归并排序是稳定的排序算法,即相同值的元素在排序后的顺序保持不变。