求逆序数 分治思想求解
问题描述
给定一长度为n的序列,请求出该序列的逆序数。要求使用分治法实现。
输入:
2,5,4,3,8,11
输出
逆序数:3
思想
1.假如两个数字大小顺序是相反的,比如31,那么这两个数字就构成一组逆序数;
2.假设有序列A(2,5,4,3,8,11),对于求该整个序列的逆序数,等价于求左右两边各一半每个序列的逆序数;
3.因此,可以采用分治的方法将原问题缩减成规模更小的,具有相同性质的子问题;
4.当问题分解到规模只有一个时,可直接解决该问题;
5.当左右两边的子问题解决完之后,即可解决大的问题;
而对于归并排序,容易知道,虽然整个序列在一趟归并排序之前是无序的,但是,左右两边的子序列是相对有序的,也就是左边的数都小于右边的数(假如按从小到大排序),那么此时,当我们在对两个序列进行归并排序的同时,便可以十分快速地计算出每个数字的逆序数,具体计算方法如下:
对于序列low—(mid)—>high,比较左边的某个数字arr[i]与右边的某个数字arr[j]时,假如arr[i]>arr[j],那么这两个数字就构成一对逆序数,由于左边序列的数字都比右边序列的数字大,而arr[i]到arr[mid]之间的数又比arr[i],那么arr[i]后面的数字就更比当前的arr[j]大了,因此逆序数应该加mid-i+1.
注意!我们是在归并排序的基础上计算逆序数的,因此原本的归并排序还是需要进行,而且必须进行,否则就算出来不对的结果。
Code
请结合代码加深对该算法的实现的理解。
#include"stdio.h"
#include"iostream"
using namespace std;
#define Maxn 9999
// 归并排序
int t[Maxn];
void merge(int* arr,int low,int mid,int high)
{
int i=low,j=mid+1,k=low;
while(i<=mid&&j<=high)
{
if(arr[i]<arr[j])
t[k++]=arr[i++];
else
t[k++] = arr[j++];
}
while(i<=mid)t[k++]=arr[i++];
while(j<=high)t[k++]=arr[j++];
for(i=low;i<=high;i++)
arr[i] = t[i];
}
void mergeSort(int* arr,int i,int j)
{
if(i<j)
{
int mid = (i+j)/2;
mergeSort(arr,i,mid);
mergeSort(arr,mid+1,j);
merge(arr,i,mid,j);
}
}
// 计算逆序数
int cnt=0;
void count(int*arr,int low,int mid,int high)
{
int i=low,j=mid+1,k=low;
while(i<=mid&&j<=high)
{
if(arr[i]<=arr[j])
t[k++] = arr[i++];
else
{
cout<< "cnt=" <<cnt<<endl;
cnt += mid-i+1;
t[k++] = arr[j++];
}
}
while(i<=mid)t[k++] = arr[i++];
while(j<=high)t[k++] = arr[j++];
for(i=low;i<=high;i++)
arr[i]=t[i];
}
void reverseNumber(int*arr,int i,int j)
{
if(i<j)
{
int mid = (i+j)/2;
reverseNumber(arr,i,mid);
reverseNumber(arr,mid+1,j);
count(arr,i,mid,j);
}
}
void main()
{
int arr[] = {2,5,4,3,8,11};
int size = sizeof(arr)/4;
int i;
// mergeSort(arr,0,size-1);
for(i=0;i<size;i++)
cout<< arr[i] <<" ";
reverseNumber(arr,0,size-1);
cout<<endl;
for(i=0;i<size;i++)
cout<< arr[i] <<" ";
cout<< "逆序数:" <<cnt<<endl;
}