Data Structures - Segment Tree

学习了下一种新的数据结构,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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,185评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,652评论 3 393
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,524评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,339评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,387评论 6 391
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,287评论 1 301
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,130评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,985评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,420评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,617评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,779评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,477评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,088评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,716评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,857评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,876评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,700评论 2 354

推荐阅读更多精彩内容

  • 背景 一年多以前我在知乎上答了有关LeetCode的问题, 分享了一些自己做题目的经验。 张土汪:刷leetcod...
    土汪阅读 12,744评论 0 33
  • 文JIE胭脂雪 【一念天堂地狱】目录 欢迎点击 【上一章】一念天堂地狱9——婆婆去世 谢小文在焦急中往家赶,摔的浑...
    JIE胭脂雪阅读 261评论 3 2
  • 2106年就这样平淡无奇地过去了,馒头看着窗外灰蒙蒙的天想着,今年主人们没有出门,哦,去年的元旦他们好像也没有出门...
    馒小头阅读 323评论 4 3
  • 文/大白(微信:pptspeech) 「读书无用论」,曾一度甚嚣尘上。我也知道,但没与之正面交锋。今天,却被我碰上...
    PPT演讲师大白阅读 249评论 0 0
  • 每次读到某个牛人或者大咖的简历时,4点起床写作,跑步游泳每天必不可少,奶娃度假滑雪登山摄影各种十八般爱好样样玩的转...
    小际阅读 356评论 1 4