线段树
一、使用场景
线段树又叫区间树,也是一种二叉树
场景
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"); } }