线段树主要用于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)