引用Wiki对线段树的介绍:
线段树(Segment Tree)是一种二叉搜索树,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点。
对于线段树中的每一个非叶子节点[a,b],它的左子树表示的区间为[a,(a+b)/2],右子树表示的区间为[(a+b)/2+1,b]。因此线段树是平衡二叉树。叶节点数目为N,即整个线段区间的长度。
使用线段树可以快速的查找某一个节点在若干条线段中出现的次数,时间复杂度为O(logN)。而未优化的空间复杂度为2N,因此有时需要离散化让空间压缩。
这里举一个简单的例子来说明线段树如何来解决区间查询的问题(题目URL http://acm.hdu.edu.cn/showproblem.php?pid=1166):
1 10 1 2 3 4 5 6 7 8 9 10 Query 1 3 Add 3 6 Query 2 7 Sub 10 2 Add 6 3 Query 3 10 End
Case 1: 6 33 59
下面是用线段树解题的代码:
package tree.segment; import java.util.Scanner; public class SegmentTree { public int leftBorder; public int rightBorder; public int value; public SegmentTree leftChild; public SegmentTree rightChild; /** * @param args */ public static void main(String[] args) { Scanner in = new Scanner(System.in); int T = Integer.parseInt(in.nextLine()); for (int cs=1; cs<=T; cs++) { int N = Integer.parseInt(in.nextLine()); int[] valueArray = new int[N+1]; String[] values = in.nextLine().trim().split(" "); for (int i=1; i<=N; i++) valueArray[i] = Integer.parseInt(values[i-1]); SegmentTree root = build(1, N, valueArray); System.out.println("Case "+cs+":"); while (in.hasNext()) { String command = in.nextLine(); if (command.startsWith("E")) break; else if (command.startsWith("Q")) { String[] details = command.split(" "); System.out.println(querySum(root, Integer.parseInt(details[1]), Integer.parseInt(details[2]))); } else if (command.startsWith("A")) { String[] details = command.split(" "); changeValue(root, Integer.parseInt(details[1]), Integer.parseInt(details[2])); } else { String[] details = command.split(" "); changeValue(root, Integer.parseInt(details[1]), 0-Integer.parseInt(details[2])); } } traverseTree(root); } } //构建线段树 public static SegmentTree build(int left, int right, int[] valueArray) { SegmentTree tree = new SegmentTree(); tree.leftBorder = left; tree.rightBorder = right; if (left == right) { tree.value = valueArray[left]; tree.leftChild = tree.rightChild = null; return tree; } int middle = (left + right) >> 1; tree.leftChild = build(left, middle, valueArray); tree.rightChild = build(middle+1, right, valueArray); pushUp(tree, tree.leftChild, tree.rightChild); return tree; } //计算区间[left, right]内所有结点和 public static int querySum(SegmentTree root, int left, int right) { if (left <= root.leftBorder && right >= root.rightBorder) { return root.value; } int value = 0; if (root.leftChild != null && left <= root.leftChild.rightBorder) value += querySum(root.leftChild, left, right); if (root.rightChild !=null && right >= root.rightChild.leftBorder) value += querySum(root.rightChild, left, right); return value; } //把区间为[node, node]的叶子节点上的值增加value public static void changeValue(SegmentTree root, int node, int value) { if (root.rightBorder < node || root.leftBorder > node) return; if (root.leftBorder == root.rightBorder && root.leftBorder == node) { root.value += value; return; } if (root.leftChild.rightBorder >= node) { changeValue(root.leftChild, node, value); } else { changeValue(root.rightChild, node, value); } pushUp(root, root.leftChild, root.rightChild); } //更新当前节点值 public static void pushUp(SegmentTree root, SegmentTree left, SegmentTree right) { root.value = left.value + right.value; } //遍历树,打印叶子节点的值 public static void traverseTree(SegmentTree root) { if (root.leftChild == null) System.out.println(root.leftBorder + "=>" + root.value); else { System.out.println("left:"+root.leftBorder+" right:"+root.rightBorder+" value:" + root.value); traverseTree(root.leftChild); traverseTree(root.rightChild); } } }
相关练习题:
http://acm.hdu.edu.cn/showproblem.php?pid=1754
http://acm.hdu.edu.cn/showproblem.php?pid=1394
http://acm.hdu.edu.cn/showproblem.php?pid=2795
http://poj.org/problem?id=2828
http://poj.org/problem?id=2886
其它线段树相关学习博客地址:
http://dongxicheng.org/structure/segment-tree/
http://www.notonlysuccess.com/index.php/segment-tree-complete/