归并排序
归并排序(Merge Sort)是一种分治策略的排序算法, 其基本思想是将待排序的序列划分为若干个子序列,每个子序列是一个有序的序列。然后再把有序子序列合并为整体有序序列。
算法步骤
归并排序的算法可以概括为以下步骤:
分解:将当前待排序的序列分解为两个子序列。
递归进行排序并合并:对子序列递归地应用归并排序算法,然后将已排序的子序列合并成一个有序的序列,这是归并排序算法的核心步骤。
合并:将两个已经排序的子序列合并成一个最终的排序序列。
具体实现
递归分解:
将待排序的数组一直对半分割,直到每个子数组只包含一个元素,此时可以认为每个子数组都是有序的。
递归合并:
从最底层的只包含一个元素的子数组开始,逐步向上层合并,每次合并都将两个有序数组合并成一个更大的有序数组。
在合并过程中,通过比较两个子数组的首元素,选择较小的元素放入新的数组中,然后移动下标,直到一个数组为空。之后,将另一个非空子数组的剩余元素依次放入新数组。
示例
假设有一个待排序的数组 [8, 4, 5, 7, 1, 3, 6, 2]。
分解:一直分割到每个子数组只含一个元素,此时每个子数组都是有序的。
[8], [4], [5], [7], [1], [3], [6], [2]
递归合并:开始合并这些子数组,使其变得有序。
合并 [8] 和 [4],得到 [4, 8]
合并 [5] 和 [7],得到 [5, 7]
合并 [1] 和 [3],得到 [1, 3]
合并 [6] 和 [2],得到 [2, 6]
再合并上述得到的三个子数组:
合并 [4, 8] 和 [5, 7],得到 [4, 5, 7, 8]
合并 [1, 3] 和 [2, 6],得到 [1, 2, 3, 6]
最后,合并两个有序数组[4, 5, 7, 8]和 [1, 2, 3, 6],得到最终排序结果:
[1, 2, 3, 4, 5, 6, 7, 8]
复杂度分析
时间复杂度:归并排序的时间复杂度为 O(n log n),其中 n 是数组的长度。这是因为归并排序将数组拆分为两半,然后对每一半分别进行排序,最后再将它们合并。这个过程会递归进行,直到数组被拆分为只包含一个元素的子数组。由于每次拆分都会使数组长度减半,因此递归的深度是 log n。在每一层递归中,都需要对所有的元素进行操作,因此每一层的时间复杂度是 O(n)。所以总的时间复杂度是 O(n log n)。
空间复杂度:由于归并排序需要额外的空间来存储合并过程中的临时数组,因此其空间复杂度为O(n)。
稳定性:归并排序是一种稳定的排序算法,即相同值的元素在排序后保持原有的相对顺序。
适用场景:归并排序适用于对大量数据进行排序,特别是当数据完全无序时,归并排序的性能表现较好。此外,归并排序也常用于外部排序,即处理存储在外部存储介质(如磁盘)上的大数据集。
代码实现
首先要定义一个_MergerSort函数,该函数是归并排序的主要实现部分。然后定义了一个MergerSort函数,该函数主要用于初始化临时数组tmp并调用_MergerSort函数进行排序。排序完成后,MergerSort函数会释放tmp数组的内存空间。
// 辅助函数,用于递归地对数组进行归并排序
void _MergerSort(int* a, int begin, int end, int* tmp) {
// 如果只有一个元素或没有元素,直接返回,因为一个元素或没有元素本身就是有序的
if (begin == end) {
return;
}
// 找到数组的中间位置
int mid = (begin + end) / 2;
// 对左半部分进行归并排序
_MergerSort(a, begin, mid, tmp);
// 对右半部分进行归并排序
_MergerSort(a, mid + 1, end, tmp);
// 定义两个子数组的起始和结束位置
int begin1 = begin;
int end1 = mid;
int begin2 = mid + 1;
int end2 = end;
int i = begin; // 用于tmp数组的索引
// 当两个子数组都有元素时,选择较小的元素放入tmp数组
while (begin1 <= end1 && begin2 <= end2) {
if (a[begin1] < a[begin2]) {
tmp[i] = a[begin1];
i++;
begin1++;
} else {
tmp[i] = a[begin2];
i++;
begin2++;
}
}
// 如果左子数组还有元素,将其剩余元素依次放入新数组。
while (begin1 <= end1) {
tmp[i] = a[begin1];
i++;
begin1++;
}
// 如果右子数组还有元素,将其剩余元素依次放入新数组。
while (begin2 <= end2) {
tmp[i] = a[begin2];
i++;
begin2++;
}
// 将tmp数组中的有序元素复制回原数组a
memcpy(a + begin, tmp + begin, sizeof(int) * (end - begin + 1));
}
// 主函数,用于初始化并调用归并排序的辅助函数
void MergerSort(int* a, int n) {
// 动态分配一个与原数组相同大小的临时数组tmp
int* tmp = (int*)malloc(sizeof(int) * n);
if (tmp == NULL) { // 如果内存分配失败,打印错误信息并返回
perror("malloc fail");
return;
}
// 调用辅助函数进行归并排序
_MergerSort(a, 0, n - 1, tmp);
// 释放临时数组tmp的内存空间,并将tmp设置为NULL,防止野指针的产生
free(tmp);
tmp = NULL;
}
使用示例
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
void PrintArray(int* a, int n)//打印
{
for (int i = 0; i < n; i++)
{
printf("%d ", a[i]);
}
printf("\n");
}
void _MergerSort(int* a, int begin, int end, int* tmp) {
if (begin == end) {
return;
}
int mid = (begin + end) / 2;
_MergerSort(a, begin, mid, tmp);
_MergerSort(a, mid + 1, end, tmp);
int begin1 = begin;
int end1 = mid;
int begin2 = mid + 1;
int end2 = end;
int i = begin;
while (begin1 <= end1 && begin2 <= end2) {
if (a[begin1] < a[begin2]) {
tmp[i] = a[begin1];
i++;
begin1++;
}
else {
tmp[i] = a[begin2];
i++;
begin2++;
}
}
while (begin1 <= end1) {
tmp[i] = a[begin1];
i++;
begin1++;
}
while (begin2 <= end2) {
tmp[i] = a[begin2];
i++;
begin2++;
}
memcpy(a + begin, tmp + begin, sizeof(int) * (end - begin + 1));
}
void MergerSort(int* a, int n) {
int* tmp = (int*)malloc(sizeof(int) * n);
if (tmp == NULL) {
perror("malloc fail");
return;
}
_MergerSort(a, 0, n - 1, tmp);
free(tmp);
tmp = NULL;
}
void TestMergerSort()
{
int a[] = { 5, 3, 9, 6, 2, 4, 7, 1, 8 };
PrintArray(a, sizeof(a) / sizeof(int));//计算数组元素个数并打印
MergerSort(a, sizeof(a) / sizeof(int));
PrintArray(a, sizeof(a) / sizeof(int));
}
int main() {
TestMergerSort();
return 0;
}