归并排序——分治
基本思想
假定排序表有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;
}