归并排序
归并排序,是创建在归并操作上的一种有效的排序算法。归并在数据结构上就是将两个或者两个以上有序表组成一个新的有序表。
其原理是假设初始序列含有n个字符,可以将每一个字符看作一个序列,则初始序列可以看作n个有序的子序列,每个子序列的长度为1。然后两两归并,得到不小于(n/2)个长度为2或一的有序子序列。之后再两两归并,重复至一个长度为n的有序序列为止。
例一:将一个长度为n的序列按从小到大的顺序排列。
第一行输入n,第二行输入序列,第三行输出排序好的序列
8
49 56 1025 41 2 22 5 158
2 5 22 41 49 56 158 1025
思路: 首先,根据归并排序的概念,将长度为8的原序列划分,形成八个长度为1的子序列。 在这过程中间我们运用二分,先找到一个中间值,分成左右两个区域 为左半区和右半区,再对每一个区域进行二分,直到序列的长度都为1。 如下图1;
之后,再对分好的序列进行比较合并。 其合并后的序列怎么样呢?再这我们可一新开一个数组用来储存合并后的序列。 先将长度为1的序列合并为长度为2的序列。如下图,49 和 56进行比较,49小于56,所以49 的下标为0,56的下标为1。
然后,要将长度为2的序列两两合并为长度为4的序列。第一个长度为2的序列的两个数分别是49 和 56,第二个的是41 和1025。我们先将49和41进行比较,41 小于 49就将 41 放入我们新开的数组中,再比较 49 与 1025,将 49 放入数组中。之后比较 56 与1025 ,将 56 放入数组中,还剩下一个 1025 由于没数比较,直接放入数组。 一样的操作完成之后的合并,长度为4的序列合成长度为8的序列也是如此。完整过程如下:
代码如下:
#include <stdio.h>
#include<stdlib.h>
void merge(int arr[], int tempArr[], int left, int mid, int right) {
// 标记左半区第一个未排序的元素
int l_pos = left;
// 标记右半区第一个未排序的元素
int r_pos = mid + 1;
// 临时数组元素的下标
int pos = left;
// 合并
while (l_pos <= mid && r_pos <= right)
{
if (arr[l_pos] < arr[r_pos]) // 左半区第一个剩余元素更小
tempArr[pos++] = arr[l_pos++];
else // 右半区第一个剩余元素更小
tempArr[pos++] = arr[r_pos++];
}
//合并左半区
while (l_pos <= mid)
tempArr[pos++] = arr[l_pos++];
//合并右半区
while (r_pos <= right)
tempArr[pos++] = arr[r_pos++];
//把临时数组中合并后的元素复制回原来的数组
while (left <= right) {
arr[left] = tempArr[left];
left++;
}
}
void msort(int arr[], int tempArr[], int left, int right) {
//如果只有一个元素,那么就不需要继续划分
//只有一个元素区域,本身就是有序的,只需要被归并即可
if (left < right) {
//中间值
int mid = (left + right )/ 2;
//递归划分左半区
msort(arr, tempArr, left, mid);
//右半区
msort(arr, tempArr, mid + 1, right);
//合并已经排序的部分
merge(arr, tempArr, left, mid, right);
}
}
void merge_sort(int arr[], int n) {
//分配一个辅助数组
int*tempArr = (int *)malloc(n * sizeof(int));
if (tempArr) { //分配成功
msort(arr, tempArr, 0, n - 1);
free(tempArr);//释放空间
}
else {
printf("error:failed to allocate memory");
}
}
int main() {
int arr[50005];
int n;
scanf("%d",&n);
for(int i=0;i<n;i++)
scanf("%d",&arr[i]);
merge_sort(arr, n);//进行归并
for(int i=0;i<n;i++)
printf("%d ",arr[i]);
return 0;
}
实现逻辑:
1. 申请空间,使其大小为需要排序序列长度,该空间用来存放合并后的序列。分配一个辅助数组tempArr[];
2. 递归划分,进入归并 。
检查是否还存在待排序的子数组(通过比较left
和right
的大小)。计算当前子数组的中间索引mid
。递归调用msort
函数对左半部分子数组进行排序(从left
到mid
)。递归调用msort
函数对右半部分子数组进行排序(从mid+1
到right
)。调用merge
函数合并左半部分和右半部分的子数组,存储到tempArr
中
3. 归并排序算法中,归并最后到底都是相邻元素之间的比较交换,并不会发生相同元素的相对位置发生变化,故是稳定性算法。
总结
对于有n个元素归并排序的时间复杂度为 O(A log n) (A为归并的层数,每一层为log n ),空间复杂度为O(n)。 归并排序和选择排序一样,归并排序的性能不受输入数据的影响,但表现比选择排序好的多,因为始终都是O(A log n)的时间复杂度。代价是需要额外的内存空间。