归并排序——分治(C语言)

归并排序——分治

基本思想

假定排序表有n个元素,则可以看做n个有序表,每个子表长度为1,然后两两归并,依次重复,直到合并成一个长度为n的有序表为止。

基本步骤

1、确定分界点 mid = (l + r) / 2,将序列划分为两个序列
2、通过递归排序左右两段序列
3、归并——合二为一(方法:设置两个指针分别指向q[l] 和 q[mid + 1], 再找一个空数组,比较两指针所指的元素,将指针所指元素较小的元素复制到空数组中,指针向后移动,依次类推,最后将剩余的数组直接复制进入新的数组中)

基本模块
来源于yxc

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];
    
}

经典例题

1、ACWing 787.归并排序
给定你一个长度为n的整数数列。

请你使用归并排序对这个数列按照从小到大进行排序。

并将排好序的数列按顺序输出。

输入格式
输入共两行,第一行包含整数 n。

第二行包含 n 个整数(所有整数均在1~109范围内),表示整个数列。

输出格式
输出共一行,包含 n 个整数,表示排好序的数列。

数据范围
1≤n≤100000
输入样例:

5
3 1 2 4 5

输出样例:

1 2 3 4 5

解题步骤:
1、将中间点mid = (l + r)/2 作为分界点
2、将分界点左右两段递归排序
3、引入空指针tmp,将指针i指向数组q的最左端L,指针j指向mid + 1,比较两个指针所指元素的大小,较小的存入tmp数组中,移动指针,继续比较(最后考虑左右两段序列是否有剩余,若有则直接复制到tmp数组中)
4、将tmp里面的元素复制回q数组中

#include<stdio.h>
#include<stdlib.h>

#define N  100010

int n;
int q[N], tmp[N];
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;//k用来指定合并后的序列,i, j分别指向q[l], q[mid + 1]
    while(i <= mid && j <= r)//循环比较指针i,j所指元素较小的存储到tmp数组中
         if(q[i] <= q[j]) tmp[k++] = q[i++];
         else tmp[k++] = q[j++];
    
    while(i <= mid) tmp[k++] = q[i++];//循环结束后,若左边序列有剩余,将左边剩余的数直接复制到tmp数组中
    
    while(j <= r) tmp[k++] = q[j++];//循环结束后,若右边序列有剩余,将左边剩余的数直接复制到tmp数组中
    
    for(i = l, j = 0; i <= r ; i++, j++) q[i] = tmp[j];//将tmp里面的指复制到q数组中
    
}

int main()
{
    scanf("%d",&n);
    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;
    
}

2、逆序对的数量
给定一个长度为n的整数数列,请你计算数列中的逆序对的数量。

逆序对的定义如下:对于数列的第 i 个和第 j 个元素,如果满足 i < j 且 a[i] > a[j],则其为一个逆序对;否则不是。

输入格式
第一行包含整数n,表示数列的长度。

第二行包含 n 个整数,表示整个数列。

输出格式
输出一个整数,表示逆序对的个数。

数据范围
1≤n≤100000
输入样例:

6
2 3 4 5 6 1

输出样例:

5

解题思路:利用归并排序的思想,先将序列按中间点为分界点分为两部分,算出左右两端的逆序对数,最后计算跨左右两端的逆序对数

解题步骤:
1、以中间点mid为分界点
2、利用递归分别计算左右两段的逆序对数
3、比较两段q[i] 和q[j] 计算此时跨分界点的逆序对数为mid - i + 1(注意), 因为去q[i] > q[j], 则从q[i] ~q[mid] 都大于q[j],同时归并排序
4、将tmp数组中的元素复制到q数组中
**

#include<stdlib.h>
#include<stdio.h>

#define N 100010

int n;
int q[N], tmp[N];

long int merge_sort(int l, int r)
{
    if(l >= r) return 0 ;
    
    int mid = (l + r) >> 1 ;//以中间点作为分界点
  long int res = merge_sort(l, mid) + merge_sort(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++];
             res += mid - i + 1;
         }
         //扫尾,将剩余的元素直接复制到tmp数组中
    while(i <= mid) tmp[k++] = q[i++];
    while(j <= r) tmp[k++] = q[j++];
    //物归原主
    for(int i = l , j = 0; i <= r; i++, j++)
    q[i] = tmp[j];
    
    return res;
}
int main()
{
   scanf("%d", &n);
    for(int i = 0; i < n ; i++)
    scanf("%d", &q[i]); 
    printf("%ld", merge_sort(0, n - 1) );
    return 0;
}

  • 2
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值