线段树

线段树相关知识点梳理

  • 1.线段树实现:包括add,update,query方法的实现
  • 2.业务代码简单验证
  • =========思路============
  • 我现在面临的问题是什么?
  • 1.有一个需求,需要我将数组中5~99的所有值都统统加5,那么如果按照正常的遍历去实现,时间复杂度是O(N)的。
  • 2.假设这个需求调用十分频繁,可想而知会造成比较大的性能消耗。
  • 所以线段树就是处理上述问题的,尤其明显的优势:如区间类的更新,累加,查询都适用。
  • 先说结论;
  • 线段树可以将时间复杂度降到O(logN)级别,并且需要的额外空间复杂度不会超过4*N(画图知道,堆的结构类似一个小山,从上到下铺满了也就是1,2,4,8,16这样铺下来,我们会发现及时arr长度等于N 比如=8的时候 前面的节点个数=7 我们近似看作8 这就是2N,又下面那排最大不超过2N,所以我们只需要准备一个4N的空间,绝对够用)
  • 我们需要构造一个类似堆的数据;假设我们原数组内一共有4个数,下标从1-4(这里不从0开始是方便思考)
  • 我们需要进行如下转换
  • [3,7,5,9]=>[24,10,14,3,7,5,9] 再看一组数据
  • [3,7,5,9,1] => [25,10,15,3,7,5,10,null,null,null,null,null,null,9,1]
  • 我们姑且把原数组叫arr,转换后的数组叫sum(sum也是从1开始下标)
  • 规律就是,先把后面的数组用堆的形势展开,然后sum[1]的位置记录的是arr[1-4]的累加和sum[2]是arr[1-2]的累加和(左孩子)
  • null那些说明最后一行有些空间没有用到,但是还是分配了。结构的最后还需要写入信息。
  • (当然这种遇到无法被2整除的,是让左孩子分配更多还是右孩子分配更多无所谓,看个人喜好)。比如你把1-5 分成1-3 4-5也可以。
  • 基本结构有了,我们需要在分情况进行处理。
  • 如果我有一个格子 存放的是2-10的累加和,那么如果新任务也是需要在2-10上加,那么就直接在这层加就好了,不需要在把2-10往下拆,继续加。
  • 我们还可以引入一个数组,姑且叫做lazy。它的作用是假设之前任务是在3-29之间都加5 那么我们可以在3-29对应的数组下标同位置的lazy数组下记录一个5,
  • 表示这里有一个5要累加,但是可以先不加,因为我们可以看后续还会收到什么命令。如果后续命令是累加2 那么我们直接在lazy数组那里改成7,
  • 知道出现一个任务不是把3-29全包,比如出现个新任务要求在3-9之间累加6,
  • 那么我们就必须先将之前lazy下来的任务下发,累加完毕,然后在根据3-9往下拆,拆到合适的位置在lazy一个6.
  • 当然有可能3-9其实是由3-6,7-9拼成的。没关系,我们直接将连各个分别lazy之后最后结算的时候累加一下即可
  • */
public class SegmentTree_31 {
    //构造一下SegmentTree类
    public static class SegmentTree {
        /**
         * max 原数组长度
         * arr 维护一个从1开始的原数组
         * sum 累加和数组 维护区间累加和
         * lazy 懒数组,更新慵懒标记
         * change 更新数组 维护更新情况
         * update 布尔值 是否更新
         * */
        private int maxN;
        private int[] arr;
        private int[] sum;
        private int[] lazy;
        private int[] change;
        private boolean[] update;
        public SegmentTree(int[] origin){
            maxN = origin.length+1;
            arr = new int[maxN];
            for (int i=1;i>1;
            build(l,mid,rt<<1);
            build(mid+1,r,rt<<1|1);
            pushUp(rt);
        }
        //L ,R ,C 参数代表整个累加任务是从L到R区间,每个数加个C
        //l r rt参数代表从最初任务一路往下拆下来,当前的sum节点,rt代表他的下标,l,r分别记录了他所囊括的arr是l-r的区间
        private void add(int L, int R, int C,int l, int r, int rt){
            if(L<=l&&r<=R){
                //说明当前所在的sum节点是完全被任务所包围的,比如我任务是要在1-9区间加个5,现在这个位置是2-7
                sum[rt]+=C*(r-l+1);
                lazy[rt]+=C;
                return;
            }
            int mid = (l+r)>>1;
            //先pushDown,将左右孩子区间的sum lazy数据先更新正确
            pushDown(mid-l+1,r-mid,rt);
            if(L<=mid){
                add(L,R,C,l,mid,rt<<1);
            }
            if (R>mid){
                add(L,R,C,mid+1,r,rt<<1|1);
            }
            pushUp(rt);
        }
        private void update(int L, int R, int C,int l, int r, int rt){
            if(L<=l&&r<=R){
                //说明当前所在的sum节点是完全被任务所包围的,比如我任务是要在1-9区间统一更新成5,现在这个位置是2-7
                update[rt] = true;
                change[rt] = C;
                sum[rt]=C*(r-l+1);
                lazy[rt]=0;
                return;
            }
            int mid = (l+r)>>1;
            //先pushDown,将左右孩子区间的sum lazy数据先更新正确
            pushDown(mid-l+1,r-mid,rt);
            if(L<=mid){
                update(L,R,C,l,mid,rt<<1);
            }
            if (R>mid){
                update(L,R,C,mid+1,r,rt<<1|1);
            }
            pushUp(rt);
        }
        // 因为查询不需要修改数据 所以省去了参数C
        private long query(int L, int R,int l, int r, int rt){
            if(L<=l&&r<=R){
                return sum[rt];
            }
            int mid = (l+r)>>1;
            //先pushDown,将左右孩子区间的sum lazy数据先更新正确
            pushDown(mid-l+1,r-mid,rt);
            long ans = 0;
            if(L<=mid){
                ans+=query(L,R,l,mid,rt<<1);
            }
            if (R>mid){
                ans+=query(L,R,mid+1,r,rt<<1|1);
            }
            return ans;
        }
    }
    // 写一个业务代码用做对数器
    public static class Right{
        private int[] arr;
        public Right(int[] origin){
            arr = new int[origin.length+1];
            for (int i=1;i

你可能感兴趣的:(线段树)