算法篇----分治(归并排序)

一、归并排序

归并排序是一种分治算法,其基本思想是将一个大问题分解成若干个规模较小的子问题,递归地解决这些子问题,然后将子问题的解合并起来得到原问题的解。归并排序的效率较高,时间复杂度为O(n log n)。

二、步骤总结

1)归并排序通过递归将数组不断划分为更小的部分,直到每个部分只有一个元素,然后逐步合并 这些有序的部分,最终得到一个完全有序的数组。

2)归并排序的关键步骤是合并两个有序数组,这需要额外的空间(临时数组tmp)来存储合并结 果。

三、与快排的区别

二者都是通过递归来进行排序,但不同的是快排需要选择一个基准点,之后左右开工一起排,而归并排序则是将数组不断划分至最小单元,之后从左向右不断递归排序,可以类比为二叉树的后序遍历,而快排可以类比为前序遍历

四、参考习题

1、 排序数组

912. 排序数组 - 力扣(LeetCode)

这是一个典型的归并排序算法的例题,看一下代码复习一下吧~

复制代码
class Solution {
    vector<int> tmp;        //造一个辅助数组,用于全局是会节省一点开销~
public:
    vector<int> sortArray(vector<int>& nums) 
    {
        tmp.resize(nums.size());     //把辅助数组的容量给确定一下
        mergesort(nums,0,nums.size()-1);
        return nums;
    }
    void mergesort(vector<int>& nums,int left,int right)
    {
        if(left>=right) return;
        //1.选择中间点划分区间
        int mid=(left+right)>>1;
        //[left,mid] [mid+1,right]
        //2.把左右区间排序
        mergesort(nums,left,mid);
        mergesort(nums,mid+1,right);
        //3.合并两个有序数组
        int cur1=left,cur2=mid+1,i=0;
        while(cur1<=mid && cur2<=right)
            tmp[i++]=nums[cur1]<nums[cur2]?nums[cur1++]:nums[cur2++];  
            //此举就是比较左右两个区间的数,依次选小的放入辅助数组中
        //可能有数组没有遍历完,处理一下
        while(cur1<=mid) tmp[i++]=nums[cur1++];
        while(cur2<=right) tmp[i++]=nums[cur2++];
        //4.还原
        for(int i=left;i<=right;i++)
            nums[i]=tmp[i-left];

    }
};

2.交易逆序对的总数

LCR 170. 交易逆序对的总数 - 力扣(LeetCode)

解题思路:

如果暴力破解的话思路很简单,但是会超时,所以我们用分治的算法来解决一下,我们可以将数组划分成两段,之后先统计左端的逆序对个数,并进行排序,之后再统计右段的逆序对个数,并进行排序,最后再统计一左一右的逆序对个数。而左右两端的统计是通过不断递归来实现的~

在统计一左一右的逆序对个数时,我们左右两段的数组已经是排成升序的了,如图说是

在cur1左侧的都比cur1小,cur2同理,那么我们只需要找到,当nums[cur1]<=nums[cur2]时,让cur1++,而当nums[cur1]>nums[cur2]时,让ret+=right-cur2+1即可,之后cur2++,继续该操作

复制代码
class Solution 
{
    int tmp[50010];
public:
    int reversePairs(vector<int>& nums) 
    {
        return mergesort(nums,0,nums.size()-1);
    }
    int mergesort(vector<int>& nums,int left,int right)
    {
        if(left>=right) return 0;
        int ret=0;
        //1.找中间点,将数组分为两端
        int mid=(left+right)>>1;
        //[left,mid] [mid+1,right]
        //2.左边个数+排序+右边个数+排序
        ret+=mergesort(nums,left,mid);
        ret+=mergesort(nums,mid+1,right);
        //3.一左一右计数
        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;
    }
};

3.计算右侧小于当前元素的个数

解题思路:

这道题答题思路跟上一条差不多,但是增加的难的点是如何确定下标,更确切的说是如何保证下标和元素一直保持一一对应,为此我们可以仿照哈希表,在构造一个Index的数组,存放各个的下标,值得注意的是,元素在进行排序时,要记得对下标也进行对应的移动

复制代码
class Solution 
{
    vector<int> ret;
    vector<int> index;//// 记录 nums 中当前元素的原始下标
    int tmpindex[500010];
    int tmpnums[500010];
public:
    vector<int> countSmaller(vector<int>& nums) 
    {
        int n=nums.size();
        ret.resize(n);
        index.resize(n);
        // 初始化⼀下 index 数组
        for(int i=0;i<n;i++)
        {
            index[i]=i;
        }
        mergesort(nums,0,nums.size()-1);
        return ret;
    }
   void  mergesort(vector<int>& nums,int left,int right)
   {
    if(left>=right) return;
    int mid=(right+left)>>1;
    mergesort(nums,left,mid);
    mergesort(nums,mid+1,right);
    int cur1=left,cur2=mid+1,i=0;
    while(cur1<=mid&&cur2<=right)  //降序
    {
        if(nums[cur1]<=nums[cur2])
        {
            tmpnums[i]=nums[cur2];
            tmpindex[i++]=index[cur2++];
        }
        else
        {
            ret[index[cur1]]+=right-cur2+1;
            tmpnums[i]=nums[cur1];
            tmpindex[i++]=index[cur1++];
        }
    }
    //4.处理剩下的排序过程
    while(cur1<=mid)
    {
        tmpnums[i]=nums[cur1];
        tmpindex[i++]=index[cur1++];
    }
    while(cur2<=right)
    {
        tmpnums[i]=nums[cur2];
        tmpindex[i++]=index[cur2++];
    }
    for(int j=left;j<=right;j++)
    {
        nums[j]=tmpnums[j-left];
        index[j]=tmpindex[j-left];
    }
   }
};