实训三—分治算法专题—第9关:求排列的逆序数


任务描述

本关任务:输入一个数组,求出这个数组中的逆序对的总数。保证输入的数组中没有的相同的数字。

相关知识

一个排列含有逆序的个数称为这个排列的逆序数。在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。

例如,排列2,6,3,4,5,1中含有8个逆序(2,1),(6,3),(6,4),(6,5),(6,1),(3,1),(4,1),(5,1),因此该排列的逆序数就是8。 显然,由1,2,…,n 构成的所有n!个排列中,排列1,2,…,n的逆序数是0;排列n,(n-1),…,2,1的逆序数最大,为n(n-1)/2,逆序数越大的排列与原始排列的差异度就越大。

现给定1,2,…,n的一个排列,求它的逆序数。

求排列的逆序数有两种解法枚举和分治,时间复杂度分别为O(n2)O(n∗logn)。在此我们利用分治思想来解。 思路: 类似于归并排序:

  1. 将数组分成两半,分别求出左半边的逆序数和右半边的逆序数
  2. 再算有多少逆序是由左半边取一个数和右半边取一个数构成

实际上就是归并排序的改造,即对此排列进行归并排序,边排序边计算它的逆序数。

求逆序数的关键: 设左半边和右半边都是从小到大有序的(此时进行归并),从左到右依次扫描,比较两个各取一个数的大小,如果左边第一个数比右边第一个数大,那么左边排列的其他数字也可以和右边第一个数构成逆序数,以此类推。

编程要求

根据提示,在右侧编辑器补充代码,计算排列的逆序数并输出结果。

测试说明

平台会对你编写的代码进行测试:

测试输入: 9 9 6 8 2 1 3 4 5 7 预期输出: 9,6,8,2,1,3,4,5,7, 逆序数的个数为:20

#include<cstdio>
#include <iostream>
using namespace std;
long sum = 0;//逆序数的个数 
void output(int *a,int n) ;
void swap(int &a, int &b)  ;
void Merge(int a[],int s,int m, int e)  ;
void MergeSort(int a[],int s,int e)  ;            //归并排序
void MergeAndCountNum(int * arr,int s,int mid,int e); //归并有序序列并计算逆序数的个数 
int main()
{
	int i,n,x;
    int *a=0,*b=0;  
    scanf("%d",&n);  
    a=new int[n]; 
    b=new int[n];    
    for(i=0;i<n;i++)  
   {  
      scanf("%d",&x);
      a[i]=b[i]=x;  
   }  
    output(a,n);  
    MergeSort(a,0,n-1);    
	printf("逆序数的个数为:"); 
	cout << sum ;
	delete []a; 
    delete []b;      
    system("pause");
	return 0;
} 


void output(int *a,int n)  
{  
    int i;  
    for(i=0;i<n;i++)  
    {  
        printf("%d,",a[i]);  
    }  
    printf("\n");  
}
void swap(int &a, int &b)  
{  
    int t;  
    t=a;a=b;b=t;  
}
void Merge(int a[],int s,int m, int e)  
{ //将数组a的局部a[s,m]和a[m+1,e]合并到tmp,并保证tmp有序,然后再拷贝回a[s,e] //归并操作时间复杂度:O(e-m+1),即O(n)   
    int pb = 0;   
    int p1 = s,p2 = m+1;   
   int *tmp = new int[e-s+1];  
    /********** Begin **********/	
    while( p1 <= m && p2 <= e)  
    { if( a[p1] < a[p2])   
        tmp[pb++] = a[p1++];   
      else   
        tmp[pb++] = a[p2++];  
    }  
    while( p1 <= m)   
        tmp[pb++] = a[p1++];   
    while( p2 <= e)   
        tmp[pb++] = a[p2++];  
    for(int i = 0;i < e-s+1; ++i)  
        a[s+i] = tmp[i];  
	/********** End **********/
   delete []tmp;  
}   
void MergeSort(int a[],int s,int e)  
{  
    int *tmp = new int[e-s+1];  
    if( s < e)   
    {    int m = s + (e-s)/2;           
        /********** Begin **********/	
        MergeSort(a,s,m);   
        MergeSort(a,m+1,e);   
        MergeAndCountNum(a,s,m,e);   
        /********** End **********/
    }  
    delete []tmp;  
 }

 void MergeAndCountNum(int * arr,int s,int mid,int e)
{
    /********** Begin **********/
        int *tmp = new int[e-s+1];
        int pb = 0;
        int p1 = s, p2 = mid + 1;
        while (p1 <= mid && p2 <= e) {
            if (arr[p1] > arr[p2]) {
                tmp[pb++] = arr[p2++];
                sum += mid - p1 + 1; // 计算逆序数的个数
            } else {
                tmp[pb++] = arr[p1++];
            }
        }
        while (p1 <= mid) {
            tmp[pb++] = arr[p1++];
        }
        while (p2 <= e) {
            tmp[pb++] = arr[p2++];
        }
        for (int i = 0; i < e - s + 1; ++i) {
            arr[s + i] = tmp[i];
        }
        delete[] tmp;
    /********** End **********/
}

  • 5
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值