从求逆序对和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)

下面是出处: 资料来源

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值