分治法 —— 求逆序对个数 (归并排序应用2)
求逆序对个数
题目描述
有一实数序列A[1]、A[2] 、A[3] 、……A[n-1] 、A[n] (n<10000),若i<j,并且A[i]>A[j],则称A[i]与A[j]构成了一个逆序对,求数列A中逆序对的个数。
例如,5 2 4 6 2 3 2 6,可以组成的逆序对有
(5 2),(5 4),(5 2),(5 3),(5 2),
(4 2),(4 3),(4 2),
(6 2),(6 3),(6 2),
(3 2)
共:12个
输入
共两行,第一行有一个正整数n,第二行有n个整数。
输出
只有一行为逆序对个数。
样例输入 Copy
8
5 2 4 6 2 3 2 6
样例输出 Copy
12
题目链接 - 求逆序对个数
题意: 若i<j,并且A[i]>A[j],则称A[i]与A[j]构成了一个逆序对
我相信你第一时间想到的做法即是 冒泡排序,但冒泡排序的时间复杂度为 O(n ^ 2)若数据过大非常容易超时的!!!
其实完全可以使用归并排序,听到这? 你可能会惊讶? 啥,归并也可以嘛?
归并排序的时间复杂度是 O (n log n)
解题之前我们必须清楚的知道递归式的归并排序如何写且比较熟练,不知道或者不熟的可以参考本篇博客,嘿嘿 归并排序 递归
结合图片我们不难发现核心代码重点
比如最后一次排序中 (2 4 5 6) ( 2 2 3 6),因为4 是比 (2 2 3) 大的,那么有3个逆序数且 归并排序后是有序的,因此4后面的数据 5 和 6 也是有3个逆序数,故使 t * (middle - i),一旦t使用后 我们需要重新置0的,故后使 t = 0;
Code
代码中核心代码part除外,其他地方的代码和归并排序一模一样!!!
好好理解一下为啥可以用归并排序来求逆序对个数呢?
(其实题目很明确( i < j && a[i] > a[j]) 说明只要大的数在数组前面都需要进行和数组后面小的数进行交换的,这正是归并排序做的事。我们只需要使用变量来记录计数交换的次数即可呀!)
#include <cstdio>
int MergeSort(int s[], int left, int middle, int right)
{
int i = left, j = middle;
int b[right - left + 1];
int index = 0;
int sum = 0;
int t = 0;
//----------------------------------------------
//核心代码
while (i < middle && j <= right)
{
if (s[i] > s[j])
{
b[index++] = s[j++];
t++;
}
if (s[i] <= s[j] || j == right + 1)
{// j == right + 1是防止j已经到达最右边啦,i却没有
//因此我们需要进入if条件语句中而不是直接跳出
sum += t * (middle - i);
b[index++] = s[i++];
t = 0;
}
}
//----------------------------------------------------------------
while (i < middle)
b[index++] = s[i++];
while (j <= right)
b[index++] = s[j++];
index = 0;
for (int m = left; m <= right; m++)
s[m] = b[index++];
return sum ;//最后返回给k值
}
int k = 0;
void Merge(int s[], int low, int high)
{
if (low < high)
{
int mid = (low + high) / 2;
Merge(s, low, mid);
Merge(s, mid + 1, high);
k += MergeSort(s, low, mid + 1, high);
}
}
int main()
{
int a[30005];
int n;
scanf("%d", &n);
for (int i = 0; i < n; i++)
scanf("%d", &a[i]);
Merge(a, 0, n - 1);
printf("%d\n", k);
return 0;
}
上一篇: 分治法 —— 归并排序(递归和非递归)
下一篇: 蓝桥杯模拟赛