从求逆序对和mergeSort谈起(3)

Inversion Count for an array indicates – how far (or close) the array is from being sorted. If array is already sorted then inversion count is 0. If array is sorted in reverse order that inversion count is the maximum.

 Two elements a[i] and a[j] form an inversion if 
 a[i] > a[j] and i < j. For simplicity, we may 
 assume that all elements are unique.

 Example:
 Input:  arr[] = {8, 4, 2, 1}
 Output: 6
 Given array has six inversions (8,4), (4,2),
 (8,2), (8,1), (4,1), (2,1).     

以上分别用分支合并算法和BST来解决求解逆序对的问题,这里是用Binary Index Tree(BIT,数状数组)来解决这一问题。
BIT:
BIT对于一个长度为n的数组nums(n)可以进行以下两种操作:
(1)计算nums[0,..,i]的和,时间复杂度 O(logn)
(2)更新数组内的元素,时间复杂度为 O(logn)
BIT的实现:用一个数组BIT实现树结构的功能
(1)求和时,下标为x的元素的后继x=x+(x&-x);
(2)更新,下标为x的元素的后继x=x-(x&-x);
基本的方法:
创建一个数组BIT,其大小为nums(n)的最大值加一,对于nums从后往前遍历,对于位于位置i的元素,我们想要知道在i之后有多少个数小于nums[i]。统计小于nums[i]元素个数的操作可以通过BIT求和获得。一旦我们将一个数加入到BIT中,更新当前BIT的计数由0变为1,其后的后继节点也会更新。

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
/*
nums=[8,4,2,1];
这里假设存在原始数组:arr=[0,0,0,0,0,0,0,0,0];一旦存在下标为x的数,arr[x]=1;
BITree=[0,0,0,0,0,0,0,0,0];BITree[idx]是arr[0,...,idx]的和
stp1: nums[3]=1; getSum(nums,0)=0;
         arr=[0,1,0,0,0,0,0,0,0];
      BITree=[0,1,1,0,1,0,0,0,1]
stp2: nums[2]=2;getSum(nums,1)=1;
         arr=[0,1,1,0,0,0,0,0,0];
      BITree=[0,1,2,0,2,0,0,0,2]
stp3: nums[1]=4;getSum(nums,3)=BIT[3]+BIT[2]=2;
         arr=[0,1,1,0,1,0,0,0,0];
      BITree=[0,1,2,0,3,0,0,0,3]
stp4: nums[0]=8;getSum(nums,7)=BIT[7}+BIT[6]+BIT[4]=3;
         arr=[0,1,1,0,1,0,0,0,1];
      BITree=[0,1,2,0,3,0,0,0,4]

 invcnt=0+1+2+3=6;
 这里的假设所有的元素都是正数
*/
int getSum(vector<int>& BIT,int idx)
{
    int sum=0;//sum=BIT[0,...,idx]的和
    while(idx>0)
    {
        sum+=BIT[idx];
        idx-=idx&(-idx);
    }
    return sum;
}
void updateBIT(vector<int>& BIT,int idx,int val)
{
   while(idx<BIT.size()) 
   {
       BIT[idx]+=val;
       idx+=idx&(-idx);
   }
}
int getSmaller(vector<int>& nums)
{
    int n=nums.size();
    int invcnt=0,maxele=0;
    for(int i:nums) maxele=max(maxele,i);
    vector<int> BIT(maxele+1,0);
    for(int i=n-1;i>=0;i--)//因为是倒着遍历的,所以BIT[i]之前的都是逆序对
    {
        invcnt+=getSum(BIT,nums[i]-1);
        //计算比nums[i]小的数的总数,即arr[0,...,nums[i]-1]的总数
        updateBIT(BIT,nums[i],1);//把现在的元素加入BIT
    }
    return invcnt;
}
int main()
{
    vector<int> nums = {8, 4, 2, 1};
    cout << "Number of inversions are : " << getSmaller(nums)<<endl;;
    return 0;
}

复杂度分析:这里的getSum、updateBIT的时间复杂度 O(log(maxElement)) ,算法迭代了n次,故复杂度为 O(nlog(maxElement) .

但是要考虑到有可能出现负数,而且如果nums的最大数很大,会导致用太多的空间。因此,我们可以对数组进行转换,按照由小到大的次序进行编号,使得它们之间有关逆序的相对关系不会改变,而且只需要 O(n) 额外的空间。

关于转换函数:convert(nums)
Example :-
arr[] = {7, -90, 100, 1}

It gets converted to,
arr[] = {3, 1, 4 ,2 }
as -90 < 1 < 7 < 100.
这里{3,1,4,2}能完整地保留{7,-90,100,1}中各元素之间的逆序关系,且都是正数,就可以用上面的BIT解决了。

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
void convert(vector<int>& nums)
{
    int i,n=nums.size();
    vector<int> tmp(n,0);
    copy(nums.begin(),nums.end(),tmp.begin());
    sort(tmp.begin(),tmp.end());
    for(int i=0;i<n;i++)
    nums[i]=lower_bound(tmp.begin(),tmp.end(),nums[i])-tmp.begin()+1;
    //说明,lower_bound返回tmp数组中第一个>=nums[i]的指针
}
int getSum(vector<int>& BIT,int idx)
{
    int sum=0;//sum=BIT[0,...,idx]的和
    while(idx>0)
    {
        sum+=BIT[idx];
        idx-=idx&(-idx);
    }
    return sum;
}
void updateBIT(vector<int>& BIT,int idx,int val)
{
   while(idx<BIT.size()) 
   {
       BIT[idx]+=val;
       idx+=idx&(-idx);
   }
}
int getSmaller(vector<int>& nums)
{
    int i,n=nums.size();
    int invcnt=0;
    convert(nums);
    vector<int> BIT(n+1,0);
    for(int i=n-1;i>=0;i--)//因为是倒着遍历的,所以BIT[i]之前的都是逆序对
    {
        invcnt+=getSum(BIT,nums[i]-1);
        //计算比nums[i]小的数的总数,即arr[0,...,nums[i]-1]的总数
        updateBIT(BIT,nums[i],1);//把现在的元素加入BIT
    }
    return invcnt;
}
int main()
{
    vector<int> nums = {7,-90,100,1};
    convert(nums);
    for(int i=0;i<nums.size();i++) cout<<nums[i]<<" ";cout<<endl;
    cout << "Number of inversions are : " << getSmaller(nums)<<endl;;
    return 0;
}

时间复杂度分析:这里convert的时间复杂度是 O(nlogn) (排序的复杂度)。BIT中的update和getsum都是 O(logn) ,共迭代了n次,故此时的复杂度为 O(nlogn)

下面是出处: 资料来源

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java归并排序求逆序对是一种常见的排序法,它通过将组分成两个子组,分别对子组进行排序,然后将两个已排序的子组合并成一个有序的组。在归并的过程中,可以统计逆序对量。 下面是Java归并排序求逆序对的实现步骤: 1. 首先,定义一个全局变量count,用于记录逆序对量。 2. 创建一个辅助组temp,用于存储归并过程中的临时结果。 3. 编写一个merge方法,用于将两个已排序的子组合并成一个有序的组,并在合并过程中统计逆序对量。 - 在merge方法中,首先定义三个指针:left、right和index,分别指向左子组、右子组和辅助组temp的起始位置。 - 然后,比较left指针和right指针所指向的元素大小: - 如果左子组的元素小于等于右子组的元素,则将左子组的元素复制到temp组,并将left指针向右移动一位。 - 如果左子组的元素大于右子组的元素,则将右子组的元素复制到temp组,并将right指针向右移动一位。同时,count需要加上left指针到左子组末尾的长度,因为左子组剩余的元素都大于当前的右子组元素。 - 继续上述比较和复制的过程,直到左子组或右子组的元素全部复制到temp组。 - 最后,将temp组中的元素复制回原组的相应位置。 4. 编写一个mergeSort方法,用于递归地对组进行归并排序。 - 在mergeSort方法中,首先判断组的长度是否大于1,如果不大于1,则直接返回。 - 如果组的长度大于1,则将组分成两个子组,并分别调用mergeSort方法对子组进行排序。 - 最后,调用merge方法将两个已排序的子组合并成一个有序的组,并统计逆序对量。 5. 在主函中调用mergeSort方法对组进行排序,并输出逆序对量。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值