Data Structure: Segment Tree 线段树

来源:互联网 发布:mac按键失灵 编辑:程序博客网 时间:2024/06/06 22:15

线段树主要用于Range Query. 适用问题在:这个大区域的某个子区间最小值,这个大区间的某个子区间的和等等。线段树的某个叶子节点都是代表一个大区间的元素。


为什么要使用线段树?


那先让我们看看,如果不使用线段树,上述的求某个子区间最小值的问题该如何解决。


方案1:每次query都去遍历一次子区间。如果有m次查询,有n个元素的话,时间复杂度就是O(mn)。 


方案2:设置一个matrix,x, y 两个方向都是区间的元素。(0, 3)代表从0到3的最小值。那么建立这么一个matrix需要O(n^2)的空间复杂度和时间复杂度。查询只需要O(1)。但是如果有update就十分麻烦。


线段树的优点在于:

O(n)的时间负责度和空间复杂度去建立。

O(logn)的查询复杂度。


首先,我们需要知道线段树的几个性质:


1)线段树是一棵完全二叉树;

2)基于性质1,线段树适合使用数组进行存储,因为中间空值不多;

3)存储线段树的数组长度 = ceil (大于原数组长度的2的次方数) * 2 - 1。譬如,原数组的长度是4,4刚好是2的2次方。所以长度 = 4 * 2 - 1 = 7. 但原数组长度是5,则存储数组长度是:8 * 2 - 1 = 15. 


所以,任何数组的线段树存储数组的长度都不超过4n,n是原来数组的长度。建立线段树数组时候,用上这个性质。


这里,我们稍微温习一下使用数组来存储完全二叉树的两个例子:1)堆 2)线段树


利用数组来存储树有两个很重要的性质:

1)当前节点index为i,其两个孩子的index为:2 * i + 1(左), 2 * i + 2(右)

2) 反之,父节点的index为:(i - 1) / 2,截尾得到int. 


线段树的建立逻辑和查询逻辑都是依据线段树的实际意义建立的。线段树的每个节点代表一个区间,叶子节点的区间是左右两个值相等,代表一个元素。父节点代表两个子节点区间的并集。直到根节点代表整个区间。


利用上述实际意义,建立线段树的逻辑是:

1)递归建立;

2)终止条件:区间左右两个值相等,直接得到当前局部最优解

3)否则,二分递归;

4)得到最小情况后,回归得到当前局部最优解(类似动归的思想)


查询的逻辑在代码中有所体现:


Totally match:当前的节点代表的区间 [start, end] 满足 qStart <= start && end <= qEnd,返回当前节点的值。

No match: start > qEnd || end < qStart

Partical match:则是不满足上述两种情况。


代码如下:


public class Solution {// range minimum query// only the current root is relative to segment tree.public void createSeg(int[] array, int[] seg, int start, int end, int curRoot) {if (start == end) {seg[curRoot] = array[start];// leavesreturn;}int mid = start + (end - start) / 2;createSeg(array, seg, start, mid, 2 * curRoot + 1);createSeg(array, seg, mid + 1, end, 2 * curRoot + 2);seg[curRoot] = Math.min(seg[2 * curRoot + 1], seg[2 * curRoot + 2]);}public int rangeQuery(int[] seg, int qStart, int qEnd, int start, int end, int curRoot) {// think about 3 kinds // 1) Total match. Current [start, end] is total match qStart and qEnd or is totally a subset of qStart and qEnd. Return its value. // 2) Not match. None of elements of current [start, end] is in [qStart, qEnd], return INT_MAXVALUE// 3) Partial match. Some of current [start, end] are in [qStart, qEnd] while the others are not. Continue recursion to its children. if (start >= qStart && end <= qEnd) {return seg[curRoot];}if (start > qEnd || end < qStart) {return Integer.MAX_VALUE;}int mid = start + (end - start) / 2;return Math.min(rangeQuery(seg, qStart, qEnd, start, mid, 2 * curRoot + 1), rangeQuery(seg, qStart, qEnd, mid + 1, end, 2 * curRoot + 2));}public static void main(String[] args) {int[] array = new int[]{-1,1,2,4,-2,6};Solution ins = new Solution();int n = array.length;// Maximum length of seg = 4*n;int[] seg = new int[4 * n];ins.createSeg(array, seg, 0, 5, 0);System.out.println(ins.rangeQuery(seg, 0, 3, 0, 5, 0));}}


复杂度分析:

* Time complexity to create segment tree is O(n) since new array will be at max 4n size
* Space complexity to create segment tree is O(n) since new array will be at max 4n size
* Time complexity to search in segment tree is O(logn) since you would at max travel 4 depths
* Time complexity to update in segment tree is O(logn)






原创粉丝点击