排序算法 -归并排序
文章目录
- 1. 归并排序(Merge Sort)
- 1.1 简介
- 1.2 归并排序的步骤
- 1.3 归并排序c 语言实现
- 代码说明
- 1.4 时间复杂度
- 1.5 空间复杂度
- 1.6 动画
1. 归并排序(Merge Sort)
1.1 简介
归并排序(Merge Sort)
是一种基于分治思想的排序算法,它的核心思想是将一个数组分成两个或多个子数组,分别对这些子数组进行排序,然后再将这些已排序的子数组合并成一个完整的、有序的数组。归并排序的主要特点是其稳定性和高效性。
1.2 归并排序的步骤
归并排序(Merge Sort)是一种基于分治思想的排序算法,它的核心思想是将一个数组分成两个或多个子数组,分别对这些子数组进行排序,然后再将这些已排序的子数组合并成一个完整的、有序的数组。归并排序的主要特点是其稳定性和高效性。
-
分解:将数组从中间(或按其他方式)分成两个或更多个子数组,直到每个子数组只包含一个元素(此时子数组自然有序)。
-
递归排序:递归地对每个子数组进行归并排序。由于每个子数组在分解过程中最终都会变成一个元素的数组(有序),因此递归的基准条件是子数组长度为1。
-
合并:将两个或更多个已排序的子数组合并成一个有序的数组。合并过程通常涉及比较两个子数组的元素,并按顺序将它们放入一个新的数组中。
1.3 归并排序c 语言实现
#include <stdio.h>
#include <stdlib.h>
// 合并两个已排序的子数组
void merge(int arr[], int left, int mid, int right) {
int n1 = mid - left + 1;
int n2 = right - mid;
// 创建临时数组
int* L = (int*)malloc(n1 * sizeof(int));
int* R = (int*)malloc(n2 * sizeof(int));
// 拷贝数据到临时数组L[]和R[]
for (int i = 0; i < n1; i++)
L[i] = arr[left + i];
for (int j = 0; j < n2; j++)
R[j] = arr[mid + 1 + j];
// 合并临时数组回到arr[left..right]
int i = 0; // 初始化左子数组的起始索引
int j = 0; // 初始化右子数组的起始索引
int k = left; // 初始化合并后子数组的起始索引
while (i < n1 && j < n2) {
if (L[i] <= R[j]) {
arr[k] = L[i];
i++;
} else {
arr[k] = R[j];
j++;
}
k++;
}
// 拷贝L[]的剩余元素(如果有)
while (i < n1) {
arr[k] = L[i];
i++;
k++;
}
// 拷贝R[]的剩余元素(如果有)
while (j < n2) {
arr[k] = R[j];
j++;
k++;
}
// 释放临时数组的内存
free(L);
free(R);
}
// 归并排序的主函数
void mergeSort(int arr[], int left, int right) {
if (left < right) {
// 找到中间点
int mid = left + (right - left) / 2;
// 递归排序两个子数组
mergeSort(arr, left, mid);
mergeSort(arr, mid + 1, right);
// 合并两个已排序的子数组
merge(arr, left, mid, right);
}
}
// 打印数组
void printArray(int arr[], int size) {
for (int i = 0; i < size; i++)
printf("%d ", arr[i]);
printf("\n");
}
// 主函数
int main() {
int arr[] = {12, 11, 13, 5, 6, 7};
int arr_size = sizeof(arr) / sizeof(arr[0]);
printf("Given array is \n");
printArray(arr, arr_size);
mergeSort(arr, 0, arr_size - 1);
printf("\nSorted array is \n");
printArray(arr, arr_size);
return 0;
}
代码说明
-
merge函数:这个函数负责合并两个已排序的子数组。它首先为两个子数组分配临时内存,然后将它们拷贝到临时数组中。接着,它比较两个临时数组的元素,并按顺序将它们拷贝回原数组。最后,它释放临时数组的内存。
-
mergeSort函数:这是归并排序的主函数。它首先检查左索引是否小于右索引,如果是,则找到中间点,并递归地对左右两个子数组进行排序。排序完成后,它调用merge函数来合并两个已排序的子数组。
-
printArray函数:这个函数用于打印数组的元素。
-
main函数:这是程序的入口点。它定义了一个数组,计算其大小,打印原始数组,调用mergeSort函数对数组进行排序,然后打印排序后的数组。
编译并运行此程序,你将看到原始数组和排序后的数组的输出。
1.4 时间复杂度
归并排序的时间复杂度为O(n log n),这里的n代表数组的元素数量。这一结论源自归并排序的分治特性:
-
分解阶段:每次都将数组一分为二,直至每个子数组仅含一个元素。此分解过程需要log n层(因为每次都将问题规模减半)。
-
合并阶段:在合并两个已排序的子数组时,需要遍历这两个子数组的所有元素。在最坏情况下,每层合并的总操作数为n(尽管每层合并的实际操作数可能因子数组大小而异,但总操作数在n的量级上)。
1.5 空间复杂度
-
递归调用栈:虽然递归调用栈的空间复杂度与递归的深度相关,但在归并排序中,递归深度为log n(因为每次数组都被一分为二)。然而,这部分空间复杂度通常被视为“辅助空间”的一部分,且在实际分析中可能不被单独计算(特别是当它与n相比不显著时)。
-
合并时的临时数组:在合并两个已排序的子数组时,通常需要一个额外的、与较大子数组等大的临时数组来存储合并结果。在最坏情况下(即当两个子数组大小相近时),这个临时数组的大小为 n 2 \frac{n}{2} 2n近似为 n 2 \frac{n}{2} 2n,但在大O表示法中仍视为O(n))。