分治法 —— 求逆序对个数 (归并排序应用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;
}
  • 25
    点赞
  • 118
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
分治法逆序对个数c的实现如下: 1. 将组a分成两个子组a1和a2,分别对a1和a2递归地逆序对个数c1和c2,然后合并a1和a2并计算跨越a1和a2的逆序对个数c3。 2. 合并a1和a2时,设置两个指针i和j分别指向a1和a2的起始位置,然后将较小的元素放入新组b中,并将指针向后移动,直到其中一个子组遍历完毕。此时,将另一个子组的剩余元素全部放入b中。 3. 计算跨越a1和a2的逆序对个数c3时,设置两个指针i和j分别指向a1和a2的末尾位置,并将它们向前移动,同时设置一个计器count用于计算跨越a1和a2的逆序对个数。比较a1[i]和a2[j]的大小,如果a1[i]>a2[j],则说明a2[j]和a1[i+1]到a1[n-1]都构成了逆序对,因为a1[i+1]到a1[n-1]都比a2[j]大,此时将count加上i+1的值,并将a1[i]放入新组b中,然后将指针i向前移动;否则,将a2[j]放入新组b中,并将指针j向前移动。 4. 返回c1+c2+c3作为逆序对个数。 代码实现如下: ``` int mergeSort(vector<int>& a, int left, int right) { if (left >= right) return 0; int mid = (left + right) / 2; int c1 = mergeSort(a, left, mid); int c2 = mergeSort(a, mid + 1, right); vector<int> b(right - left + 1); int i = left, j = mid + 1, k = 0, c3 = 0; while (i <= mid && j <= right) { if (a[i] <= a[j]) b[k++] = a[i++]; else { b[k++] = a[j++]; c3 += mid - i + 1; } } while (i <= mid) b[k++] = a[i++]; while (j <= right) b[k++] = a[j++]; for (int i = left; i <= right; i++) a[i] = b[i - left]; return c1 + c2 + c3; } int countInversePairs(vector<int>& nums) { return mergeSort(nums, 0, nums.size() - 1); } ```
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值