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

线段树

什么是线段树

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

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.

线段树特性

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

  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

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;
    }

    @Override
    public String toString() {
        return "SegmentNode{" +
                "left=" + left +
                ", right=" + right +
                ", start=" + start +
                ", end=" + end +
                ", sum=" + sum +
                '}';
    }
}

构建线段树

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;
}

查询指定线段树数据

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值。

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;
    }

    @Override
    public 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 = 36
  Node [0, 2]: value = 9
    Node [0, 1]: value = 4
      Node [0, 0]: value = 1
      Node [1, 1]: value = 3
    Node [2, 2]: value = 5
  Node [3, 5]: value = 27
    Node [3, 4]: value = 16
      Node [3, 3]: value = 7
      Node [4, 4]: value = 9
    Node [5, 5]: value = 11
-------------------------------------------------------
Node [0, 5]: value = 45
  Node [0, 2]: value = 18
    Node [0, 1]: value = 4
      Node [0, 0]: value = 1
      Node [1, 1]: value = 3
    Node [2, 2]: value = 5
  Node [3, 5]: value = 27
    Node [3, 4]: value = 16
      Node [3, 3]: value = 7
      Node [4, 4]: value = 9
    Node [5, 5]: value = 11
5
相关推荐
x13870285957几秒前
c语言中srtlen(指针使用计算字符长度)、传值和传址调用
c语言·开发语言·算法·visual studio
海兰7 分钟前
【实用程序】电商销售分析仪表盘 — 从零搭建一个AI参与的全栈数据洞察系统
人工智能·学习·算法
iCxhust16 分钟前
C#进程管理程序
开发语言·汇编·stm32·单片机·c#·微机原理
凡人叶枫23 分钟前
Effective C++ 条款28:避免使用 handles 指向对象内部
linux·服务器·开发语言·c++·嵌入式开发
技术小结-李爽24 分钟前
【工具】Maven的下载、安装、使用
java·maven
AI帮小忙25 分钟前
Debian系linux操作系统里安装OpenClaw
linux·运维·debian
极创信息27 分钟前
Linux挖矿病毒深度清理实战教程,从进程隐藏、Rootkit驻留到彻底根除
java·大数据·linux·运维·安全·tomcat·健康医疗
努力成为AK大王33 分钟前
并发编程的核心挑战、优化方案与核心知识点总结
java·开发语言·数据库
云烟成雨TD36 分钟前
Agent Scope Java 2.x 系列【10】技能(Skill)
java·人工智能·agent
摇滚侠40 分钟前
SpringMVC 入门到实战 DispatcherServlet 源码解读 92-95
java·后端·spring·maven·intellij-idea