线段树入门

引用Wiki对线段树的介绍:

线段树Segment Tree)是一种二叉搜索树,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点。

对于线段树中的每一个非叶子节点[a,b],它的左子树表示的区间为[a,(a+b)/2],右子树表示的区间为[(a+b)/2+1,b]。因此线段树是平衡二叉树。叶节点数目为N,即整个线段区间的长度。

使用线段树可以快速的查找某一个节点在若干条线段中出现的次数,时间复杂度为O(logN)。而未优化的空间复杂度为2N,因此有时需要离散化让空间压缩。

线段树入门_第1张图片


这里举一个简单的例子来说明线段树如何来解决区间查询的问题(题目URL http://acm.hdu.edu.cn/showproblem.php?pid=1166):

Problem Description
C国的死对头A国这段时间正在进行军事演习,所以C国间谍头子Derek和他手下Tidy又开始忙乎了。A国在海岸线沿直线布置了N个工兵营地,Derek和Tidy的任务就是要监视这些工兵营地的活动情况。由于采取了某种先进的监测手段,所以每个工兵营地的人数C国都掌握的一清二楚,每个工兵营地的人数都有可能发生变动,可能增加或减少若干人手,但这些都逃不过C国的监视。
中央情报局要研究敌人究竟演习什么战术,所以Tidy要随时向Derek汇报某一段连续的工兵营地一共有多少人,例如Derek问:“Tidy,马上汇报第3个营地到第10个营地共有多少人!”Tidy就要马上开始计算这一段的总人数并汇报。但敌兵营地的人数经常变动,而Derek每次询问的段都不一样,所以Tidy不得不每次都一个一个营地的去数,很快就精疲力尽了,Derek对Tidy的计算速度越来越不满:"你个死肥仔,算得这么慢,我炒你鱿鱼!”Tidy想:“你自己来算算看,这可真是一项累人的工作!我恨不得你炒我鱿鱼呢!”无奈之下,Tidy只好打电话向计算机专家Windbreaker求救,Windbreaker说:“死肥仔,叫你平时做多点acm题和看多点算法书,现在尝到苦果了吧!”Tidy说:"我知错了。。。"但Windbreaker已经挂掉电话了。Tidy很苦恼,这么算他真的会崩溃的,聪明的读者,你能写个程序帮他完成这项工作吗?不过如果你的程序效率不够高的话,Tidy还是会受到Derek的责骂的.
 

Input
第一行一个整数T,表示有T组数据。
每组数据第一行一个正整数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条命令
 

Output
对第i组数据,首先输出“Case i:”和回车,
对于每个Query询问,输出一个整数并回车,表示询问的段中的总人数,这个数保持在int以内。
 

Sample Input
   
   
   
   
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
 

Sample Output
   
   
   
   
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/

你可能感兴趣的:(线段树)