各类排序方法 归并排序 扩展练习 逆序对数量

七月挑战一个月重刷完Y总算法基础题,并且每道题写详细题解

进度:(3/106)

归并排序的思想也是分而治之

归并优点:速度稳定,排序也稳定

排序也稳定(数组中有两个一样的值,排序之后他们的前后顺序不发生变化,我们就说这个排序是稳定的)

缺点:比起快排,空间复杂度更高

使用场景:数据量巨大,寻求稳定

速度:稳定n(logn)

思想思路

第一步

找分界点(mid,中间值)

第二步

先递归排序两边

第三步

将两个有序数组合二为一(归并)

有序数组合并实现思路(归并)

利用双指针

准备三个数组和两个指针

三个数组其中两个是有序数组,一个是准备用来存储的

cpp 复制代码
#include<iostream>
#include<algorithm>
using namespace std;
int n,arr[10000010],sum[10000010];
void m_sort(int arr[],int l,int r){
    //到达边界,退出
    if(l>=r)return;
    //找到分界点
    int mid=(l+r)>>1;
    //利用分界点分成两个数组,递归两个数组
    m_sort(arr,l,mid),m_sort(arr,mid+1,r);
    //上面递归结束的,一定是两段有序数组
    //遍历两个数组,把两个数组的值,按照归并顺序放到第三暂存个数组
    //这个两个数组,其实指的是一个数组上的两个有序区间
    //每次放入之前,先进行比较两个数组里的指针指向的数
    //谁指向的数组元素小,先放谁进入第三个暂存数组
    //然后那个数组的指针指向下个元素,循环往复,直至其中一个数组遍历完毕
    int i=l,j=mid+1,k=0;
    while(i<=mid&&j<=r){
        if(arr[i]<arr[j])sum[k++]=arr[i++];
        else sum[k++]=arr[j++];
    }
    //上面的循环只保证其中一个数组遍历完毕
    //下面的两个循环,把未遍历完毕的那个数组剩下的元素,添加进暂存数组
    while(i<=mid)sum[k++]=arr[i++];
     while(j<=r)sum[k++]=arr[j++];
     //把暂存数组,放入原数组,替代原数组元素,完成对arr[l-r]的排序
     for(int i=l,j=0;i<=r&&j<=k; i++,j++){
         arr[i]=sum[j]; 
     }
}
int main(){
    cin>>n;
    for(int i=0;i<n;i++)cin>>arr[i];
    m_sort(arr,0,n-1);
    for(int i=0;i<n;i++)cout<<arr[i]<<' ';
    return 0;
}

归并排序已完成

感悟思想:归并排序思想也是分而治之,但是和快排不同的是,快排是把大的放左边,小的放右边后

越递归,数组有序性越强,先分好再递归,归并排序是先递归,再排序

先确定下标的中间,中位点的下标,也就是数组长度是十的话,中位点的下标就是5

然后直接就分别递归中位点的左边和右边

直到数组变成一位的时候,递归停止,开始回溯

回溯到数组是两位的时候,现在的中位点左右两边,都是有序数组,只需要用两个指针,各指向两个数组

把小的,先放入新数组即可,后面同理,回溯时,中位点两边的数组一定都是有序数组

把两个有序数组合并成新数组,只需要使用双指针,把指向的数组的值较小的那个放入新数组,然后右移指针即可

直到回溯完毕,最后一次合并,原来的无序数组就排好了

有人叫回溯是回归,回归+合并,归并排序。

经典题:逆序对的数量

题目

活动 - AcWing

思路

用分治的思想,mid将数组分成=两段

则逆序对只有三种可能:

红色,同时在左半边,绿色,同时在右半边,黄色,一半在左一半在右

我们先解决黄色情况

假设数组a[n],数组b[n]都是两个有序数组,且a[n]就是mid前0到mid的子串,b[n]是mid后mid+1到r的子串

使用双指针算法,i,j分别指向数组第一个元素

a[i]和b[j]逐位比较

一旦b[j]小于a[i]中一个数,那说明,i到mid所有数,都和b[j]组成逆序对

因为a[]数组有序,a[i]到a[mid]的数必大于a[i],也就必大于b[j],也就必和b[j]组成逆序对

那我们只需要在两者归并排序时,累加记录res+=mid-i+1;

res便是这两个有序数组里的逆序对个数

(mid就是a[n]的长度,-i是减去已经指向过的数,+1是因为i从0开始)

黄色情况可以解决了,那只需要把所有情况都递归分割成黄色情况,所有情况都解决了

结论,只需要在归并排序时,累加记录arr[j]放在arr[i]前的情况时mid-(i+1)的值

代码

这里只对改动部分注释,看归并排序点这归并排序

cpp 复制代码
#include<iostream>
using namespace std;
//数可能很大,使用long long
typedef long long ll;
int a[100010],s[100010];
ll add(int a[],int l,int r){
    if(l>=r)return 0;
    int mid=l+r>>1;
    //res接收值
    ll res=add(a,l,mid);
    //第二段的加上第一段的
    res+=add(a,mid+1,r);
    int k=0,i=l,j=mid+1;
    while(i<=mid&&j<=r){
        if(a[i]<=a[j])s[k++]=a[i++];
        else{
            //一旦a[j]需要放在a[i]前面
            //说明a[j]和a[i]以及a[i]到a[mid]所有数,都组成逆序对
            //mid-(i+1)的结果,就是a[i]到a[mid]的数的个数(+1是因为i从0开始)
            //每次出现这个时,把这个结果累加即可
            res+=mid-i+1;
            s[k++]=a[j++];
        }
    }
    while(i<=mid)s[k++]=a[i++];
    while(j<=r)s[k++]=a[j++];
    for(int i=0,j=l;j<=r;j++,i++){
        a[j]=s[i];
    }
    //处理完这段l-r,把res结果返回出去,处理下一段l-r
        return res;
}
int main(){
    int n;
    cin>>n;
    for(int i=0;i<n;i++){
        cin>>a[i];
    }
       ll res=add(a,0,n-1);
       cout<<res<<endl;
    return 0;
}
相关推荐
半截詩2 分钟前
力扣Hot100-24两两交换链表中的节点(三指针)
算法
2401_8576363913 分钟前
Scala中的尾递归优化:深入探索与实践
大数据·算法·scala
点云侠26 分钟前
matlab 干涉图仿真
开发语言·人工智能·算法·计算机视觉·matlab
夏天的阳光吖35 分钟前
每日一题---OJ题:分隔链表
数据结构·链表
2401_8576380336 分钟前
【深度解析】滑动窗口:目标检测算法的基石
人工智能·算法·目标检测
Czi橙1 小时前
玩玩快速冥(LeetCode50题与70题以及联系斐波那契)
java·算法·快速幂·斐波那契
Python大数据分析@1 小时前
用Python实现的10种聚类算法汇总
python·算法·聚类
橘子味的小橙2 小时前
leetCode-hot100-动态规划专题
java·算法·leetcode·动态规划
沿街2 小时前
【集成学习】基于python的stacking回归预测
人工智能·python·算法·机器学习·回归·集成学习
CylMK2 小时前
NOI大纲——普及组——二叉搜索树
开发语言·数据结构·c++·算法