自学内容网 自学内容网

线段树-认识线段树+实现线段树

一、认识线段树

1、定义

线段树是平衡二叉树

2、特点

线段树将一个区间划分成单元区间,每个单元区间对应线段树中的一个结点

3、应用

频繁查找一个数组中指定区间内的和、最值

学了动态规划后使用迭代要好过使用递归,因为递归每次进去是有空间损耗的

二、线段树的实现(放数据、查数据)

1、线段树节点

/*
    线段树节点类
 */
public class SegmentNode {
      public int start;// 区间起点
      public int end;// 区间终点
      public int sum;// 区间内求和
      public SegmentNode left;
      public SegmentNode right;

    public SegmentNode(int start, int end) {
        this.start = start;
        this.end = end;
    }
}

2、线段树的创建

/*
   线段树类
 */
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;
        }

        // 当start < end时,由于线段树是节点区间整体取值,因此要进行递归,求取左右子树的值
        int mid = start + (end - start)/2;// 获取区间中点 不使用mid = (start + end)/2的原因是可能会超出int类型的最大值
        newNode.left = buildTree(start,mid);
        newNode.right = buildTree(mid + 1,end);
        newNode.sum = newNode.left.sum + newNode.right.sum;

        return newNode;
    }
}

3、打印线段树中的数据

(1)四种方式

  • 前序遍历:中左右
  • 中序遍历:左中右
  • 后序遍历:左中右

(2)前序遍历打印线段树中的数据

  • 核心代码
public void printTree(SegmentNode root,String prefix){
        System.out.println(prefix + "Node [" + root.start + "," + root.end + "] sum: " + 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[] nums = {1, 11, 5, 7, 9, 2};
        SegmentTree tree = new SegmentTree(nums);// 创建树
        SegmentNode root = tree.buildTree(0,nums.length-1);// 向树中放入数据
        tree.printTree(root, "");// 打印树
    }
  • 结果输出 

 

 4、在线段树中查询给定区间的和

(1)核心代码

public int query(SegmentNode root,int start,int end){
        // 所给区间与线段树区间无交集
        if(start > root.end || end < root.start){
              return 0;// 0: 作用1:代表所给区间与线段树无交集 作用2:不会影响给定区间求和
        }

        // 所给区间包含线段树区间
        if(start <= root.start && end >= root.end){
              return root.sum;// !!! 这一块要返回的是当前结点的和
        }

        // 所给区间与线段树区间有部分交集
        int leftSum = query(root.left,start,end);
        int rightSum = query(root.right,start,end);

        return leftSum + rightSum;
    }

(2)测试代码

public static void main(String[] args) {
        int[] nums = {1, 11, 5, 7, 9, 2};
        SegmentTree tree = new SegmentTree(nums);// 创建树
        SegmentNode root = tree.buildTree(0,nums.length-1);// 向树中放入数据
        tree.printTree(root, "");// 打印树
        System.out.println(tree.query(root,1,3));// 查询给定区间的和
    }

(3)结果输出

5、更新线段树中每个节点的sum属性的值(即给原来的每个数组元素的值都加上指定大小的更新新值

(1)优化代码的思想:要做一件事情的时候,先将这件事情攒着,当到达某一时间点的时候,把攒着的事情一块一做

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

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

(4)具体思路:如果【left,right】区间增加a,在查询时,就可以把【left,right】区间标记的增加量推下去就可以直接求值了

(5)pushDown()方法

public void pushDown(SegmentNode node){
        // 子节点不存在不需要下推
        if(node.left == null || node.right == null){
            return;
        }
        if(node.lazy != 0){
            node.left.sum += node.lazy * (node.end - node.start + 1);
            node.left.lazy += node.lazy;
            node.right.sum += node.lazy * (node.end - node.start + 1);
            node.right.lazy += node.lazy;
        }
        node.lazy = 0;
    }

(6)update方法

缺点:不能将所有节点的sum属性的值全部正确更新

public SegmentNode update(SegmentNode root,int start,int end,int updateVal){
         // 给定区间与线段树区间无交集
         if(start > root.end || end < root.start){
             return null;// 给定区间有误,未进行更新操作
         }

         // 给定区间包含线段树区间
         if(start<=root.start && end>=root.end){
             root.sum += (root.end - root.start + 1)*updateVal;
             root.lazy += updateVal;
             return root;
         }

         pushDown(root);

         update(root.left,start,end,updateVal);
         update(root.right,start,end,updateVal);
         root.sum = root.left.sum + root.right.sum;
         return root;
    }

(7)测试代码

public static void main(String[] args) {
        int[] nums = {1, 3, 5, 7, 9, 11};
        SegmentTree tree = new SegmentTree(nums);// 创建树
        SegmentNode root = tree.buildTree(0,nums.length-1);// 向树中放入数据
        tree.printTree(root, "");// 打印树
        System.out.println("---------------------");
        tree.update(root,1,4,5);// 更新线段树,更新值为5
        tree.printTree(root, "");// 打印树
    }


原文地址:https://blog.csdn.net/Watermelon_Mr/article/details/142134857

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!