引用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):
中央情报局要研究敌人究竟演习什么战术,所以Tidy要随时向Derek汇报某一段连续的工兵营地一共有多少人,例如Derek问:“Tidy,马上汇报第3个营地到第10个营地共有多少人!”Tidy就要马上开始计算这一段的总人数并汇报。但敌兵营地的人数经常变动,而Derek每次询问的段都不一样,所以Tidy不得不每次都一个一个营地的去数,很快就精疲力尽了,Derek对Tidy的计算速度越来越不满:"你个死肥仔,算得这么慢,我炒你鱿鱼!”Tidy想:“你自己来算算看,这可真是一项累人的工作!我恨不得你炒我鱿鱼呢!”无奈之下,Tidy只好打电话向计算机专家Windbreaker求救,Windbreaker说:“死肥仔,叫你平时做多点acm题和看多点算法书,现在尝到苦果了吧!”Tidy说:"我知错了。。。"但Windbreaker已经挂掉电话了。Tidy很苦恼,这么算他真的会崩溃的,聪明的读者,你能写个程序帮他完成这项工作吗?不过如果你的程序效率不够高的话,Tidy还是会受到Derek的责骂的.
每组数据第一行一个正整数N(N<=50000),表示敌人有N个工兵营地,接下来有N个正整数,第i个正整数ai代表第i个工兵营地里开始时有ai个人(1<=ai<=50)。
接下来每行有一条命令,命令有4种形式:
(1) Add i j,i和j为正整数,表示第i个营地增加j个人(j不超过30)
(2)Sub i j ,i和j为正整数,表示第i个营地减少j个人(j不超过30);
(3)Query i j ,i和j为正整数,i<=j,表示询问第i到第j个营地的总人数;
(4)End 表示结束,这条命令在每组数据最后出现;
每组数据最多有40000条命令
对于每个Query询问,输出一个整数并回车,表示询问的段中的总人数,这个数保持在int以内。
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/