3.AcWing787.归并排序(AcWing算法基础课二刷)

关键词:排序、分治、双指针、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总画图讲的非常细致:是O\left ( nlogn \right )。具体公式可以用数学方法推导:排序一段区间的时间复杂度是O\left ( L \right ),L为当前数组区间的长度;每一层的时间复杂度是L*O\left ( L \right ),实际上就是O\left ( n \right )。由于归并排序是折半分治的排序,一个数组的长度可以对折logn次,于是有logn层递归,相乘之后时间复杂度是O\left ( nlogn \right ),不会出现退化的情况,这一点优于快速排序。归并排序是一个稳定的排序,相同元素的相对位置不会发生变化。

最后科普一下sort函数吧。

sort是C++特有的函数,他在algorithm头文件中,使用时加上

#include<algorithm> 才可以生效。

sort的时间复杂度接近O\left ( nlogn \right ),是多种排序方法的结合,会根据特定数组来判定该使用哪种排序算法,适用于多种情况。

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和其他特定的情况中重载运算符会发挥出优势。详情请看参考链接。

注:logn在未特别注明的情况下,对数都是以2为底。

参考链接/文献:

Ⅰ.AcWing——算法基础课,2019,闫学灿 活动 - AcWing

Ⅱ.AcWing787.归并排序 活动 - AcWing

Ⅲ.C++ 中的sort()排序函数原理、用法看这一篇就够了——CSDN,2020,猿六凯

C++ 中的sort()排序函数原理、用法看这一篇就够了_c++升序函数不用sort_猿六凯的博客-CSDN博客

感谢您的支持

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值