ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

算法归纳4-前缀和/差分/树状数组/线段树

2022-06-06 21:36:37  阅读:183  来源: 互联网

标签:前缀 idx 树状 int 线段 tree 差分 数组 区间


1,对比

  • https://blog.csdn.net/honghuidan/article/details/77527808
  • 两者相同点:单点/区间修改,区间查询
    • 区间查询:前缀和
    • 区间修改,单点查询:差分
    • 单点修改,区间查询:树状数组,线段树
    • 区间修改,区间查询:线段树+懒标记
  • 不同点:
    • 树状数组只能维护前缀操作和(前缀和,前缀积,前缀最大最小),而线段树可以维护区间操作和
    • 树状数组:
      • 某些操作是存在逆元的,这样就给人一种树状数组可以维护区间信息的错觉:维护区间和,模质数意义下的区间乘积,区间xor和。能这样做的本质是取右端点的前缀和,然后对左端点左边的前缀和的逆元做一次操作,所以树状数组的区间询问其实是在两次前缀和询问。
      • 所以我们能看到树状数组能维护一些操作的区间信息但维护不了另一些的:最大/最小值,模非质数意义下的乘法,原因在于这些操作不存在逆元,所以就没法用两个前缀和做区间查询。
    • 线段树
      • 线段树就不一样了,线段树直接维护的就是区间信息,所以一切满足结合律的操作都能维护区间和,并且lazy标记的存在还能使线段树能够支持区间修改,这点是树状数组做不到的。
      • 可以说树状数组能做的事情其实是线段树的一个子集,大多数情况下使用树状数组真的只是因为它好写并且常数小而已。
      • 不过随着zkw线段树的普及,树状数组仅有的两点优势也不复存在了……估计要成为时泪了吧。

2,前缀和

3,差分

4,树状数组

5,线段树

  • https://leetcode.cn/problems/my-calendar-iii/solution/xian-duan-shu-by-xiaohu9527-rfzj/
    • 懒标记:将节点的值加一,表示节点所表示的区间内的所有元素都加了一,而不是去具体修改每一个元素值。(例如:节点表示区间最大值的时候就很好用)
    • 懒标记使得线段树支持区间修改。
  • 线段树将更新和查询的时间复杂度都变为O(logn)
  • 线段树的结构:
    • 基于原数组,生成一个新的数组表示线段树的结构
    • 树的根节点在新数组index=1
    • 新数组中所有节点(索引为index)的左右子节点分别为2*index,2*index+1
    • 原数组中的每一个元素在线段树数组中都是叶子节点,线段树数组中的其他节点(非叶子节点)表示都是原数组的一段区间和,根节点存储整个原数组的和(这里有两个数组,一个原数组,一个基于原数组生成的线段树数组)
  • 查询操作
    • 根据查询区间,从新数组的根结点(index=1)出发,递归地向两边扩展(2*index, 2*index+1),在子树中找到合适的区间和返回,时间复杂度O(logn)
  • 更新操作
    • 根据修改的节点,从新数组对应的叶子节点出发,一直更新其父节点直到更新根节点,时间复杂度O(logn)
  • 代码实例:
//没有懒标记,单点修改,区间查询
import java.util.Arrays;

public class test {

    /**
     * 基于原数组生成线段树数组
     * @param arr   原数组
     * @param tree  生成的线段树数组
     * @param start 当前阶段处理的原数组起始索引(区间和起始索引)
     * @param end   当前阶段处理的原数组结束索引(区间和结束索引)
     * @param node_idx  与[start, end]相对应的区间和在线段树数组中的索引
     */
    public static void build_tree(int[] arr, int[] tree, int start, int end, int node_idx){
        if(start==end){
            //此时到达叶子节点
            tree[node_idx] = arr[start];
            return;
        }
        int leftNode_idx = 2*node_idx;
        int rightNode_idx = 2*node_idx+1;
        int middle = (start+end)/2;
        build_tree(arr, tree, start, middle, leftNode_idx);
        build_tree(arr, tree, middle+1, end, rightNode_idx);
        tree[node_idx] = tree[leftNode_idx]+tree[rightNode_idx];    //后序
    }

    /**
     * 由线段树数组查询得到原数组区间和
     * @param arr   原数组
     * @param tree  线段树数组
     * @param start 当前阶段处理的原数组起始索引
     * @param end   当前阶段处理的原数组结束索引
     * @param l 要查询的区间和的原数组起始索引
     * @param r 要查询的区间和的原数组结束索引
     * @param node_idx  与[start, end]相对应的区间和在线段树数组中的索引
     * @return
     */
    public static int query(int[] arr, int[] tree, int start, int end, int l, int r, int node_idx){
        if(l>end||r<start){
            //要查询的区间超出了当前区间的范围
            return 0;
        }else if(l<=start && end<=r){
            //要查询的区间覆盖了当前的区间范围
            return tree[node_idx];
        }else{
            int leftNode_idx = node_idx*2;
            int rightNode_idx = node_idx*2+1;
            int middle = (start+end)/2;
            int left_sum = query(arr, tree, start, middle, l, r, leftNode_idx);
            int right_sum = query(arr, tree, middle+1, end, l, r, rightNode_idx);
            return left_sum+right_sum;
        }
    }

    /**
     * 线段树单点修改-和建树很像
     * @param arr   原数组
     * @param tree  线段树数组
     * @param start 当前阶段处理的原数组起始索引
     * @param end   当前阶段处理的原数组结束索引
     * @param node_idx  与[start, end]相对应的区间和在线段树数组中的索引
     * @param update_idx    要修改的元素在原数组中的索引
     * @param val   原数组元素修改后的值
     */
    public static void update(int[] arr, int[] tree, int start, int end, int node_idx, int update_idx, int val){
        if(start==end){
            //修改点即可
            arr[start] = tree[node_idx] = val;
        }else{
            int leftNode_idx = node_idx*2;
            int rightNode_idx = node_idx*2+1;
            int middle = (start+end)/2;
            if(update_idx>middle){
                //要更新的值在右子树
                update(arr, tree, middle+1, end, rightNode_idx, update_idx, val);
            }else{
                //要更新的值在左子树
                update(arr, tree, start, middle, leftNode_idx, update_idx, val);
            }
            tree[node_idx] = tree[leftNode_idx]+tree[rightNode_idx];
        }
    }


    public static void main(String[] args) {
        int[] a = {10, 11, 12, 13, 14, 15};

        //生成线段树
        int[] tree = new int[a.length*4];
        build_tree(a, tree, 0, a.length-1, 1);
        System.out.println("生成的线段树为"+Arrays.toString(tree));

        //区间查询
        int l=0;
        int r=a.length-1;
        System.out.println(query(a, tree, 0, a.length-1, l, r, 1));

        //区间修改
        update(a, tree, 0, a.length-1, 1, a.length-1, 20);
        System.out.println("修改后的线段树为"+Arrays.toString(tree));
        System.out.println(query(a, tree, 0, a.length-1, l, r, 1));
    } 
}

标签:前缀,idx,树状,int,线段,tree,差分,数组,区间
来源: https://www.cnblogs.com/tensorzhang/p/16349776.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有