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

七月挑战一个月重刷完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;
}
相关推荐
天选之女wow26 分钟前
【Hard——Day4】25.K 个一组翻转链表
数据结构·算法·leetcode·链表
曾经的三心草42 分钟前
Java数据结构-List-栈-队列-二叉树-堆
java·数据结构·list
余俊晖1 小时前
英伟达开源多模态视觉语言模型-Nemotron Nano V2 VL模型架构、训练方法、训练数据
人工智能·算法·语言模型·自然语言处理·多模态
2501_941111461 小时前
C++中的原型模式
开发语言·c++·算法
高洁011 小时前
国内外具身智能VLA模型深度解析(2)国外典型具身智能VLA架构
深度学习·算法·aigc·transformer·知识图谱
一只会写代码的猫2 小时前
C# 性能优化:从垃圾回收到多线程并发
jvm·算法
学生小羊2 小时前
A. C05.L08.贪心算法入门
算法·贪心算法
AndrewHZ3 小时前
【图像处理基石】图像连通域计算:原理、算法实现与应用全解析
图像处理·算法·计算机视觉·cv·算法原理·视觉算法·连通域计算
Dev7z3 小时前
基于Matlab遗传算法与蚁群算法的风光储并网微电网容量优化研究
算法·matlab·蚁群算法·多能源微电网
一直在努力的小宁3 小时前
《代码随想录-精华内容提取》07 二叉树
数据结构·算法·链表·面试