zoukankan      html  css  js  c++  java
  • 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);
            }
        }
    
  • 相关阅读:
    Aria2任意文件写入漏洞
    webpack添加node_path不是('webpack' 不是内部或外部命令,也不是可运行的程序或批处理文件?)
    闭包的7种形式
    首页 多级展示
    velocity模板入门
    AngularJs 时间控件
    mybatis按时间条件搜索
    数据结构之线性表(双向循环链表)
    数据结构之线性表(链表)
    数据结构之线性表(顺序表)
  • 原文地址:https://www.cnblogs.com/mengyou0304/p/4769183.html
Copyright © 2011-2022 走看看