Data Structure: Segment Tree 线段树

线段树主要用于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];// leaves
			return;
		}
		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)






评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值