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日

相关文章

  • win7怎么打开后缀名为.pst的文件 win7系统文件后缀名.pst打开办法

    Win7系统文件后缀名.pst打开办法 如果你在Win7系统中遇到了后缀名为.pst的文件,下面是一些打开这种文件的方法: 方法一:使用Microsoft Outlook打开.pst文件 首先,确保你已经安装了Microsoft Outlook软件。如果没有安装,你可以从Microsoft官方网站下载并安装它。 打开Microsoft Outlook软件。 …

    other 2023年8月5日
    00
  • pythontkinter教程-04:输入框

    Python Tkinter教程-04: 输入框 在Python Tkinter中,输入框是一种常用的用户界面元素,用于接收用户输入的文本。以下是Python Tkinter中输入框的详细攻略。 步骤1:创建输入框 Python Tkinter中,我们可以使用Entry类来创建一个输入框。以下是一个简单的示例: from tkinter import * r…

    other 2023年5月9日
    00
  • 一文总结Java获取文件后缀名的所有方法

    一文总结Java获取文件后缀名的所有方法 在Java中,获取文件后缀名的方法有多种,本篇文章将会总结介绍这些方法。 方法一:使用String类型的substring()方法 可以通过String类型提供的substring()方法获取文件名中的后缀名。 示例代码如下: public static String getFileSuffixUsingSubstr…

    other 2023年6月26日
    00
  • 开机系统准备工具如何关闭

    当然,我很乐意为您提供有关“开机系统准备工具如何关闭”的完整攻略。以下是详细的步骤和两个示例: 1 关闭开机系统准备工具 开机系统准备工具是操作系统的一个功能,它可以帮助您在计算机启动时进行故障排除和修复。如果您不需要使用此功能,可以通过步骤关闭它: 1.1 使用系统配置工具 可以使用系统配置工具来关闭开机系统准备工具。以下是步骤: 打开“运行”对话框,方法…

    other 2023年5月6日
    00
  • C/C++内存管理详情

    C/C++内存管理详情攻略 1. 内存管理概述 在C/C++中,内存管理是程序员需要关注的重要方面之一。正确地管理内存可以避免内存泄漏和悬挂指针等问题,提高程序的性能和稳定性。下面将详细介绍C/C++中的内存管理技术。 2. 内存分配和释放 2.1 malloc和free 在C语言中,可以使用malloc函数动态分配内存,使用free函数释放内存。示例代码如…

    other 2023年7月31日
    00
  • Spring实例化bean的方式代码详解

    下面就为大家详细讲解一下“Spring实例化bean的方式代码详解”的完整攻略。 1. 简介 在Spring框架中,bean是一个可重用组件,它由Spring IoC容器管理和实例化。Spring框架提供了多种实例化bean的方式,本文将详细讲解。 2. 实例化bean的方式 2.1 构造函数实例化 使用构造函数实例化bean是Spring IoC容器最常用…

    other 2023年6月27日
    00
  • mybatis-plus 新增/修改如何实现自动填充指定字段

    在mybatis-plus中实现自动填充指定字段的操作分为以下两个步骤: 实现填充器接口:自定义填充器实现类,实现MetaObjectHandler接口。 添加填充配置:在 mybatis-plus 的全局配置中,添加自定义的填充器及其配置。 下面我们来具体讲解如何实现自动填充指定字段: 1. 自定义填充器实现类 自定义的填充器需要实现MetaObjectH…

    other 2023年6月25日
    00
  • 如何检测网络中的重复IP地址 防止ip地址冲突

    如何检测网络中的重复IP地址 防止IP地址冲突 在网络中,重复的IP地址可能会导致IP地址冲突,从而影响网络通信和设备连接。为了避免这种情况的发生,我们可以采取以下步骤来检测网络中的重复IP地址并防止IP地址冲突。 步骤一:扫描网络中的IP地址 首先,我们需要扫描网络中的所有IP地址,以便确定是否存在重复的IP地址。可以使用网络扫描工具来完成这个任务,例如N…

    other 2023年7月31日
    00
合作推广
合作推广
分享本页
返回顶部