欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Data Structures - Segment Tree

程序员文章站 2024-03-17 23:37:10
...

学习了下一种新的数据结构,segment tree
主要看这篇文章:
http://www.geeksforgeeks.org/segment-tree-set-1-sum-of-given-range/

看完之后就基本清楚了。
说说他的应用场景。
假设给你一个array,有两个函数,
一个是,
int sum(int i, int j) 给出 index i-j 的sum

void update(int i, int value) 更新index的值为 value

我的做法是:
建立一个新的数组,
sums[i] = sums[0] + sums[1] + ... + sums[i]
那么
sum(i, j) = sums[j] - sums[i - 1];
当然还要考虑一些Corner case

这个时候, sum 函数时间复杂度是就是O(1),空间复杂度是O(n)

update 呢?
我需要把 涉及到 index 的所有 sums 元素都更新下,
范围是 : [0, index] in sums[]

时间复杂度是 O(n)

如果用segment tree 来做这道题目,
空间复杂度是 O(n)
sum 时间复杂度是 O(log n)
update 时间复杂度是 O(log n)

My code:


public class SegmentTree {
    private int[] nums;
    private int[] st;
    public void initialize(int[] nums) {
        this.nums = nums;
        int n = nums.length;
        int level = (int ) Math.ceil(Math.log(n) / Math.log(2));
        int len = 2 * (int) Math.pow(2, level) + 1;
        st = new int[len];
        buildST(0, 0, nums.length - 1, st, nums);
    }
    public int getSum(int begin, int end) {
        return getSum(0, begin, end, 0, nums.length - 1);
    }
    
    public void update(int index, int value) {
        int diff = value - nums[index];
        nums[index] = value;
        update(0, diff, index, 0, nums.length - 1);
    }
    
    private int getSum(int index, int qs, int qe, int ss, int se) {
        if (qs <= ss && qe >= se) {
            return st[index];
        }
        else if (qs > se || qe < ss) {
            return 0;
        }
        else {
            int mid = ss + (se - ss) / 2;
            return getSum(2 * index + 1, qs, qe, ss, mid) + getSum(2 * index + 2, qs, qe, mid + 1, se);
        }
    }
    
    private void update(int index, int diff, int i, int ss, int se) {
        if (i < ss || i > se) {
            return;
        }
        else if (ss == se) {
            st[index] += diff;
        }
        else {
            st[index] += diff;
            int mid = ss + (se - ss) / 2;
            update(2 * index + 1, diff, i, ss, mid);
            update(2 * index + 2, diff, i, mid + 1, se);
        }
    }
    
    private int buildST(int index, int begin, int end, int[] st, int[] nums) {
        int mid = begin + (end - begin) / 2;
        if (begin == end) {
            st[index] = nums[begin];
        }
        else {
            st[index] = buildST(2 * index + 1, begin, mid, st, nums) + buildST(2 * index + 2, mid + 1, end, st, nums);
        }
        return st[index];
    }
    
    public static void main(String[] args) {
        int[] nums = new int[]{1,3,5,7,9,11};
        SegmentTree test = new SegmentTree();
        test.initialize(nums);
        int ret = test.getSum(2, 3);
        System.out.println(ret);
        test.update(2, 6);
        ret = test.getSum(2, 3);
        System.out.println(ret);
    }
    
}

不得不说,完整写出这个方法还是很麻烦的。
因为首先你需要构建一个segment tree
然后sum然后update
这些都需要 recursion

整个过程还是很麻烦的。
首先,st的长度 len
int level = (int) (Math.ceiling(Math.log(n) / Math.log(2)));
int len = 2 * (int) Math.pow(2, level) - 1;
int[] st = new int[len];

然后就是用一些divide and conquer的思想来完成sum and update

然后是segment tree 的另外一个应用,求
range minimum query

给定一个数组 nums[]
求 [i, j] 间的最小数。
可以遍历,复杂度是O(n)
如果用segment tree,
同样, query: time O(log n)
update: time O(log n)
参考网址:
http://www.geeksforgeeks.org/segment-tree-set-1-range-minimum-query/

代码如下:
My code:


public class SegmentTree_RMQ {
    private int[] nums;
    private int[] st;
    public void initialize(int[] nums) {
        this.nums = nums;
        int n = nums.length;
        int level = (int ) Math.ceil(Math.log(n) / Math.log(2));
        int len = 2 * (int) Math.pow(2, level) + 1;
        st = new int[len];
        buildST(0, 0, nums.length - 1, st, nums);
    }
    public int getMin(int begin, int end) {
        return getMin(0, begin, end, 0, nums.length - 1);
    }
    
    public void update(int index, int value) {
        int diff = value - nums[index];
        nums[index] = value;
        update(0, diff, index, 0, nums.length - 1);
    }
    
    private int getMin(int index, int qs, int qe, int ss, int se) {
        if (qs <= ss && qe >= se) {
            return st[index];
        }
        else if (qs > se || qe < ss) {
            return Integer.MAX_VALUE;
        }
        else {
            int mid = ss + (se - ss) / 2;
            return Math.min(getMin(2 * index + 1, qs, qe, ss, mid), getMin(2 * index + 2, qs, qe, mid + 1, se));
        }
    }
    
    private int update(int index, int diff, int i, int ss, int se) {
        if (i < ss || i > se) {
            return 0;
        }
        else if (ss == se) {
            st[index] += diff;
            return st[index];
        }
        else {
            int mid = ss + (se - ss) / 2;
            return Math.min(update(2 * index + 1, diff, i, ss, mid), update(2 * index + 2, diff, i, mid + 1, se));
        }
    }
    
    private int buildST(int index, int begin, int end, int[] st, int[] nums) {
        int mid = begin + (end - begin) / 2;
        if (begin == end) {
            st[index] = nums[begin];
        }
        else {
            st[index] = Math.min(buildST(2 * index + 1, begin, mid, st, nums), buildST(2 * index + 2, mid + 1, end, st, nums));
        }
        return st[index];
    }
    
    public static void main(String[] args) {
        int[] nums = new int[]{1,3,5,7,9,11};
        SegmentTree_RMQ test = new SegmentTree_RMQ();
        test.initialize(nums);
        int ret = test.getMin(2, 3);
        System.out.println(ret);
        test.update(2, 9);
        ret = test.getMin(2, 3);
        System.out.println(ret);
    }
    
}

其实掌握了segment tree思想后,这两道题目都差不多了。
那么,segment tree适用于哪种情形呢?
适用于在array中,给定一个范围,求sum,求最小值,求最大值,等等。
而且,求值操作很多,update操作也很多。这个时候用 segment tree是最理想的。如果update并不多,那完全可以用一些牺牲空间的方法来做。

记得有个类型题目,是什么 range query,应该都可以用segment tree来做。马上都写一下。

暂且总结到这里吧。

Anyway, Good luck, Richardo! -- 09/04/2016