Binary Indexed Tree 树状数组

Binary Indexed Tree 树状数组

做Leetcode 做到MeetingRoomII的时候我知道不用线段树或者树状数组是不太好搞了。还是来学习一下吧。

树状数组算是线段树的一种特殊情况(子集),所以树状数组能解决的问题线段树一定能做,但线段树能做的树状数组不一定能做。

引入的问题

问题1

对一个数组进行如下操作
update(i1,i2,operation)
求 value(i)=?

例如:int[] ar=new int[N]
update(ar,2,5,+,4) //(2 to 5).map(ar(_)+=4)
return ar(k)

问题2

对一个数组进行如下操作 update(index,operation,value)
求 sum(i1,i2)=?


例如:int[] ar=new int[N]
update(ar,3,+,5);	 //ar[3]+=5
update(ar,11,-,3);  //ar[11]-=3
。。。。。。
求:sum(ar,0,12)? //(0 to 12).map(ar(_)).sum

应用线段树的思路,对问题1,可以在中间节点缓存操作O(lgn),求解时再计算出结果。

对问题2,可以直接缓存sum,每次操作时都对sum附带进行操作O(lgn),而求结果时,直接使用缓存的sum结果O(lgn)。

树状数组的解法

按照刚才的解题分析,应当使用线段树进行操作,但线段树操作效率比较低,是否一种折中的办法呢?

例如:把线段树的每个节点映射到一个额外的数组上?

那么问题很明确,如何将一颗有N个叶子节点的树映射到一个长度为N的数组上?

最直观的思路显然是这样:

图1

图看上去很容易理解,我们希望将中间结果(12)(58)等存在另外一个数组B[]里,剩下的问题只有一个,怎么把这些节点向数组的index映射?而且这个映射显然是算法可描述的,这样在计算时才能容易找到各个节点。

图2

究竟怎样想出来的这种映射方式已经不得而知,但的确是个很神妙的构想,这也是Binary Index Tree的精髓了。

基本设计

图2中黄色的块被废弃了,如果用a->b表示a存入b则:

(1~2)->2
(1~4)->4
(5~6)->6
(1~8)->8

1->1
3->3
5->5
7->7

是不是有点规律了?

  1. 奇数点全部存原数组值
  2. 偶书点K存入的位数与K&(-K)后面的0相关,由M个0就存了1<<M个数字

求解问题2

如果希望计算(st,ed)的sum时,如何计算呢?直接计算st到ed之间的数据相当难算,但是

sum(st,ed)=sum(1,ed)-sum(1,st-1)

这时候再看一下图2是不是明白了?
从新定义一个函数sumFromStart(k)表示从1加到K的和。

sum(st,ed)=sumFromStart(ed)-sumFromStart(st-1)	

最后看看这个sumFromStart写法吧其实很容易想到:

  • 2 是10计算1~2的和
  • 4 是100计算了1~4的和
  • 8 是1000计算了1~8的和

如果我们想算1~7

7=111=100+10+1

而且17=(14)+(5~6)+7

所以sumFromStart(7)=B(4)+B(6)+B(7)

再写清楚点,如果是二进制:

sumFromStart(111)=B(100)+B(110)+B(111)

想明白了? 还没有?那就看图吧

图3

所以,sumFromStart(k)定义如下

int sumFromStart(int k,int[] b){
	int sum=0;
	while(k!=0){
		sum+=b[k];
		k=k-(k&(-k));
	}
}

最后还有一个更新操作,因为是单个更新,所以注意要把上面的点也更新了,以对A[5],操作为例,需要更新B(5),B(6),B(8)。写出来这三个

B(101)
B(110)
B(1000)

看不出啥太明显的规律啊?

101 +1 =110
110 +10=1000

明白了么? k+k&(-k)啊,所以update写出来

void update(int index,int v,Operation=add,int[] b){
	while(index<b.length){
		b[index]+=v;
		index=index+index&(~index);
	}	
}
//问题2返回
void sum(int st,int ed,int[]b){
	return sumFromStart(ed,b)-sumFromStart(st-1,b);
}

求解问题1

问题1是段累加,单点求值,所以可以把段累加过程加入B数组(复杂度lgn),求解时再算单点(复杂度lgn)

int[] b=new int[N];
void update(int st,int ed,int added,){
	updateFromStart(st-1,-added,b);
	updateFromStart(ed,added,b);
}
void updateFromStart(int index,int added,int[] b){
	while(index<b.length){
		b[index]+=added;
		index-=index&(-index);
	}
}

int getV(int index){
	int sum=0;
	while(index<b.length){
		sum+=b[index];
		index+=index&(-index);
	}
	return sum;
}


求解MeetingRoomII

最后给出meeting RoomII代码,很遗憾,这个题最后求最大重叠,所以遍历了每个点找最大,时间复杂度O(nlgn)

public int minMeetingRooms(Interval[] ins) {
        int st=Integer.MAX_VALUE;
        int ed=Integer.MIN_VALUE;
        for(Interval in: ins){
            st=Math.min(st,in.start);
            ed=Math.max(ed,in.end);
        }
        int[] bis=new int[ed-st+5];
        int delta=st-1;
        for(Interval in:ins){
            add(in.start-delta,-1,bis);
            add(in.end-delta,1,bis);
        }
        int max=0;
        for(int i=1;i<bis.length;i++){
            max=Math.max(max,getFrom(bis,i));
        }
        return max;
    }

    private int getFrom(int[] bis, int i) {
        int res=0;
        while(i<bis.length){
            res+=bis[i];
            i+=i&(-i);
        }
        return res;
    }

    private void add(int index,int added,int[] b){
        while(index>0){
            b[index]+=added;
            index-=index&(-index);
        }
    }
posted @ 2015-08-29 15:11  RobinMeng  阅读(234)  评论(1编辑  收藏  举报