归并排序求逆序对

逆序对的定义:在一个数组a中如果有两个元素满足i < j && a[i] > a[j],那么(i, j)就称为数组中的一个逆序对

现在,我们要求的是,给定一个长度为N的数组a,求出该数组的逆序对的个数

比如 int a[4] = {7, 5, 6, 4} 逆序对为(7, 6), (7, 5), (7, 4), (6, 4), (5, 4)


1.常规的做法是依次枚举所有的元素对(a[i], a[j]),显然,这样的时间复杂为平方级别的O(n^2)



2.还有一种做法是基于冒泡排序,从最后一个元素开始,逐次往上面冒,每当交换发生的时候,逆序对的个数就加1,简易证明如下

a[1]a[2]a[3]...a[i]a[i+1]..a[n-1],假定之前逆序对的个数为l,现在在冒泡的过程中发现a[i+1] > a[i], 显然要交换这两个元素的位置,并且一个逆序对已经产生,即(a[i], a[i+1]),交换之后的数组为a[1]a[2]a[3]...a[i+1]a[i]..a[n-1],这个时候,任意两个元素l,m != i, j(a[l],a[m])如果在交换之前满足逆序,那么在交换之后同样满足逆序,因为他们的位置并没有发生变化,因此,此次交换对逆序对的总数没有影响,再由于冒泡是从后往前冒,所以能够保证每一次交换产生的逆序对在原来的数组中都存在,证毕!

由于冒泡排序可以通过各种优化来降低时间复杂度,但是最坏情况下冒泡的时间复杂度依然是O(n^2)



3.基于分治的思想,我们可以将数组分为两半,假定左边的逆序对已经求毕,右边的逆序对也已经求毕,比如还是数组int a[4] ={7, 5, 6, 4},左半边{7, 5}逆序对为1, 右半边{6, 4}逆序对为1,剩下的逆序对肯定是一个元素在左半边,另一个元素在右半边,那么,在O((n^2)/4)的时间内可以枚举所有的这种类型的逆序对,但是根据主定理

T(n) = 2*T(n/2) + O(n^2)最后计算的时间复杂度T(n)依然是平方级别的,有没有什么更好的办法呢?

我们继续思考,如果我们假定左半边和右半边的数组均已经排好序了,比如左半边的数组为{5, 7}右半边的数组为{4, 6},我们首先比较7和6,发现7>6由于7在数组左半边,6在数组右半边,显然7在原数组中的序号小于6在原数组中的序号,(7, 6)满足逆序对,由于右半边的数组是排好序的,也就是说6之前的所有元素都比6小,当然也比7小,所以6之前的所有元素和7也满足逆序对的条件,这样就免去了6之前所有元素的扫描。

这样,每当前半部分的当前元素p大于后半部分的当前元素q,逆序对就增加右半部分区间中q之前的元素个数,并且将p向左边移动

如果p <= q显然p,q不满足逆序对,那么直接将q往前移一步继续比较,直到两边的元素至少有一边比较完毕


以上的步骤都是基于左半边和右半边都已经排好序的情况下,这个假定貌似有点呵呵了,但是,如果聪明的你熟悉归并排序的话,这个假定对你来说就是小菜一碟了,归并排序的思想就是每次将左半边和右半边的元素分别排序,然后将排好序的元素归并, 这个归并过程我们刚好可以“顺便”计算一下逆序对,只要将归并的过程从两半的数组尾部开始进行即可,C++实现代码如下,你也可点击这里(IE浏览器除外)直接运行下面的代码

#include <iostream>
#include <string>
#define MAX 110
using namespace std;

int merge_sort_main(int a[], int copy[], int begin, int end)
{
  if (begin == end)
    return 0;
  int mid = begin + (end - begin) / 2;
  int left = merge_sort_main(copy, a, begin, mid);
  int right = merge_sort_main(copy, a, mid + 1, end);
  int i = mid, j = end, k = end + 1;
  int ans = left + right;
  while (i >= begin && j >= mid+1)
  {
    if (copy[i] > copy[j])
    {
      a[--k] = copy[i--];
      ans += j - mid;
    }
    else
      a[--k] = copy[j--];
  }
  while (i >= begin)
      a[--k] = copy[i--];
  while (j >= mid + 1)
      a[--k] = copy[j--];
  return ans;
}
int merge_sort(int a[], int n)
{
  int copy[MAX];
  for (int i = 0; i < n; ++i)
    copy[i] = a[i];
  return merge_sort_main(a, copy, 0, n-1);
}

int main() {
  int a[10] = {7, 5, 6, 4};
  cout << merge_sort(a, 4) << endl;
  return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值