Java数据结构之线段树中的懒操作详解

Java数据结构之线段树中的懒操作详解

什么是线段树

线段树是一种常用的数据结构,用于快速解决区间查询类问题。 线段树可以支持区间修改,单点查询,区间查询等操作。

线段树是采用二叉树的结构形成的,一个节点表示一个区间[left, right]。每个节点包含三个值:节点对应的区间范围[left, right]、节点代表的值val、以及节点所拥有的标记,通常标记用来记录区间修改的信息,也称为懒标记。

线段树中的懒操作

线段树自上而下递归下去,如果遇到一个节点的区间完全包含查询区间,则直接返回节点上的值,否则就分别递归该节点的左儿子和右儿子进行查询。 我们每次递归到一个节点时,都需要将该节点的lazy标记下传到其左右子节点。 为了避免重复下传标记,当节点被标记之后,我们将懒标记暂存,到下次对区间取值或区间修改时再进行下传,这样可以使得标记下传的复杂度为O(logn)。

线段树中主要涉及到以下两种类型的操作:

区间修改

区间修改操作是在指定区间内增加或修改某个值。当需要修改某个区间的值时,我们可以标记该区间需要被修改,而对于该区间的子节点,我们将修改信息暂时存储到懒标记中,等到子节点被查询到时,才下推修改。

这个过程比较简单,如果我们需要将区间[l, r]内的值都增加c,我们只需要将该区间对应节点的val值增加c即可,同时要将这个修改操作保存到懒标记中,让其下传到子节点。具体代码如下:

void update(int rt, int l, int r, int L, int R, int val) {  // rt 表示当前节点的编号,表示区间[l, r]。l, r 表示节点表示的区间范围。L, R 表示需要修改的区间。
    if(l >= L and r <= R) {
        tr[rt] += val * (r - l + 1);
        lazy[rt] += val;
        return;
    }
    pushdown(rt, r - l + 1);
    int mid = (l + r) >> 1;
    if(L <= mid) update(rt << 1, l, mid, L, R, val);
    if(R > mid) update(rt << 1 | 1, mid + 1, r, L, R, val);
    pushup(rt);
}

区间查询

区间查询即为查找某个区间[l, r]内的值,该操作比较简单,如果当前查询的区间[l, r]与当前节点表示的区间有重合,则需要向下递归查询其左右子节点,具体代码如下:

int query(int rt, int l, int r, int L, int R) { // rt 表示当前节点的编号,表示区间[l, r]
    if(l >= L and r <= R) {
        return tr[rt];
    }
    pushdown(rt, r - l + 1);
    int mid = (l + r) >> 1;
    int res = 0;
    if(L <= mid) res += query(rt << 1, l, mid, L, R);
    if(R > mid) res += query(rt << 1 | 1, mid + 1, r, L, R);
    return res;
}

示例说明:

以下是一个建立线段树,并进行区间修改和区间查询的示例代码:

import java.util.Arrays;

public class SegmentTree {

    private int[] tr;
    private int[] lazy;

    public SegmentTree(int n) { 
        tr = new int[n << 2];
        lazy = new int[n << 2];
        Arrays.fill(lazy, 0); // 初始化时,所有节点的懒标记都为0
    }

    private void pushup(int rt) {
        tr[rt] = tr[rt << 1] + tr[rt << 1 | 1];  // 更新父节点的值
    }

    private void pushdown(int rt, int len) {
        if(lazy[rt] != 0) {
            lazy[rt << 1] += lazy[rt];
            lazy[rt << 1 | 1] += lazy[rt];
            tr[rt << 1] += lazy[rt] * (len - (len >> 1));
            tr[rt << 1 | 1] += lazy[rt] * (len >> 1);
            lazy[rt] = 0;
        }
    }

    public void update(int rt, int l, int r, int L, int R, int val) {
        if(l >= L && r <= R) {
            tr[rt] += val * (r - l + 1);
            lazy[rt] += val;
            return;
        }
        pushdown(rt, r - l + 1);
        int mid = (l + r) >> 1;
        if(L <= mid) update(rt << 1, l, mid, L, R, val);
        if(R > mid) update(rt << 1 | 1, mid + 1, r, L, R, val);
        pushup(rt);
    }

    public int query(int rt, int l, int r, int L, int R) {
        if(l >= L && r <= R) {
            return tr[rt];
        }
        pushdown(rt, r - l + 1);
        int mid = (l + r) >> 1;
        int res = 0;
        if(L <= mid) res += query(rt << 1, l, mid, L, R);
        if(R > mid) res += query(rt << 1 | 1, mid + 1, r, L, R);
        return res;
    }

    public static void main(String[] args) {

        int[] nums = {1, 3, 5, 7, 8, 9};
        SegmentTree st = new SegmentTree(nums.length);
        buildTree(1, 0, nums.length - 1, nums, st);

        // 对[0,2]区间进行加10操作
        st.update(1, 0, nums.length - 1, 0, 2, 10);
        System.out.println(st.query(1, 0, nums.length - 1, 0, 2)); // 输出:36

        // 对[3,5]区间进行加5操作
        st.update(1, 0, nums.length - 1, 3, 5, 5);
        System.out.println(st.query(1, 0, nums.length - 1, 3, 5)); // 输出:22
    }

    private static void buildTree(int rt, int l, int r, int[] nums, SegmentTree st) {
        if(l == r) {
            st.tr[rt] = nums[l];
        } else {
            int mid = (l + r) >> 1;
            buildTree(rt << 1, l, mid, nums, st);
            buildTree(rt << 1 | 1, mid + 1, r, nums, st);
            st.pushup(rt);
        }
    }
}

该示例代码建立了一个线段树,并对[0,2]区间进行加10操作后,进行区间查询,输出结果为36。然后再对区间[3,5]进行加5操作,进行区间查询,输出结果为22。

以上就是Java数据结构之线段树中的懒操作详解的完整攻略。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:Java数据结构之线段树中的懒操作详解 - Python技术站

(0)
上一篇 2023年6月27日
下一篇 2023年6月27日

相关文章

  • 浅析Android.mk

    当进行Android C/C++项目开发时,需要针对不同的架构编写代码,例如x86、ARM等。而Android.mk文件就是Makefile文件,在编译时告诉编译器如何构建应用程序的配置文件。在本文中,我们将浅析Android.mk文件,介绍其语法体系、常见语句和示例说明。 Android.mk文件语法体系 Android.mk文件包含了编译应用程序需要的所…

    other 2023年6月26日
    00
  • Android样式和主题之选择器的实例讲解

    Android样式和主题之选择器的实例讲解 在Android开发中,样式和主题是非常重要的概念,它们可以用来定义应用程序的外观和行为。其中,选择器是一种特殊的样式,它可以根据不同的状态来改变控件的外观。本文将详细讲解如何使用选择器来定义控件的样式。 选择器的基本语法 选择器是一个XML文件,它定义了一组状态和对应的样式。以下是选择器的基本语法: <se…

    other 2023年8月20日
    00
  • vue自定义元素身上的右键事件

    Vue自定义元素身上的右键事件:完整攻略 在Vue中,我们可以使用v-on指令来绑定事件。但是,对于自定义元素,我们需要使用v-on指令的修饰符来绑定右键事件。本攻略将介绍如何在Vue自定义元素身上定右键事件,并提供两个示例。 步骤一:使用v-on指令绑定右键事件 在Vue中,我们可以使用v指令来绑定事件。对于自定义元素,我们使用v-on指令修饰符来绑定右键…

    other 2023年5月9日
    00
  • paypal提现到派安盈无法绑定firstcenturybank账号怎么办

    如果您在PayPal上提现到派安盈账户时无法绑定First Century Bank账号,可以按照以下攻略进行操作: 确认账户信息 先,您需要确认您的派安盈账户信息是否正确。请检查您的账户名、账户号码、银行名称等信息是否正确。如果信息不正确,您需要联系派安盈客服进行修改。 联系First Century Bank客服 如果您的派安盈账户信息正确但仍然无法绑定…

    other 2023年5月9日
    00
  • mysql大文本类型

    MySQL大文本类型 在MySQL中,有一些数据类型可以用来存储不同大小和类型的数据。其中一个重要的数据类型是大文本类型,可以用来存储长字符串和二进制数据。 在下面的文章中,我们将讨论以下内容: MySQL大文本类型的定义和用途 MySQL大文本类型的种类 如何使用MySQL大文本类型 1. MySQL大文本类型的定义和用途 MySQL中的大文本类型可以存储…

    其他 2023年3月28日
    00
  • MySql服务器系统变量和状态变量介绍

    MySql服务器系统变量和状态变量介绍 MySQL是一种流行的关系型数据库管理系统,它提供了许多系统变量和状态变量来控制和监视服务器的行为。系统变量是可以在服务器启动时设置的全局参数,而状态变量是反映服务器当前状态的信息。 系统变量 系统变量用于配置MySQL服务器的行为。以下是一些常见的系统变量: max_connections:该变量控制服务器允许的最大…

    other 2023年7月29日
    00
  • vim设置colorscheme小技巧

    Vim设置colorscheme小技巧 在使用Vim进行操作时,为了提升编辑体验,我们需要设置一个合适的colorscheme。一个好的colorscheme可以帮助我们更好地区分不同的文本内容,从而提升代码阅读与写作的效率。接下来,本文将介绍一些关于Vim设置colorscheme的小技巧。 1. 安装colorscheme 首先,我们需要在Vim中安装合…

    其他 2023年3月28日
    00
  • plt.scatter()参数说明

    plt.scatter()参数说明 在Python的数据可视化库matplotlib中,plt.scatter()是用于绘制散点图的函数。它接受多个参数,本文将对这些参数进行详细的说明。 参数列表 plt.scatter()的基本语法如下: plt.scatter(x, y, s=None, c=None, marker=None, cmap=None, n…

    其他 2023年3月28日
    00
合作推广
合作推广
分享本页
返回顶部