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

七月挑战一个月重刷完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将数组分成=两段

则逆序对只有三种可能:

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

我们先解决黄色情况

假设数组an,数组bn都是两个有序数组,且an就是mid前0到mid的子串,bn是mid后mid+1到r的子串

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

ai和bj逐位比较

一旦bj小于ai中一个数,那说明,i到mid所有数,都和bj组成逆序对

因为a\[\]数组有序,ai到amid的数必大于ai,也就必大于bj,也就必和bj组成逆序对

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

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

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

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

结论,只需要在归并排序时,累加记录arrj放在arri前的情况时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 小时前
MCP 到底是什么?为什么 Agent 都想接上它
算法·aigc·agent
北域码匠5 小时前
冒泡排序太慢?鸡尾酒排序双向优化,原生 C# 零第三方库完整代码
数据结构·排序算法·泛型·c# 算法·鸡尾酒排序·原生 c# 开发·冒泡排序优化·嵌入式算法
kisshyshy8 小时前
从递归到迭代,一文吃透二叉树的核心知识与 JavaScript 实现
javascript·算法·代码规范
To_OC19 小时前
LC 49 字母异位词分组:想到哈希表很简单,选对 key 才是精髓
javascript·算法·leetcode
用户938515635071 天前
从 O(n²) 到 O(nlogn):一文读懂快速排序的“快”与“妙”
javascript·算法
To_OC1 天前
手写快排次次翻车?别死背快排模板了,这才是面试官想听的底层逻辑
javascript·算法·排序算法
饼干哥哥1 天前
Reddit VOC调研太慢?搭一个AI专家团队半小时洞察任何品类|以猫用饮水机为例
人工智能·算法·ai编程
地平线开发者1 天前
Transformer模型部署之性能优化指南
算法
地平线开发者1 天前
人在途中:从“编译失败”到“模型可落地”——CUDA 自定义算子
算法·自动驾驶