(2)归并排序
目录
归并排序(Merge Sort)是创建在归并操作上的一种有效的排序算法。该算法采用分治(Divide and Conquer)策略,将已有序的子序列合并得到完全有序的序列,且各层分治递归可以同时进行。思路简单,速度仅次于快速排序,是一种稳定排序算法。
基本思想
采用分治思想,分治模式在每一层递归上有三个步骤:
- 分解(Divide):将n个元素分成个含n/2个元素的子序列。
- 解决(Conquer):用合并排序法对两个子序列进行递排序。
- 合并(Combine):合并两个已排序的子序列已得到的排序结果。
算法描述
① 申请空间result,使其大小为两个已经排序的序列和,用来存放合并后的序列
② 定义两个指针,其初始位置分别为两个已经排序序列的起始
③ 比较两个指针所指向的元素,选择相对小的元素放入到result,并移动指针到下一位置
④ 重复③直至其中的某一指针到达序列最后
⑤ 将另一序列剩下的所有元素直接复制到result尾部
实现步骤
1、确定序列中间分界点mid,将其均匀划分为left和right两个部分。
2、递归排序left、right。
3、归并-把两个有序的序列合并成一个有序序列。(重点)
归并其实要做两件事:
(1)“分解”:将序列每次折半划分(递归实现)
(2)“合并”:将划分后的序列段两两合并后排序
动图分析
归并排序演示
复杂度
最佳时间复杂度:O(n)
最差时间复杂度:O(nlogn)
平均时间复杂度:O(nlogn)
空间复杂度:O(n)
稳定性:稳定
归并的空间复杂度就是那个临时的数组和递归时压入栈的数据占用的空间:n + logn;所以空间复杂度为: O(n)。
归并排序算法中,归并的最后都是相邻元素之间的比较与交换,所有并不会发生相同元素的相对位置的变化,所以它是稳定性算法。
接下来是实战演练!!!
题目:
代码:
#include <iostream>
using namespace std;
const int N = 1000010;
int n;
//q[]是我们要排序的数组
//result[]存储两个子序列合并后的最终结果
int q[N],result[N];
void merge_sort(int q[],int l,int r)
{
if(l >= r) return;
//确定分界点,l、r分别表示原序列左右两端
int mid = l + r >> 1;
//递归处理左右两端,使其变成两个有序的子序列
merge_sort(q,l,mid),merge_sort(q,mid+1,r);
//k表示当前reslut数组元素个数,i、j两个指针分别指向左右两个子序列的起始
int k = 0,i = l,j = mid + 1;
while(i <= mid && j <= r)
{
if(q[i] <= q[j])
{
result[k++] = q[i++];
}else{
result[k++] = q[j++];
}
}
//把剩余部分直接复制到合并数组
while(i <= mid) result[k++] = q[i++];
while(j <= r) result[k++] = q[j++];
//把tmp中的数据复制到q中
for(i = l,j = 0;i <= r;i++,j++)
{
q[i] = result[j];
}
}
int main()
{
scanf("%d",&n);
for(int i=0;i<n;i++)
{
scanf("%d",&q[i]);
}
merge_sort(q,0,n-1);
for(int i=0;i<n;i++){
printf("%d ",q[i]);
}
return 0;
}
结果:
优化改进
在规模较小时,合并排序可采用直接插入,避免递归调用; 在写法上,可以在生成辅助数组时,两头小,中间大,这时不需要再在后边加俩个while循环进行判断,只需一次比完。 为了节省将元素复制到辅助数组作用的时间,可以在递归调用的每个层次交换原始数组与辅助数组的角色。
总结
归并排序和选择排序一样,归并排序的性能不受输入数据的影响,但表现比选择排序要好,因为始终都是O(nlogn)的时间复杂度,但是需要额外的内存空间。
写在最后!!!
这是做的acwing创始人闫神讲的学习视频笔记,但是作为算法小白,知识还很不牢固。如果有错误欢迎指正~~,感谢!!!
文章借鉴于点此链接进行跳转