归并排序及模板

归并排序及模板


1.思想

    归并排序也是基于分治法的思想。
    1.  确定分界点(一般为中间点mid=(l+r)/2)使得整个数组被划分为leftright区间。
    2.  递归排序leftright区间。
    3.  归并-合二为一。进而将整个数组排序完成。(注意:在归并的时候,leftright区间都应该是有序的。归并之后的数组仍是一个有序数组)。
    在上述步骤中,最难的部分就是归并的部分。

img

    在这里,我们采用双指针算法来进行归并,具体思想如下:
    假设,我们需要归并的数组为ab(已经有序)。
    1.  设定指针i,指向数组a的第一个位置(数组a的最小值)。同时设定指针j,指向数组b的第一个位置(数组b的最小值)。同时开辟数组res,用来存储归并之后的数组。
    2.  将i和j指向的元素进行比较,如果i小于等于j,那么就把i指向的元素放到res中,同时i往下走一格,反之同理。
    3.  当ab至少有一个遍历完成时,双指针算法结束,但是需要把没有遍历完的数组的剩余元素接到res的后面,至此双指针算法结束。
    在上述过程中,虽然会花费额外空间,但是时间复杂度良好。是O(n)
    结合上述的算法,我们来分析一下归并排序的时间复杂度。首先,整个数组在第一次递归中,被划分为了2个区间。第二次递归,被划分为了4个区间,.....最后被划分为n个长度只有1的区间(此时每个区间都是天然有序的)。那么,从1个长度为n的区间,到n个长度为1的区间,中间划分了logn次。然而,划分区间之后,就形成了一颗二叉树。当我们处理二叉树的每一层时(归并),花费的时间复杂度都是O(n)。
    综上所述,归并排序的时间复杂度是O(nlogn)。
    快速排序的平均时间复杂度也是O(nlogn),但是最坏时间复杂度为O(n²)。

img

2.模板

void merge_sort(int q[], int l, int r)
{
    if (l >= r) return;

    int mid = l + r >> 1;
    merge_sort(q, l, mid);
    merge_sort(q, mid + 1, r);

    int k = 0, i = l, j = mid + 1;
    while (i <= mid && j <= r)
        if (q[i] <= q[j]) tmp[k ++ ] = q[i ++ ];
        else tmp[k ++ ] = q[j ++ ];

    while (i <= mid) tmp[k ++ ] = q[i ++ ];
    while (j <= r) tmp[k ++ ] = q[j ++ ];

    for (i = l, j = 0; i <= r; i ++, j ++ ) q[i] = tmp[j];
}

3.例题与习题

    地址:https://www.acwing.com/problem/content/789/
#include <iostream>
#include <cstdio>

using namespace std;

void merge_sort(int q[],int l,int r){
    if(l >= r){
        return;
    }
    //划分
    int mid = (l+r) >> 1;
    //递归
    merge_sort(q,l,mid);
    merge_sort(q,mid+1,r);
    //归并
    int res[100010];
    int i = l;
    int j = mid + 1;
    int k = 0;
    while(i <= mid && j <= r){
        if(q[i] <= q[j]){
            res[k++] = q[i++];
        }else{
            res[k++] = q[j++];
        }
    }
    while(i <= mid){
        res[k++] = q[i++];
    }
    while(j <= r){
        res[k++] = q[j++];
    }
    //将归并完的数组,复制到原来的数组上
    for(i=l,k=0;i<=r;i++,k++){
        q[i] = res[k];
    }
}

int main(){
    int n;
    scanf("%d",&n);
    int q[100010];
    for(int i=0;i<n;i++){
        scanf("%d",&q[i]);
    }
    merge_sort(q,0,n-1);
    for(int i=0;i<n;i++){
        printf("%d ",q[i]);
    }
    return 0;
}
    地址:https://www.acwing.com/problem/content/790/
    该算法的求解思路如下:
        1.  我们假设归并排序在给某一区间排序的过程中,就可以把该区间的逆序数找到,那么我们可以将逆序数对的所在区间情况分为三种:
            1.  逆序数对都在区间左边,那么进行递归即可。即:merge_sort(q,l,mid)
            2.  逆序数对都在区间右边,那么进行递归即可。即:merge_sort(q,mid+1,r)
            3.  如果逆序数对在两个区间(一个在左区间,一个在右区间),那么统计逆序对的数量。
            由于归并排序的特点,我们可以发现,当进行递归后,最终会达到区间只有一个数字的情况,即逆序数对在左右两个区间。当统计完局部区间的逆序数对之后,再对整体区间的逆序数对进行统计即可。
        2.  我们如何统计逆序数对呢?这时需要用到归并排序的双指针算法,假设i指向左区间的第一个位置,j指向右区间的第一个位置,tmp代表将两个区间归并之后的数组。那么,如果q[i] > q[j] 那么i之后的数,一直到mid,都大于q[j]。因此,q[j]的逆序对数量为(mid-i+1),将q[j]存到tmp数组之后,再让j往后移动一个位置,再进行比较。如果q[i] <= q[j],那么不构成逆序数对。将q[i]存到tmp数组之后,再让i往后移动一个位置,再进行比较。当遍历完这两个数组其中之一或全部时,再将所求得的逆序对数量进行累加即可。之后按照归并排序的流程,我们就可以求得整个区间的逆序数对。
#include <iostream>
#include <cstdio>

using namespace std;

//代表全局的逆序对数量
long long result = 0;

long long merge_sort(int q[],int l,int r){
    if(l >= r){                 //如果区间里面没有数或只有一个数,那么没有逆序对
        return 0;
    }
    int mid = (l+r) >> 1;
    int i = l;
    int j = mid + 1;
    int tmp[100010];
    long long count = 0;              //代表局部的逆序对数量
    int k = 0;
    //如果逆序对都在左区间中,那么就进行递归
    //如果逆序对都在右区间中,那么就进行递归
    result += merge_sort(q,l,mid) +  merge_sort(q,mid+1,r);            
    //如果逆序对分别在左右区间中,那么就进行统计
    while(i <= mid && j <= r){
        if(q[i] > q[j]){        //构成了逆序对的条件
            tmp[k++] = q[j++];
            count += mid - i + 1; //计算逆序对
        }else{
            tmp[k++] = q[i++];
        }
    }
    //收尾
    while(i <= mid){
        tmp[k++] = q[i++];
    }
    while(j <= r){
        tmp[k++] = q[j++];
    }
    //物归原主
    for(i = l,j = 0;i<=r;i++,j++){
        q[i] = tmp[j];
    }
    return count;
}

int main(){
    int n;
    int q[100010];
    scanf("%d",&n);
    for(int i=0;i<n;i++){
        scanf("%d",&q[i]);
    }
    result += merge_sort(q,0,n-1);
    printf("%lld",result);
    return 0;
}
    作者:gao79138
    链接:https://www.acwing.com/
    来源:本博客中的截图、代码模板及题目地址均来自于Acwing。其余内容均为作者原创。
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
posted @   夏目^_^  阅读(42)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
点击右上角即可分享
微信分享提示