归并排序(merge_sort):从二路到多路

归并排序采用分治策略,分别处理左右两侧信息,然后合并。多路归并排序利用小顶堆选择最小元素。由于能使用外存,归并排序在大数据排序中应用广泛。涉及题目包括逆序对、合并链表、排序链表等,重点在于处理跨区间信息。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

归并排序基础思想就是 分治

左边处理一下,得到左边的信息;右边处理一下,得到右边的信息;最后再处理,横跨左右两边的信息。
代码演示:


```cpp
//二路归并
void merge_sort(int  *arr,int l,int r){
    if(l>=r) return ;
    int mid = (l+r)>>2;
    merge_sort(arr,l,mid);//left sort
    merge_sort(arr,mid+1,r);//right sort
    int *temp = (int *)malloc(sizeof(int)*(r-l+1));//开辟额外的存储空间
    int k=0,p1=l,p2=mid+1;
    while(p1<=mid||p2<=r){
        if(p2>r||(p1<=mid&&arr[p1]<=arr[p2])){
            temp[k++] = arr[p1++]; 
        }else{
            temp[k++] = arr[p2++];
        }
    }
    //将temp拷贝到原来的数组中去
    for(int i=l;i<=r;i++) arr[i]=temp[i-l];
    free(temp);
    return ;
}

当多路排序时 我们使用小顶堆来进行选择每个集合中的最小元素
因为归并排序可以使用外存和快速排序只能放在内存中(内部排序)存在区别
(最后merge_sort处理的也只是文件指针而不是文件数据)
所以归并排序是大数据排序里面使用较多的一种排序算法
例题:
剑指 Offer 51. 数组中的逆序对

class Solution {
public:
//利用归并排序求解逆序数
    vector<int> temp;//额外存储区
    int countResult(vector<int> &nums,int l,int r){
        if(l>=r) return 0;//只包含一个数或者空区间的逆序数数量为0
        int ans=0,mid=(l+r)>>1;
        ans+=countResult(nums,l,mid);
        ans+=countResult(nums,mid+1,r);
        int k=0,p1=l,p2=mid+1;//这里k也可以等于l 但是最后遍历赋值时候(18row)改成nums[i]=temp[i];
        while(p1<=mid||p2<=r){
            if(p2>r||(p1<=mid&&nums[p1]<=nums[p2])) temp[k++]=nums[p1++];
            else {
                temp[k++] = nums[p2++];
                ans+=(mid-p1+1);//在归并排序时候就可以计算左边的所有比nums[p2]大的nums[p1]后面的元素
            }
        }
        for(int i=l;i<=r;i++) nums[i]=temp[i-l];
        return ans;

    }
    int reversePairs(vector<int>& nums) {
        //先求左边序列的逆序数再求右边数列的逆序数
        //再求两边产生的逆序数
        while(temp.size()<nums.size()) temp.push_back(0);//将temp数组的大小扩展成和nums数组的大小一样
        return countResult(nums,0,nums.size()-1);
    }
};
  1. 合并K个升序链表
class Solution {
public:
//这里使用堆、链表、归并
//在k各元素中找到最小值,使用小顶堆进行加速
//定义比较规则
    struct CMP{
        bool operator()(ListNode* p,ListNode* q){
            return p->val>q->val;
            }
    };
    ListNode* mergeKLists(vector<ListNode*>& lists) {
        //因为在C++中我们有堆(优先队列)priority_queue可以直接使用
        //定义小顶堆q
        priority_queue<ListNode *,vector<ListNode *>,CMP> q;//队列存储k个指针的位置
        //依次将k个链表头部加入优先队列当中去
        for(auto x: lists){
            if(x == nullptr) continue;
            q.push(x);
        }
        //合并列表虚拟头结点和合并链表末位结点的指针p
        ListNode ret,*p=&ret;
        while(!q.empty()){
            ListNode *cur=q.top();
            q.pop();
            p->next = cur;
            p=cur;
            if(cur->next) q.push(cur->next);//如果cur的下一个节点不为空需要将其压入优先队列当中去作为下一轮我们的带选取元素
        }
        return ret.next;
    }
};
  1. 排序链表(这题我在前面快速排序实现过 可以参考之前博客,这里使用归并排序使用算法)
class Solution {
public:
//链表排序最好的方式是归并排序
    ListNode *mergeSort(ListNode *head,int n){
        if(head == nullptr || head->next == nullptr) return head;
        //拆分成左右两边链表
        int l = n/2,r=n-l;
        ListNode *lp = head,*rp = lp,*p;//设置中间指针p
        //我们需要将链表拆成两半就需要找到右半部分首地址的前一位=》rp指向右半部分链表首地址的前一位
        for(int i=1;i<l;i++,rp=rp->next);
        //将左右两半部分拆开
        p=rp,rp=rp->next;
        p->next = nullptr;
        lp = mergeSort(lp,l);//left sort
        rp = mergeSort(rp,r);//right sort
        //将两个有序链表合并成一个有序链表
        ListNode ret;
        p= &ret;//p指向的就是合并后链表的最后一个节点
        while(lp || rp){
            if((rp == nullptr)||(lp&&lp->val<=rp->val)){
                p->next=lp;
                lp = lp->next;
                p=p->next;                
            }else{
                p->next=rp;
                rp = rp->next;
                p=p->next;
            }
        }
        return ret.next;
    }
 ListNode* sortList(ListNode* head) {
 int n=0;
        ListNode *p=head;
        while(p) p=p->next,n+=1;
        return mergeSort(head,n);
        
    }
};
  1. 两棵二叉搜索树中的所有元素
class Solution {
public:
    void getResult(TreeNode* root,vector<int>&arr){
        if(root==nullptr) return ;
        getResult(root->left,arr);
        arr.push_back(root->val);
        getResult(root->right,arr);
        return ;
    }
    vector<int> getAllElements(TreeNode* root1, TreeNode* root2) {
        //二叉搜索树先通过中序遍历得到两个有序序列 然后再通过归并得到一个大的有序序列
        vector<int>lnums,rnums;
        
        getResult(root1,lnums);
        getResult(root2,rnums);
        vector<int> ret;
        int l=0,r=0;
        while(l<lnums.size()||r<rnums.size()){
            if(r>=rnums.size()||(l<lnums.size()&&lnums[l]<=rnums[r])){
                ret.push_back(lnums[l++]);
        }else{
                ret.push_back(rnums[r++]);   
        }
        }
        return ret;
    }
};
  1. 区间和的个数
    [难点在于跨左右两半区间的信息]
class Solution {
public:
    int coutTwoPart(vector<long long>&sum,int l1, int r1,int l2,int r2,int lower,int upper){
        int ans = 0,k1 = l1,k2= l1;
        for(int j=l2;j<=r2;j++){
            long long a =sum[j] - upper;
            long long b = sum[j] -lower;
            while(k1<=r1 && sum[k1]<a) k1+=1;
            while(k2<=r1 && sum[k2]<=b) k2+=1;//左闭右开
            ans += k2-k1;
        }
        return ans;
    }
    int mergeSort(vector<long long>&sum,int l,int r,int lower,int upper){
        if(l>=r) return 0;//只有一个元素或者没有就找不到满足条件的数值对
        int mid = (l+r)>>1,ans = 0;
        ans+=mergeSort(sum,l,mid,lower,upper);
        ans+=mergeSort(sum,mid+1,r,lower,upper);
        ans+=coutTwoPart(sum,l,mid,mid+1,r,lower,upper);//统计横跨两部分区间的答案信息
        //合并两部分有序数组
        int k=l,p1=l,p2=mid+1;
        while(p1<=mid||p2<=r){
            if(p2>r||(p1<=mid&&sum[p1]<=sum[p2])){temp[k++]=sum[p1++];}

            else {temp[k++] = sum[p2++];}
        }
        for(int i=l;i<=r;i++) sum[i]=temp[i];
        return ans;
    }
    vector<long long>temp;
    int countRangeSum(vector<int>& nums, int lower, int upper) {
//区间和 =》 前缀和: 区间和=前缀和数组中两项相减的值
//题目可改写为在前缀和sum数组中 lower<=sum[i]-sum[j]<=upper
    //在归并排序过程中解决问题
    vector<long long>sum(nums.size()+1);//前缀和数组(前缀和数组第一位为0所以这里为size+1);
    while(temp.size()<sum.size())temp.push_back(0);
    sum[0]=0;
    for(int i=0;i<nums.size();i++)sum[i+1]=sum[i]+nums[i];
    //然后在前缀和数组排序过程中求解答案
    return mergeSort(sum,0,sum.size()-1,lower,upper);
    }
};
  1. 计算右侧小于当前元素的个数

```c
class Solution {
public:
    struct Data{
        Data(int val,int ind):val(val),ind(ind),cnt(0){}
        bool operator>(const Data &a){//自己设定比较关系
            return val > a.val;
        }
        int val,ind,cnt;
    };
    void mergeSort(vector<Data>&arr,int l,int r){
        if(l>=r) return ;
        int mid =(l+r)>>1;
        mergeSort(arr,l,mid);
        mergeSort(arr,mid+1,r);
        int p1=l,p2=mid+1,k=l;
        while(p1<=mid||p2<=r){
            if(p2>r||(p1<=mid&&arr[p1]>arr[p2])){
                arr[p1].cnt += (r-p2+1);
                temp[k++] = arr[p1++];
            }
            else{
                temp[k++] = arr[p2++];
            }
        }
        for(int i=l;i<=r;i++) arr[i] = temp[i];
        return ;

    }
    vector<Data>temp;
    vector<int> countSmaller(vector<int>& nums) {
        vector<Data> arr;
        
        for(int i=0;i<nums.size();i++)arr.push_back(Data{nums[i],i});
        while(temp.size()<arr.size())temp.push_back(Data{0,0});
        mergeSort(arr,0,arr.size()-1);
        vector<int> ret(nums.size());
        for(auto x:arr)ret[x.ind] = x.cnt; 
        return ret;
    }
};
1508. 子数组和排序后的区间和

```c
class Solution {
public:
    struct Data{
        Data(int i,int j,long long  sum):i(i),j(j),sum(sum){}

        int i,j,sum;
    };
    struct CMP{
        bool operator()(const Data &a,const Data &b){
            return a.sum > b.sum;
        }
    };
    int rangeSum(vector<int>& nums, int n, int left, int right) {
        priority_queue<Data,vector<Data>,CMP>q;
        for(int i=0;i<n;i++){
            q.push(Data{i,i,nums[i]});
        }
        int ans = 0,mod = 1e9+7;
        for(int i=1;i<=right;i++){
            Data d = q.top();
            q.pop();
            if(i>=left) ans =(ans +d.sum)%mod ;
            if(d.j+1<n)q.push(Data{d.i,d.j+1,d.sum+nums[d.j+1]});
        }
        return ans;

    }
};

面试题 04.08. 首个共同祖先

class Solution {
public:
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
//一遍递归求得
    if(root == NULL) return NULL;
    if(root == p|| root == q) return root;
    TreeNode *l =lowestCommonAncestor(root->left,p,q);
    TreeNode *r =lowestCommonAncestor(root->right,p,q);
    if(l!=NULL && r!=NULL) return root;
    if(l!=NULL) return l;
    return r;

    }
};
  1. 层数最深叶子节点的和
class Solution {
public:
     
    void getResult(TreeNode*root ,int k,int &max_k,int &ans){//k为当前节点的深度
        if(root == nullptr) return ;
        if(k == max_k) ans += root->val;
        if(k>max_k){
            max_k =k;
            ans = root->val;
        }
        getResult(root->left,k+1,max_k,ans);
        getResult(root->right,k+1,max_k,ans);
        return;
    }
    int deepestLeavesSum(TreeNode* root) {
        int max_k=0,ans=0;
        getResult(root,1,max_k,ans);
        return ans;
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值