数据结构和算法-06线段树-01

ops/2024/12/16 4:37:45/

线段树

什么是线段树

线段树是一种**[二叉搜索树]**,与[**区间树]**相似,它将一个区间划分成一些单元区间,每个单元区间对应线段树的一个结点

[Segment Tree] is a data structure that stores data about range of elements in nodes as a tree. It is mostly used to handle range queries with updates in an efficient manner.

image-20240908140040192

线段树特性

线段树具有平衡二叉树的特性,节点有范围(可以用来记录范围最值,求和,平均值等)。

  1. A segment tree is a binary tree with a leaf node for each element in the array. ---- 平衡二叉树

  2. Each internal node represents a segment or a range of elements in the array. — 节点表示范围

  3. The root node represents the entire array. — 根节点表示整棵树的范围

  4. Each node stores information about the segment it represents, such as the sum or minimum of the elements in the segment. – -每个节点存储线段的最大值,最小值

编号线段树特性
1平衡二叉树
2节点表示范围
3根节点表示整棵树的范围
4每个节点存储线段的最大、最小值

线段树的实现-节点

创建节点SegmentNode

image-20241212123440532
java">*** 线段树节点*/
public class SegmentNode {public SegmentNode left;public SegmentNode right;public int start;public int end;public int sum;public SegmentNode(int start, int end) {this.start = start;this.end = end;}@Overridepublic String toString() {return "SegmentNode{" +"left=" + left +", right=" + right +", start=" + start +", end=" + end +", sum=" + sum +'}';}
}

构建线段树

image-20241212125239825

java">public SegmentNode buildTree(int start, int end) {if (start > end) {return null;}SegmentNode newNode = new SegmentNode(start, end);if (start == end) {newNode.sum = elements[start];return newNode;}int mid = start + (end - start) / 2;newNode.left = buildTree(start, mid);newNode.right = buildTree(mid + 1, end);return newNode;
}

查询指定线段树数据

image-20241212132205054

image-20241212132740472

image-20241212134050909

java">public long query(SegmentNode root, int queryStart, int queryEnd){if(queryStart > root.end || queryEnd < root.start){return  0;}if(queryStart <= root.start && queryEnd >= root.end){return root.sum;}long leftSum = query(root.left, queryStart, queryEnd);long rightSum = query(root.right, queryStart,queryEnd);return leftSum +rightSum;
}

更新线段树

在线段树中,pushDown() 方法通常用于实现懒更新(Lazy Propagation)策略。懒更新策略允许我们在更新操作时避免不必要的子树遍历,只在真正需要时才将更新值下推到子节点。在节点中添加lazy属性记录lazy值。

如果我们每进行一次加的操作,就将全部线段树更改一边,时间复杂度会很高。因此,我们需要进行一个延迟加和的操作。

[思路]:如果 [left,right] 区间增加 a,在查询时,就可以把 [left,right] 区间标记的增加量推下去就可以直接求值了。

这时候,我们需要记录一个懒标记lazy,来记录这个区间增加量

pushDown()

创建pushDown( )方法,如果子节点不为空,更新子节点的lazy值,即记录子节点的lazy值。

image-20241212142640302
java">private void pushDown(SegmentNode node) {if (node.left == null || node.right == null) return;if (node.lazy != 0) {int mid = node.start + (node.end - node.start)/2;node.left.sum += node.lazy * (mid - node.start + 1);node.left.lazy += node.lazy;node.right.sum += node.lazy * (node.end -mid);node.right.lazy += node.lazy;node.lazy = 0;}}
更新节点
java">public void update(SegmentNode currNode,int updateStart,int updateEnd,int updateVal) {//不在范围内,更新失败if (updateStart > currNode.end || updateEnd < currNode.start) return ;//完全闭合区域if (updateStart >= currNode.start && updateEnd <= currNode.end) {currNode.sum += (currNode.end - currNode.start + 1) * updateVal;currNode.lazy += updateVal;return ;}pushDown(currNode);update(currNode.left, updateStart, updateEnd, updateVal);update(currNode.right, updateStart, updateEnd, updateVal);currNode.sum = currNode.left.sum + currNode.right.sum;return ;
}
打印线段树
java">public void printTree(SegmentTreeNode root, String prefix) {System.out.println(prefix + "Node [" + root.start + ", " + root.end + "]: value = " + root.sum + ", lazy = " + root.lazy);if (root.left != null) {printTree(root.left, prefix + "  "); // 增加前缀空格以便形成缩进}if (root.right != null) {printTree(root.right, prefix + "  ");}
}

完整代码实现

java">public class SegmentNode {public SegmentNode left;public SegmentNode right;public int start;public int end;public int sum;public int lazy;public SegmentNode(int start, int end) {this.start = start;this.end = end;}@Overridepublic String toString() {return "SegmentNode{" +", start=" + start +", end=" + end +", sum=" + sum +'}';}
}
java">package com.training.segment;public class SegmentTree {private int[] elements;public SegmentTree(int[] elements) {this.elements = elements;}public SegmentNode buildTree(int start, int end) {if (start > end) {return null;}SegmentNode newNode = new SegmentNode(start, end);if (start == end) {newNode.sum = elements[start];return newNode;}int mid = start + (end - start) / 2;newNode.left = buildTree(start, mid);newNode.right = buildTree(mid + 1, end);newNode.sum = (newNode.left == null ? 0 : newNode.left.sum) + (newNode.right == null ? 0 : newNode.right.sum);return newNode;}public long query(SegmentNode root, int queryStart, int queryEnd) {if (queryStart > root.end || queryEnd < root.start) {return 0;}if (queryStart <= root.start && queryEnd >= root.end) {return root.sum;}long leftSum = query(root.left, queryStart, queryEnd);long rightSum = query(root.right, queryStart, queryEnd);return leftSum + rightSum;}private void pushDown(SegmentNode node) {if (node.left == null || node.right == null) return;if (node.lazy != 0) {int mid = node.start + (node.end - node.start) / 2;node.left.sum += node.lazy * (mid - node.start + 1);node.left.lazy += node.lazy;node.right.sum += node.lazy * (node.end - mid);node.right.lazy += node.lazy;node.lazy = 0;}}public void update(SegmentNode currNode,int updateStart,int updateEnd,int updateVal) {//不在范围内,更新失败if (updateStart > currNode.end || updateEnd < currNode.start) return;//完全闭合区域if (updateStart <= currNode.start && updateEnd >= currNode.end) {currNode.sum += (currNode.end - currNode.start + 1) * updateVal;currNode.lazy += updateVal;return;}pushDown(currNode);update(currNode.left, updateStart, updateEnd, updateVal);update(currNode.right, updateStart, updateEnd, updateVal);int leftSum = currNode.left == null ? 0 : currNode.left.sum;int rightSum = currNode.right == null ? 0 : currNode.right.sum;currNode.sum = leftSum + rightSum;}public void printTree(SegmentNode root, String prefix) {System.out.println(prefix + "Node [" + root.start + ", " + root.end + "]: value = "+ root.sum);if (root.left != null) {printTree(root.left, prefix + "  "); // 增加前缀空格以便形成缩进}if (root.right != null) {printTree(root.right, prefix + "  ");}}public static void main(String[] args) {int[] data = {1, 3, 5, 7, 9, 11};SegmentTree tree = new SegmentTree(data);SegmentNode root = tree.buildTree(0, data.length - 1);tree.printTree(root, "");tree.update(root, 0, 2, 3);System.out.println("-------------------------------------------------------");tree.printTree(root, "");System.out.println(tree.query(root, 2, 2));}
}

测试结果

java">Node [0, 5]: value = 36Node [0, 2]: value = 9Node [0, 1]: value = 4Node [0, 0]: value = 1Node [1, 1]: value = 3Node [2, 2]: value = 5Node [3, 5]: value = 27Node [3, 4]: value = 16Node [3, 3]: value = 7Node [4, 4]: value = 9Node [5, 5]: value = 11
-------------------------------------------------------
Node [0, 5]: value = 45Node [0, 2]: value = 18Node [0, 1]: value = 4Node [0, 0]: value = 1Node [1, 1]: value = 3Node [2, 2]: value = 5Node [3, 5]: value = 27Node [3, 4]: value = 16Node [3, 3]: value = 7Node [4, 4]: value = 9Node [5, 5]: value = 11
5

http://www.ppmy.cn/ops/142293.html

相关文章

etcd节点扩/缩容

etcd集群节点数量的说明 etcd 是基于 raft算法的分布式键值数据库&#xff0c;生来就为集群化而设计的&#xff0c;由于Raft算法在做决策时需要超半数节点的投票&#xff0c;所以etcd集群一般推荐奇数节点&#xff0c;如3、5或者7个节点构成一个集群。 对于具有 n 个成员的集群…

构建centos docker基础镜像

1、介绍 比较老的版本docker镜像&#xff0c;不太好找&#xff0c;可以尝试自己构建 各版本构建基础镜像方法不太一样&#xff0c;方式也不同&#xff0c;自己尝试&#xff0c;本文只介绍了我自己的尝试 2、构建centos5.11 docker镜像 准备iso文件 &#xff08;1&#xff09;安…

Tomcat原理(1)——IDEA实现模拟服务端和客户端的互传

引入 一、什么是Tomcat Tomcat是一个开源的Java Web应用服务器&#xff0c;主要用于运行Java编写的网站和Web应用程序。实质上可以理解为是一个容器&#xff0c;一个用于承载项目的容器。 tomcat有什么作用&#xff0c;最基础来讲&#xff0c;当我们创建一个文件&#xff0c;当…

如何发挥网络爬虫利器phpSpider最大功效

要发挥网络爬虫利器phpSpider的最大功效&#xff0c;可以从以下几个方面入手&#xff1a; 一、基础配置与优化 安装与配置&#xff1a; 确保PHP环境已正确安装&#xff0c;并通过Composer等工具安装phpSpider及其依赖。根据目标网站的特点&#xff0c;合理设置phpSpider的配置…

React useState使用中遇到的问题及解决办法

React 中的 useState Hook 是一个非常强大和常用的工具&#xff0c;用于在函数组件中管理状态。然而&#xff0c;在使用 useState 时&#xff0c;可能会遇到一些问题和困惑。本文将详细解释 useState 的工作原理&#xff0c;并提供解决常见问题的方法。 useState 的基本用法 …

区块链软件系统海外宣发:全球化市场中的策略与实施

随着区块链技术的快速发展&#xff0c;越来越多的区块链软件系统进入全球市场&#xff0c;涉及加密货币、智能合约、去中心化金融&#xff08;DeFi&#xff09;、供应链管理等多个行业应用。为了在激烈的竞争中脱颖而出&#xff0c;区块链软件系统不仅需要具备卓越的技术能力&a…

phpSpider如何处理网页内容的动态加载问题

phpSpider处理网页内容的动态加载问题&#xff0c;主要采取以下几种策略&#xff1a; 一、分析并直接请求API 现代网站中&#xff0c;很多动态加载的内容是通过后端的API接口以JSON或XML等格式返回的。phpSpider可以通过分析网页的请求&#xff0c;找到这些API接口的URL&…

什么是JVM即时编译

什么是JVM即时编译 即时编译是JVM的核心功能&#xff0c;他让java在性能上不输于C/C JVM&#xff08;Java Virtual Machine&#xff09;即 Java 虚拟机&#xff0c;是一种用于执行 Java 字节码的虚拟计算机。它是 Java 程序的运行核心&#xff0c;提供了一个独立于底层操作系统…