今天写了一些题目,做的全是线段树的题。但我打算明天复习树状数组,对可以用好多种做法做的题目打算按照线段树写一遍,再用树状数组写一遍。
基本上来说线段树是来做区间修改和查询的。在有修改的情况下,动态查询一个区间内的最大值、最小值或者和一类的指标,这种情况下我们可以用线段树。比方说一个求区间和的线段树,我们会定义一个类,叫SegmentTreeNode,然后有三个方法,buildTree、updateTree和querySum。代码模板如下:
class SegmentTreeNode {
public int start, end, sum;
public SegmentTreeNode left, right;
public SegmentTreeNode(int start, int end, int sum, SegmentTreeNode left, SegmentTreeNode right) {
this.start = start;
this.end = end;
this.sum = sum;
this.left = left;
this.right = right;
}
}
private SegmentTreeNode buildTree(int start, int end, int[] nums) {
if (start == end) {
return new SegmentTreeNode(start, end, nums[start], null, null);
}
int mid = start + (end - start) / 2;
SegmentTreeNode left = buildTree(start, mid, nums);
SegmentTreeNode right = buildTree(mid + 1, end, nums);
return new SegmentTreeNode(start, end, left.sum + right.sum, left, right);
}
private void updateTree(SegmentTreeNode root, int index, int val) {
if (root.start == index && root.end == index) {
root.sum = val;
return;
}
int mid = root.start + (root.end - root.start) / 2;
if (index <= mid) {
updateTree(root.left, index, val);
}
else {
updateTree(root.right, index, val);
}
root.sum = root.left.sum + root.right.sum;
}
private int querySum(SegmentTreeNode root, int i, int j) {
if (root.start == i && root.end == j) {
return root.sum;
}
int mid = root.start + (root.end - root.start) / 2;
if (j <= mid) {
return querySum(root.left, i, j);
}
else if (i > mid) {
return querySum(root.right, i, j);
}
return querySum(root.left, i, mid) + querySum(root.right, mid + 1, j);
}
从上述模板我们也可以看出一些线段树的使用场景。比方说buildTree,是一次性建完这棵树的,后面或许对于树上节点的值进行修改,但树的结构从建出来之后就再也没有改过。也就是说如果在某道题中,你需要动态增删树的一些节点,那应该是不能用线段树。 在很多时候,如果跟区间修改查询相关,可以往这个方向想。如果这样的话最需要考虑的东西就是“以什么建树”和“树上携带什么信息”。当然上面这个题目是以输入数组建树的,但实质上应该去思考当你要做区间查询的时候,所谓区间的每个点代表什么。上面这个题目是以下标为查询的key的,所以树也是按照下标来做查询的。那么如果有些题目要查询某个范围内的值,那就要用“值”来建树。