一、归并排序原理
归并排序是建立在归并操作上的一种有效,稳定的排序算法,该算法是采用(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。
归并排序与快速排序的一大区别是,归并排序先分治、再归并,而快速排序是先利用分界点交换数据,再分两段递归。
归并排序的基本思路如下:
- 确定分界点:mid = (left + right) / 2;
- 递归排序:left ~ mid 和 mid+1 ~ right;
- 归并——合二为一。
归并排序的代码模板如下:
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,temp[r+2];
while(i<=mid&&j<=r)
{
if(q[i]<=q[j]) temp[k++] = q[i++];
else temp[k++] = q[j++];
}
while(i<=mid) temp[k++] = q[i++];
while(j<=r) temp[k++] = q[j++];
for(i=l,j=0;i<=r&&j<k;i++,j++)
q[i] = temp[j];
}
二、逆序对的数量
题目描述
给定一个长度为 n 的整数数列,请你计算数列中的逆序对的数量。
逆序对的定义如下:对于数列的第 i 个和第 j 个元素,如果满足 i<j 且a[i]>a[j],则其为一个逆序对,否则不是。
输入格式
第一行包含整数 n,表示数列的长度。
第二行包含 n 个整数,表示整个数列。
输出格式
输出一个整数,表示逆序对的个数。
数据范围
1≤n≤100000,
数列中的元素的取值范围 [1,1000000000]。输入样例:
6 2 3 4 5 6 1
输出样例:
5
题目链接:
先思考这个题目,对于数列的第 i 个和第 j 个元素,如果满足 i < j 且 a[i] > a[j],则其为一个逆序对。然而,一个元素可以不只是在一个逆序对中存在。如果 k > j > i 且 a[i] > a[j] > a[k],那么这里有两个含 a[i] 的逆序对,分别是 (a[i], a[j]) 和 (a[i], a[k]), a[i]是可以使用多次的。
此题可以用分治的方法来解决,当我们将一个序列分成两半后,逆序对无非三类:两个数都在左边、两个数都在右边、一左一右。前两种情况都可以通过进一步的分治递归来解决,所以对于函数内,我们需要考虑的是如何计算一左一右时的逆序对数量。
假设i<mid,j>mid且a[i]>a[j],由于序列左右两边已经分别排序过,所以下标位于i~mid的数都比a[j]要大,所以逆序对数不止加1,而应该加上(mid - i + 1)。退出此循环后,无论如何也无法再构成逆序对,因此其余部分照着原归并排序写即可。
以下为实现的代码:
#include<bits/stdc++.h>
using namespace std;
const int N = 500010;
int w[N];
long long res = 0;
void merge_sort(int left,int right);
int main()
{
int n,i;
scanf("%d",&n);
for(i=0;i<n;i++) scanf("%d",&w[i]);
merge_sort(0,n-1);
cout<<res<<endl;
return 0;
}
void merge_sort(int left,int right)
{
if(left>=right) return;
int mid = (left+right)>>1;
merge_sort(left,mid);
merge_sort(mid+1,right);
int i=left,j=mid+1,k=0;
int temp[N];
while(i<=mid && j<=right)
{
if(w[i]<=w[j]) temp[k++] = w[i++];
else{
res += (mid - i + 1);
temp[k++] = w[j++];
}
}
while(i <= mid) temp[k++] = w[i++];
while(j <= right) temp[k++] = w[j++];
for(i=left,j=0;i<=right&&j<k;i++,j++)
w[i] = temp[j];
return;
}