线段树

一、使用场景


 

线段树又叫区间树,也是一种二叉树

场景

1、通常用于求解区间和(积、最大值、最小值)(满足结合律)

2、节点值会不断修改

 

如下:每个节点至少有2个属性:区间、区间值

二分的思想:每个节点把区间的和保存起来

这样可以在O(logN)时间内查询出区间和

 

 

 

二、线段树构建


 

由数组递归构建

/**
     * 构建线段树
     *
     * @param array 数组
     * @param left 左范围
     * @param right 右范围
     * @return ZkwTree
     */
    public static ZkwTree build(int[] array, int left, int right) {
        ZkwTree zkwTree = new ZkwTree();
        zkwTree.left = left;
        zkwTree.right = right;
        if (left == right) {
            // 叶子节点 退出
            zkwTree.value = array[left];
            return zkwTree;
        }

        int mid = (left + right) / 2;
        ZkwTree leftZkwTree = build(array, left, mid);
        ZkwTree rightZkwTree = build(array, mid + 1, right);
        zkwTree.value = Math.addExact(leftZkwTree.value, rightZkwTree.value);
        zkwTree.leftZkwTree = leftZkwTree;
        zkwTree.rightZkwTree = rightZkwTree;
        return zkwTree;
    }

  

三、单点更新


 

某个节点变更,只影响此节点的父节点,依次内推,直到根节点

时间复杂度O(logN)

/**
     * 单点更新
     *
     * @param rootTree 根节点
     * @param index 需要更新的索引
     * @param addVale 需要添加的值
     */
    public static void updateSingle(ZkwTree rootTree, int index, int addVale) {
        if (rootTree.left == rootTree.right) {
            if (index == rootTree.left) {
                rootTree.value += addVale;
            }
            // 叶子节点 退出
            return;
        }

        int mid = (rootTree.left + rootTree.right) / 2;
        if (index <= mid) {
            // 更新左节点
            updateSingle(rootTree.leftZkwTree, index, addVale);
        } else {
            // 更新右节点
            updateSingle(rootTree.rightZkwTree, index, addVale);
        }

        // 更新父节点
        rootTree.value = rootTree.leftZkwTree.value + rootTree.rightZkwTree.value;
    }

  

四、区间更新


 

延时标记:若某节点的区间正好满足区间更新的范围 则记录延时标记,当前节点更新,而子节点不更新

                  子节点只有在使用时,才延时更新,提示效率

/**
     * 区间更新(一段区域同时添加相同的值)
     * 延时标记(若一个节点区间跟更新的区间完全一致,先更新当前节点的值,标记,后续查询和更新时若用到子节点,需要处理子节点的值,且标记子节点 父节点的标记清除,以此类推)
     *
     * @param rootTree
     * @param left
     * @param right
     * @param addValue
     */
    public static void updateRange(ZkwTree rootTree, int left, int right, int addValue) {
        if (rootTree.left == left && rootTree.right == right) {
            // 区间匹配 更新 记录标记
            rootTree.value += Math.multiplyExact(right - left + 1, addValue);

            // 延时标记
            rootTree.lazy += addValue;
            return;
        }

        // 处理延时标记 子节点的值需要更新
        dealLazyDown(rootTree);

        int mid = (rootTree.left + rootTree.right) / 2;
        if (right <= mid) {
            // 子树更新
            updateRange(rootTree.leftZkwTree, left, right, addValue);
        } else if (left > mid) {
            // 右树更新
            updateRange(rootTree.rightZkwTree, left, right, addValue);
        } else {
            // 左右子树同时更新
            updateRange(rootTree.leftZkwTree, left, mid, addValue);
            updateRange(rootTree.rightZkwTree, mid + 1, right, addValue);
        }

        // 子节点更新后 更新当前节点的值
        rootTree.value = Math.addExact(rootTree.leftZkwTree.value, rootTree.rightZkwTree.value);
    }

  

五、区间查询


 

时间复杂度O(logN),一般不会查到叶子节点,只要区间满足范围就退出了

 public static int query(ZkwTree rootTree, int left, int right) {
        if (left < rootTree.left || right > rootTree.right) {
            throw new IllegalArgumentException("index param is invalid");
        }

        // 正好区间范围匹配 直接返回
        if (rootTree.left == left && rootTree.right == right) {
            return rootTree.value;
        }

        int mid = (rootTree.left + rootTree.right) / 2;
        if (right <= mid) {
            return query(rootTree.leftZkwTree, left, right);
        } else if (left > mid) {
            return query(rootTree.rightZkwTree, left, right);
        } else {
            return query(rootTree.leftZkwTree, left, mid) + query(rootTree.rightZkwTree, mid + 1, right);
        }
    }

  

 

六、完整代码


 

/**
 * 线段树(用于解决区间和 积 最大 最小 具有结合律特点)
 *
 * 构建
 * 查询
 * 单点更新
 * 区间更新(延时标记)
 *
 */
public class ZkwTree {
    // 区间左索引
    private int left;

    // 区间右索引
    private int right;

    // 区间的值(如求和)
    private int value;

    /**
     * 区间更新 延时标记
     * 表示子节点还未处理,当前节点已经处理了!!!!
     */
    private int lazy;

    private ZkwTree leftZkwTree;

    private ZkwTree rightZkwTree;

    /**
     * 构建线段树
     *
     * @param array 数组
     * @param left 左范围
     * @param right 右范围
     * @return ZkwTree
     */
    public static ZkwTree build(int[] array, int left, int right) {
        ZkwTree zkwTree = new ZkwTree();
        zkwTree.left = left;
        zkwTree.right = right;
        if (left == right) {
            // 叶子节点 退出
            zkwTree.value = array[left];
            return zkwTree;
        }

        int mid = (left + right) / 2;
        ZkwTree leftZkwTree = build(array, left, mid);
        ZkwTree rightZkwTree = build(array, mid + 1, right);
        zkwTree.value = Math.addExact(leftZkwTree.value, rightZkwTree.value);
        zkwTree.leftZkwTree = leftZkwTree;
        zkwTree.rightZkwTree = rightZkwTree;
        return zkwTree;
    }

    public static int query(ZkwTree rootTree, int left, int right) {
        if (left < rootTree.left || right > rootTree.right) {
            throw new IllegalArgumentException("index param is invalid");
        }

        // 正好区间范围匹配 直接返回
        if (rootTree.left == left && rootTree.right == right) {
            return rootTree.value;
        }

        int mid = (rootTree.left + rootTree.right) / 2;
        if (right <= mid) {
            return query(rootTree.leftZkwTree, left, right);
        } else if (left > mid) {
            return query(rootTree.rightZkwTree, left, right);
        } else {
            return query(rootTree.leftZkwTree, left, mid) + query(rootTree.rightZkwTree, mid + 1, right);
        }
    }

    /**
     * 单点更新
     *
     * @param rootTree 根节点
     * @param index 需要更新的索引
     * @param addVale 需要添加的值
     */
    public static void updateSingle(ZkwTree rootTree, int index, int addVale) {
        if (rootTree.left == rootTree.right) {
            if (index == rootTree.left) {
                rootTree.value += addVale;
            }
            // 叶子节点 退出
            return;
        }

        int mid = (rootTree.left + rootTree.right) / 2;
        if (index <= mid) {
            // 更新左节点
            updateSingle(rootTree.leftZkwTree, index, addVale);
        } else {
            // 更新右节点
            updateSingle(rootTree.rightZkwTree, index, addVale);
        }

        // 更新父节点
        rootTree.value = rootTree.leftZkwTree.value + rootTree.rightZkwTree.value;
    }

    /**
     * 区间更新(一段区域同时添加相同的值)
     * 延时标记(若一个节点区间跟更新的区间完全一致,先更新当前节点的值,标记,后续查询和更新时若用到子节点,需要处理子节点的值,且标记子节点 父节点的标记清除,以此类推)
     *
     * @param rootTree
     * @param left
     * @param right
     * @param addValue
     */
    public static void updateRange(ZkwTree rootTree, int left, int right, int addValue) {
        if (rootTree.left == left && rootTree.right == right) {
            // 区间匹配 更新 记录标记
            rootTree.value += Math.multiplyExact(right - left + 1, addValue);

            // 延时标记
            rootTree.lazy += addValue;
            return;
        }

        // 处理延时标记 子节点的值需要更新
        dealLazyDown(rootTree);

        int mid = (rootTree.left + rootTree.right) / 2;
        if (right <= mid) {
            // 子树更新
            updateRange(rootTree.leftZkwTree, left, right, addValue);
        } else if (left > mid) {
            // 右树更新
            updateRange(rootTree.rightZkwTree, left, right, addValue);
        } else {
            // 左右子树同时更新
            updateRange(rootTree.leftZkwTree, left, mid, addValue);
            updateRange(rootTree.rightZkwTree, mid + 1, right, addValue);
        }

        // 子节点更新后 更新当前节点的值
        rootTree.value = Math.addExact(rootTree.leftZkwTree.value, rootTree.rightZkwTree.value);
    }

    /**
     * 处理延时标记
     * 查询的范围只包含此节点部分区间 子节点的值需要此时需要处理
     * @param rootTree
     */
    private static void dealLazyDown(ZkwTree rootTree) {
        // 非叶子节点 处理延时标记
        if (rootTree.lazy != 0 && rootTree.left != rootTree.right) {
            rootTree.leftZkwTree.value +=
                Math.multiplyExact(rootTree.leftZkwTree.right - rootTree.leftZkwTree.left + 1, rootTree.lazy);
            rootTree.rightZkwTree.value +=
                Math.multiplyExact(rootTree.rightZkwTree.right - rootTree.rightZkwTree.left + 1, rootTree.lazy);

            // 子节点标记
            rootTree.leftZkwTree.lazy += rootTree.lazy;
            rootTree.rightZkwTree.lazy += rootTree.lazy;
        }
    }

    public static void main(String[] args) {
        int[] array = {1, 2, 3, 4, 5, 6, 7, 8, 9};
        // 构建
        ZkwTree zkwTree = build(array, 0, array.length - 1);
        int sum0_1 = query(zkwTree, 0, 1);
        assert sum0_1 == 3;

        int sum2_4 = query(zkwTree, 2, 4);
        assert sum2_4 == 12;

        // 单点更新
        updateSingle(zkwTree, 1, 1);
        sum0_1 = query(zkwTree, 0, 1);
        int sum0_2 = query(zkwTree, 0, 2);
        sum2_4 = query(zkwTree, 2, 4);
        assert sum0_1 == 4;
        assert sum0_2 == 7;
        assert sum2_4 == 12;

        // 区间更新 延时标记
        updateRange(zkwTree, 2, 4, 2);
        sum0_1 = query(zkwTree, 0, 1);
        assert sum0_1 == 4;
        sum2_4 = query(zkwTree, 2, 4);
        assert sum2_4 == 15;
        sum0_2 = query(zkwTree, 0, 2);
        assert sum0_2 == 9;
        int sum3_5 = query(zkwTree, 3, 5);
        assert sum3_5 == 19;

        System.out.println("OK");
    }
}
View Code

 

posted @ 2020-04-07 23:10  蓝天随笔  阅读(413)  评论(0编辑  收藏  举报