[算法]——分治&归并(一)

 

目录

一、前言

二、正文

1.排序数组

1.1 题目解析

1.2 算法原理

1.3 具体代码

2.交易逆序对的总数

2.1 题目解析

2.2 算法原理

2.3 具体代码

三、结语


一、前言

        本文将为大家带来算法新一趴——分治&归并的学习,希望大家能够从中有所收获,如有不足,欢迎小伙伴们在评论区指出呀!!!

二、正文

1.排序数组

1. 排序数组 - 【力扣】

1.1 题目解析

        对于本题来说,题目的要求很简单,就是要求我们能够将所给的数组以升序进行排序。该题目也是很经典的排序题,本文只采取其中一种归并排序进行讲解,至于其他的排序,可转移至博主下面几篇文章,当然其中也包括更为细致的归并排序的讲解。

归并排序和计数排序

插入排序与希尔排序

1.2 算法原理

        本题要求我们将无序的数组处理成升序的数组。那么我们想,如果想要整个数组有序,是不是可以先以数组的中间为界,让左边有序,右边也有序,再将左右两边合并起来就可以了。那么如何实现左右两边都有序,是不是也要以左右两边各自中间为界,左边部分的左右两边有序,右边部分的左右两边有序,再将各自左右两边合并……直至分到只有一个元素,就不用排序了,那么上述过程,我们将一个很大的问题,即对整个数组进行排序的过程,分为排序左右两个部分,左右两部分再各自对内部左右两部分排序,然后在合并这样子一个将大问题切分成小问题的过程,就属于分治的过程,而采用这种分治思想的排序我们也叫做归并排序。

        具体的实现过程:

        1.选取中间点mid来换分数组的左右区间

        2.把左右区间进行排序

        3.合并两个有序数组,将其放到辅助数组tmp中(避免合并过程对原数组元素进行覆盖)

        4.处理原数组,即将辅助数组的元素拷贝到原数组

1.3 具体代码

class Solution {
public:
    vector<int> sortArray(vector<int>& nums) {
        //归并排序
        vector<int> tmps=nums;
        Mmerge_sort(nums,0,nums.size()-1,tmps);
        return nums;
    }

    void Merge_sort(vector<int>& nums,int left,int right,vector<int>& tmps)
    {
        if(left==right) return;
        //1. 选择中间点划分区间
        int mid=left+(right-left)/2;
        
        //2. 把左右区间排序
        Imerge_sort(nums,left,mid,tmps);
        Imerge_sort(nums,mid+1,right,tmps);
        
        //3. 合并两个有序数组
        int cur1=left,cur2=mid+1,i=left;
        while(cur1 <= mid && cur2<=right)
            tmps[i++]=nums[cur1]<=nums[cur2]?nums[cur1++]:nums[cur2++];
        while(cur1<=mid) tmps[i++]=nums[cur1++];
        while(cur2<=right) tmps[i++]=nums[cur2++]; 
        // 处理原数组
        for(int j=left;j<=right;j++)
        {
            nums[j]=tmps[j];
        }
    }
};

2.交易逆序对的总数

2.交易逆序对的总数 - 【力扣】

2.1 题目解析

        本题要求我们找出所给数组中所有的逆序对,并统计其数量来返回。那么什么是逆序对,也就是在该数组中我们任意选取两个元素,只要前者的元素比后者的元素大(顺序一定是前者大于后者),即说明两者组成了一个逆序对。

        我们以【9,7,5,4,6】为例,我们很容易发现(9,7)中,前者9大于后者的7,因此(9,7)就属于一个逆序对,同理我们还可以找到其他的逆序对(9, 5), (9, 4), (9, 6), (7, 5), (7, 4), (7, 6), (5, 4),总共加起来就是有8个逆序对。

2.2 算法原理

        这题其实与上一题的思路大体相同,当我们想要找到整个数组的逆序对,是不是可以先将这个数组分为两部分,先找到左右两部分各自的逆序对,再找左右两部分的逆序对。而找左右两部分各自的逆序对,是不是依旧是左边部分划分为左右两部分,再加上左右的,右边部分划分为左右两部分,再加上左右的……直至分到一个元素不能再继续划分为止

        因此本题也可以采用分治的思想,按照上面的思路已经可以做出这道题目了,但是我们还可以再优化一步,就是当我们在判断两个元素是否为逆序对的时候,其实就相当于在做归并排序的统计一左一右这步的判断过程,因此我们可以再统计左右两部分的逆序对时,就可以顺便将左右两部分归并排序,方便下一次的逆序对的统计,从而提高了效率。具体的代码思路可以参考上一题的归并排序

        细心的小伙伴会发现上一题我们的归并排序是采取降序的版本,但其实归并排序也可以有升序的版本,因此对于本题来说,其实是有两种方法的,一种升序,一种降序

2.3 具体代码

class Solution {
public:
    int tmp[50001];
    int reversePairs(vector<int>& record) {
        return Mmerge_count(record,0,record.size()-1);
    }

    //降序版
    int Mmerge_count(vector<int>& nums,int left ,int right)
    {
        
        if(left>= right) return 0;
        int mid=left+(right-left)/2;
        int ret =0;
        //分别单独统计左右两个部分
        ret=Mmerge_count(nums,left,mid)+Mmerge_count(nums,mid+1,right);
        //统计一左一右
        int cur1=left,cur2=mid+1,i=0;
        while(cur1 <= mid &&cur2 <=right)
        {
            if(nums[cur1] <= nums[cur2] )
            {
                 tmp[i++]=nums[cur2++];
            }
            else {
                ret+=right-cur2+1;
                tmp[i++]=nums[cur1++];
            }
        }
        while(cur1<=mid) tmp[i++]=nums[cur1++];
        while(cur2<=right) tmp[i++]=nums[cur2++];
        for(int j=left;j<=right;j++)
            nums[j]=tmp[j-left];
        return ret;
    }


    //升序版
    int Mmerge_count(vector<int>& nums,int left ,int right)
    {
        
        if(left>= right) return 0;
        int mid=left+(right-left)/2;
        int ret =0;
        //分别单独统计左右两个部分
        ret=Mmerge_count(nums,left,mid)+Mmerge_count(nums,mid+1,right);
        //统计一左一右
        int cur1=left,cur2=mid+1,i=0;
        while(cur1 <= mid &&cur2 <=right)
        {
            if(nums[cur1] <= nums[cur2] )
            {
                 tmp[i++]=nums[cur1++];
            }
            else {
                ret+=mid-cur1+1;
                tmp[i++]=nums[cur2++];
            }
        }
        while(cur1<=mid) tmp[i++]=nums[cur1++];
        while(cur2<=right) tmp[i++]=nums[cur2++];
        for(int j=left;j<=right;j++)
            nums[j]=tmp[j-left];
        return ret;
    }
};

三、结语

       

        到此为止,本文有关分治中归并的部分内容到此结束了,如有不足之处,欢迎小伙伴们指出呀!

         关注我 _麦麦_分享更多干货:_麦麦_-优快云博客

         大家的「关注❤️ + 点赞👍 + 收藏⭐」就是我创作的最大动力!谢谢大家的支持,我们下期见!

        欢迎大家参观我的算法专栏:

麦麦的算法专栏https://blog.youkuaiyun.com/m0_73953114/category_12866812.html

评论 21
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

_麦麦_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值