关键词:排序、分治、双指针、sort函数
地位:算法竞赛出现次数较少(有sort函数),但对于求逆序对问题有一定帮助
归并排序与快速排序非常相似,他的思想也是分治,从一个大问题不断划为许多小问题。
下面来阐述归并排序的几个步骤:
1.选取分界点,这次还是取中点,mid=(l+r)>>1;
2.分治(递归)排序被mid划分的left和right区间;
3.归并——合二为一,排序完成。
注意:归并排序和快速排序的不同点:归并排序是先划分到最小的区间,直到无法再划分,对最小的区间归并操作后再归并较大区间,他的实现是“从底到顶”;快速排序的实现是“从顶到底”。
其中最难的依然属于归并部分:我们如何进行“合二为一”的操作?
我们依然采用双指针+临时存放数组(设为tmp)的思想解决问题。
如下图,一个数组被分为left和right两段,指针(下标)i、j都从两个数组的初始下标开始。
我们每次判定当前的两个指针,哪个指针指向的值更小,则较小指针指向的数被存放到tmp数组,之后较小指针右移一位。无论如何,总有一个指针指向数组的末端。
(要是你想让数组从大到小排序把上面一段文字的”小“全替换成”大“。)
当left或right的其中一个数组的全部元素都存放到tmp时,其实可以把另一个数组的全部值存放到tmp中,此时tmp数组保证一定有序。
究竟为什么?
我们设一个长度为10的数组 2 5 7 9 10 1 3 4 6 8(归并排序保证上一次的left和right都有序)
left:2 5 7 9 10
right: 1 3 4 6 8
那么归并排序的步骤是这样的(tmp是额外开的数组):
1比2小,1存入tmp,right指针指向3;
2比3小,2存入tmp,left指针指向5;
3比5小,3存入tmp,right指针指向4;
4比5小,4存入tmp,right指针指向6;
5比6小,5存入tmp,left指针指向7;
6比7小,6存入tmp,right指针指向8;
7比8小,7存入tmp,left指针指向9;
8比9小,8存入tmp,right数组元素全部存入tmp。
剩下的9 10也一并存入tmp中。
之后再把tmp数组代替left和right数组,即当前正在递归的数组。
数组内部元素是:1 2 3 4 5 6 7 8 9 10。
你可以尝试自己画图,模拟归并排序的”合二为一“是怎么运作的。
那么接下来就是大家喜闻乐见的上代码环节了(手动滑稽):
#include<iostream>
using namespace std;
const int N=1e5+10;
int a[N],tmp[N];//tmp为存放数组
void mergeSort(int l,int r)
{
if(l>=r) return ;//判断边界,参见快速排序
int mid=(l+r)>>1;
int i=l,j=mid+1,pos=0;//通过mid给数组分界
//其中i是left的头指针,j(mid+1)是right的头指针
//与快速排序不同的是,归并排序需要先给底层排序
mergeSort(l,mid);
mergeSort(mid+1,r);
//如上述图文描述,根据指针来将每个数放入tmp数组
while(i<=mid&&j<=r)
{
//left部分比right小(或等),left数组的
//元素先放入tmp,反之放入right数组的元素
//把下方的小于等于改成大于等于就是从大到小排序了
if(a[i]<=a[j]) tmp[pos++]=a[i++];//a[i]>=a[j]
else tmp[pos++]=a[j++];
}
//根据上文描述,补充剩下还没放入的元素
//下面这两行代码实际运行其中一行
while(i<=mid) tmp[pos++]=a[i++];
while(j<=r) tmp[pos++]=a[j++];
//从l到r这个区间的数组替换成排序好的数组
for(int i=l,j=0;i<=r;i++,j++)
a[i]=tmp[j];
}
int main()
{
//输入输出操作
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
mergeSort(1,n);
for(int i=1;i<=n;i++) printf("%d ",a[i]);
return 0;
}
那么归并排序的时间复杂度是多少呢?这里y总画图讲的非常细致:是。具体公式可以用数学方法推导:排序一段区间的时间复杂度是,L为当前数组区间的长度;每一层的时间复杂度是,实际上就是。由于归并排序是折半分治的排序,一个数组的长度可以对折次,于是有层递归,相乘之后时间复杂度是,不会出现退化的情况,这一点优于快速排序。归并排序是一个稳定的排序,相同元素的相对位置不会发生变化。
最后科普一下sort函数吧。
sort是C++特有的函数,他在algorithm头文件中,使用时加上
#include<algorithm> 才可以生效。
sort的时间复杂度接近,是多种排序方法的结合,会根据特定数组来判定该使用哪种排序算法,适用于多种情况。
sort默认从小到大排序。如果给结构体排序要写一个bool类型的函数。
使用方法是:sort(a,a+n); (下标从0到n-1) sort(a+1,a+n+1); (下标从1到n)
注意sort的第二个参数的末尾要在实际末尾下标的基础上再加上1。
若需要排序的是STL容器,只需要sort(a.begin(),a.end());即可。
如果要从大到小排序,则需要写成:sort(头指针,尾指针,greater<数据类型>());
数据类型根据你需要排序的数组而定,例如int、double等。
给结构体排序,例如
struct Node
{
int one,two;
} a[N];
//则可以写成这样:
bool cmp(Node a,Node b)
{
if(a.one==b.one) return a.two>b.two;
else return a.one<b.one;
}
//意思是结构体首先会根据one的大小从小到大排序
//如果one相同则按two的大小从大到小排序
//使用sort可以这样写(下标从0开始)
sort(a,a+n,cmp);
//cmp对大多数排序有效果,有些排序cmp可能编译错误
此外也可以通过重载运算符的方式来自定义排序,不过比自定义函数麻烦一些,在
priority_queue和其他特定的情况中重载运算符会发挥出优势。详情请看参考链接。
注:在未特别注明的情况下,对数都是以2为底。
参考链接/文献:
Ⅰ.AcWing——算法基础课,2019,闫学灿 活动 - AcWing
Ⅱ.AcWing787.归并排序 活动 - AcWing
Ⅲ.C++ 中的sort()排序函数原理、用法看这一篇就够了——CSDN,2020,猿六凯
C++ 中的sort()排序函数原理、用法看这一篇就够了_c++升序函数不用sort_猿六凯的博客-CSDN博客
感谢您的支持