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

目录
  • 一、问题提出
  • 二、区间更新
  • 三、区间查询
  • 四、实战
    • 1.问题描述
    • 2.输入
    • 3.代码
    • 4.测试

一、问题提出

对于线段树,若要求对区间中的所有点都进行更新,可以引入懒操作。

懒操作包括区间更新和区间查询操作。

二、区间更新

对 [l,r] 区间进行更新,例如将 [l,r] 区间所有元素都更新为 v,步骤如下。

1.若当前节点区间被查询区间[l,r]覆盖,则仅对该节点进行更新并做懒标记,表示该节点已被更新,对该节点的子节点暂不更新。

2.判断是在左子树中查询还是在右子树中查询。在查询过程中,若当前节点带有懒标记,则将懒标记传给子节点(将当前节点的懒标记清除,将子节点更新并做懒标记),继续查找。

3.在返回时更新最值。

三、区间查询

带有懒标记的区间查询和普通的区间查询有所不同,在查询过程中若遇到节点有懒标记,则下传懒标记,继续查询。

四、实战

1.问题描述

2.输入

10
5 3 7 2 12 1 6 4 8 15
3 7 25
1 10

3.代码

package com.platform.modules.alg.alglib.p85;

public class P85 {
    public String output = "";

    private final int maxn = 100005;
    private final int inf = 0x3f3f3f3f;
    private int n;
    private int a[] = new int[maxn];
    private node tree[] = new node[maxn * 4]; // 树结点存储数组

    public P85() {
        for (int i = 0; i < tree.length; i++) {
            tree[i] = new node();
        }
    }

    void lazy(int k, int v) {
        tree[k].mx = v; // 更新最值
        tree[k].lz = v; // 做懒标记
    }

    // 向下传递懒标记
    void pushdown(int k) {
        lazy(2 * k, tree[k].lz); // 传递给左孩子
        lazy(2 * k + 1, tree[k].lz); // 传递给右孩子
        tree[k].lz = -1; // 清除自己的懒标记
    }

    // 创建线段树,k表示存储下标,区间[l,r]
    void build(int k, int l, int r) {
        tree[k].l = l;
        tree[k].r = r;
        tree[k].lz = -1; // 初始化懒操作
        if (l == r) {
            tree[k].mx = a[l];
            return;
        }
        int mid, lc, rc;
        mid = (l + r) / 2; // 划分点
        lc = k * 2; // 左孩子存储下标
        rc = k * 2 + 1; // 右孩子存储下标
        build(lc, l, mid);
        build(rc, mid + 1, r);
        tree[k].mx = Math.max(tree[lc].mx, tree[rc].mx); // 结点的最大值等于左右孩子最值的最大值
    }

    // 将区间 [l,r] 修改更新为 v
    void update(int k, int l, int r, int v) {
        // 找到该区间
        if (tree[k].l >= l && tree[k].r <= r) {
            lazy(k, v); // 更新并做懒标记
            return;
        }
        if (tree[k].lz != -1)
            pushdown(k); // 懒标记下移
        int mid, lc, rc;
        mid = (tree[k].l + tree[k].r) / 2; // 划分点
        lc = k * 2; // 左孩子存储下标
        rc = k * 2 + 1; // 右孩子存储下标
        if (l <= mid)
            update(lc, l, r, v); // 到左子树更新
        if (r > mid)
            update(rc, l, r, v);// 到右子树更新
        tree[k].mx = Math.max(tree[lc].mx, tree[rc].mx); // 返回时修改更新最值
    }

    // 求区间 [l,r] 的最值
    int query(int k, int l, int r) {
        int Max = -inf;
        if (tree[k].l >= l && tree[k].r <= r) // 找到该区间
            return tree[k].mx;
        if (tree[k].lz != -1)
            pushdown(k); // 懒标记下移
        int mid, lc, rc;
        mid = (tree[k].l + tree[k].r) / 2; // 划分点
        lc = k * 2; // 左孩子存储下标
        rc = k * 2 + 1; // 右孩子存储下标
        if (l <= mid)
            Max = Math.max(Max, query(lc, l, r)); // 到左子树查询
        if (r > mid)
            Max = Math.max(Max, query(rc, l, r)); // 到右子树查询
        return Max;
    }

    public String cal(String input) {
        int l, r;
        int i, v;
        String[] line = input.split("\n");
        n = Integer.parseInt(line[0]); // 第 1 行:10
        String[] nums = line[1].split(" ");

        // 第 2 行:5 3 7 2 12 1 6 4 8 15
        for (i = 1; i <= n; i++)
            a[i] = Integer.parseInt(nums[i - 1]);
        build(1, 1, n); // 创建线段树
        // 输入查询最值的区间 [l,r] 1 10
        String[] range = line[2].split(" ");
        l = Integer.parseInt(range[0]);
        r = Integer.parseInt(range[1]);
        // 求区间[l..r]的最值
        output += query(1, l, r) + "\n";
        // 输入更新的区间值 l r v  第 3 行: 3 7 25
        String[] change = line[3].split(" ");
        l = Integer.parseInt(change[0]);
        r = Integer.parseInt(change[1]);
        v = Integer.parseInt(change[2]);
        // 将区间 [l,r] 修改更新为 v
        update(1, l, r, v);
        // 求区间[l..r]的最值 第 4 行:1 10
        range = line[4].split(" ");
        l = Integer.parseInt(range[0]);
        r = Integer.parseInt(range[1]);
        // 求区间 [l..r] 的最值
        output += query(1, l, r) + "\n";
        return output;
    }
}

// 结点
class node {
    int l; // l 表示区间左右端点
    int r; // r 表示区间左右端点
    int mx; // mx表示区间[l,r]的最值
    int lz; // lz 懒标记
}

4.测试

以上就是Java数据结构之线段树中的懒操作详解的详细内容,更多关于Java线段树 懒操作的资料请关注我们其它相关文章!

(0)

相关推荐

  • Java数据结构之线段树详解

    目录 介绍 代码实现 线段树构建 区间查询 更新 总结 介绍 线段树(又名区间树)也是一种二叉树,每个节点的值等于左右孩子节点值的和,线段树示例图如下 以求和为例,根节点表示区间0-5的和,左孩子表示区间0-2的和,右孩子表示区间3-5的和,依次类推. 代码实现 /** * 使用数组实现线段树 */ public class SegmentTree<E> { private Node[] data; private int size; private Merger<E> merge

  • Java数据结构之线段树的原理与实现

    目录 简介 实现思路 节点定义 构建线段树 求解区间和 更新线段树 简介 线段树是一种二叉搜索树,是用来维护区间信息的数据结构.可以在O(logN)的时间复杂度内实现单点修改.区间修改.区间查询(区间求和,求区间最大值,求区间最小值)等操作.接下来我以实现区间求和为例子来讲解线段树(最大值和最小值与求和实现方式几乎无异),假设存在一个数组[1,4,6,3,9]. 实现思路 从线段树的定义,我们首先需要定义一个树节点,节点包含区间和(23),区间([1-5]),左节点,右节点等.(如果要实现求区间

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

    目录 一.问题提出 二.区间更新 三.区间查询 四.实战 1.问题描述 2.输入 3.代码 4.测试 一.问题提出 对于线段树,若要求对区间中的所有点都进行更新,可以引入懒操作. 懒操作包括区间更新和区间查询操作. 二.区间更新 对 [l,r] 区间进行更新,例如将 [l,r] 区间所有元素都更新为 v,步骤如下. 1.若当前节点区间被查询区间[l,r]覆盖,则仅对该节点进行更新并做懒标记,表示该节点已被更新,对该节点的子节点暂不更新. 2.判断是在左子树中查询还是在右子树中查询.在查询过程中,

  • Java数据结构之环形链表和约瑟夫问题详解

    目录 一.环形链表 1.创建结点 2.添加小结点 3.显示循环链表 二.约瑟夫问题 1.问题描述 2.首先确定圈大小及开始位置 3.出圈操作 4.出圈方法完整代码 总结 一.环形链表 1.创建结点 环形链表其实也很好理解,就是将单链表的头和尾连接起来,就形成了环形链表. public class Node { public int data; public Node next; public Node(int data) { this.data = data; } @Override publi

  • Java数据结构之图的路径查找算法详解

    目录 前言 算法详解 实现 API设计 代码实现 前言 在实际生活中,地图是我们经常使用的一种工具,通常我们会用它进行导航,输入一个出发城市,输入一个目的地 城市,就可以把路线规划好,而在规划好的这个路线上,会路过很多中间的城市.这类问题翻译成专业问题就是: 从s顶点到v顶点是否存在一条路径?如果存在,请找出这条路径. 例如在上图上查找顶点0到顶点4的路径用红色标识出来,那么我们可以把该路径表示为 0-2-3-4. 如果对图的前置知识不了解,请查看系列文章: [数据结构与算法]图的基础概念和数据

  • Java中对List集合的常用操作详解

    目录: 1.list中添加,获取,删除元素: 2.list中是否包含某个元素: 3.list中根据索引将元素数值改变(替换): 4.list中查看(判断)元素的索引: 5.根据元素索引位置进行的判断: 6.利用list中索引位置重新生成一个新的list(截取集合): 7.对比两个list中的所有元素: 8.判断list是否为空: 9.返回Iterator集合对象: 10.将集合转换为字符串: 11.将集合转换为数组: 12.集合类型转换: 备注:内容中代码具有关联性. 1.list中添加,获取,

  • java迭代器中删除元素的实例操作详解

    我们知道通过Iterator,可以对集合中的元素进行遍历.那么在其中遇到我们不需要的元素时,可不可以在遍历的时候顺便给删除呢?答案是当然可以.在Iterator下有一个remove函数,专门用于删除的操作.下面我们就remove进行讲解,然后对删除元素方法进行说明,最后带来实例的展示. 1.Iterator中的remove void remove():删除迭代器刚越过的元素 从基础集合中移除这个迭代器返回的最后一个元素(可选操作).两个线程中都删除,保证线程的同步. 2.删除元素说明 (1)迭代

  • Java数据结构之链表的增删查改详解

    一.链表的概念和结构 1.1 链表的概念 简单来说链表是物理上不一定连续,但是逻辑上一定连续的一种数据结构 1.2 链表的分类 实际中链表的结构非常多样,以下情况组合起来就有8种链表结构. 单向和双向,带头和不带头,循环和非循环.排列组合和会有8种. 但我这只是实现两种比较难的链表,理解之后其它6种就比较简单了 1.单向不带头非循环链表 2.双向不带头非循环链表 二.单向不带头非循环链表 2.1 创建节点类型 我们创建了一个 ListNode 类为节点类型,里面有两个成员变量,val用来存储数值

  • Java Fluent Mybatis 项目工程化与常规操作详解流程篇 下

    目录 前言 查询 查询写法1 查询写法2 代码说明 新问题 删 总结 前言 接着上一篇:Java Fluent Mybatis 项目工程化与常规操作详解流程篇 上 仓库地址:GitHub仓库 查询 定义查询请求体 package com.hy.fmp.dto.req; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; /** @

  • Java Fluent Mybatis 项目工程化与常规操作详解流程篇 上

    目录 前言 Maven依赖 配置文件调整 Knife4j配置 添加必要实体 增/改 总结 前言 接着上一篇,上篇已经测试通过,成功添加了数据.那么这篇主要是继续上一个项目,将项目进行工程化包装,增加一些必要配置,并且生成增删改查接口. GitHub代码仓库:GitHub仓库 Maven依赖 增加了druid数据库连接池,所以之前的配置文件也需要调整,下面会发出来. <dependency> <groupId>cn.hutool</groupId> <artifac

  • Java数据结构之图的领接矩阵详解

    目录 1.图的领接矩阵(Adjacency Matrix)存储结构 2.图的接口类 3.图的类型,用枚举类表示 4.图的领接矩阵描述 测试类 结果 1.图的领接矩阵(Adjacency Matrix)存储结构 图的领接矩阵(Adjacency Matrix)存储方式是用两个数组来表示图.一个一位数组存储图中顶点信息,一个二维数组(称为领接矩阵)存储图中的边或弧的信息. 举例 无向图 无向图的领接矩阵的第i行或第i列的非零元素个数正好是第i个顶点的度. 有向图 有向图的领接矩阵的第i行的非零元素个

  • Java数据结构之图的两种搜索算法详解

    目录 前言 深度优先搜索算法 API设计 代码实现 广度优先搜素算法 API设计 代码实现 案例应用 前言 在很多情况下,我们需要遍历图,得到图的一些性质,例如,找出图中与指定的顶点相连的所有顶点,或者判定某个顶点与指定顶点是否相通,是非常常见的需求. 有关图的搜索,最经典的算法有深度优先搜索和广度优先搜索,接下来我们分别讲解这两种搜索算法. 学习本文前请先阅读这篇文章 [数据结构与算法]图的基础概念和数据模型. 深度优先搜索算法 所谓的深度优先搜索,指的是在搜索时,如果遇到一个结点既有子结点,

随机推荐