# Segment Tree 线段树

Source: Jiuzhang's Tutorial: https://www.jiuzhang.com/tutorial/segment-tree/

Segment Tree (a.k.a Interval Tree) is an advanced data structure which can support queries like:

• which of these intervals contain a given point
• which of these points are in a given interval

See wiki:

The structure of Segment Tree is a binary tree which each node has two attributes start and end denote an segment / interval.

start and end are both integers, they should be assigned in following rules:

• The root's start and end is given by build method.
• The left child of node A has start=A.left, end=(A.left + A.right) / 2.
• The right child of node A has start=(A.left + A.right) / 2 + 1, end=A.right. if start equals to end, there will be no children for this node.
• Implement a build method with two parameters start and end, so that we can create a corresponding segment tree with every node has the correct start and end value, return the root of this segment tree.

Example
Given start=0, end=3. The segment tree will be:

``````               [0,  3]
/        \
[0,  1]           [2, 3]
/     \           /     \
[0, 0]  [1, 1]     [2, 2]  [3, 3]
``````

Given start=1, end=6. The segment tree will be:

``````               [1,  6]
/        \
[1,  3]           [4,  6]
/     \           /     \
[1, 2]  [3,3]     [4, 5]   [6,6]
/    \           /     \
[1,1]   [2,2]     [4,4]   [5,5]
``````

## 2. 线段树的结构

### 第一节：什么是线段树，它长什么样？

``````          [0,3]
/     \
[0,1]       [2,3]
/   \       /   \
[0,0] [1,1] [2,2] [3,3]
``````

``````            [0,3]
(val=4)
/         \
[0,1]         [2,3]
(val=4)       (val=3)
/    \         /    \
[0,0]  [1,1]   [2,2]  [3,3]
(val=1)(val=4) (val=2)(val=3)
``````

``````    n + 1/2 * n + 1/4 * n + 1/8 * n + ...
=   (1 + 1/2 + 1/4 + 1/8 + ...) * n
=   2n
``````

`所以构造线段树的时间复杂度和空间复杂度都为O(n)`

### 第二节：线段树中的Node如何定义

``````// 节点区间定义
// [start, end] 代表节点的区间范围
// max 是节点在(start,end)区间上的最大值
// left , right 是当前节点区间划分之后的左右节点区间
public class SegmentTreeNode {
public int start, end, max;
public SegmentTreeNode left, right;
public SegmentTreeNode(int start, int end, int max) {
this.start = start;
this.end = end;
this.max = max
this.left = this.right = null;
}
}
``````

## 3. 线段树区间最大值维护

``````            [0,3]
(val=4)
/         \
[0,1]         [2,3]
(val=4)       (val=3)
/    \         /    \
[0,0]  [1,1]   [2,2]  [3,3]
(val=1)(val=4) (val=2)(val=3)
``````

#### Build Segment Tree

build()

``````// 构造的代码及注释
public SegmentTreeNode build(int[] A) {
return buildhelper(0, A.length - 1, A);
}
``````

buildHelper()

``````public SegmentTreeNode buildhelper(int left, int right, int[] A){
if(left > right){
return null;
}
SegmentTreeNode root = new SegmentTreeNode(left, right, A[left]); // 根据节点区间的左边界的序列值为节点赋初值
if(left == right){
return root; // 如果左边界和右边界相等,节点左边界的序列值就是线段树节点的接节点值
}
int mid = (left + right) / 2; // 划分当前区间的左右区间
root.left = buildhelper(left, mid, A);
root.right = buildhelper(mid + 1, right, A);
root.max = Math.max(root.left.max, root.right.max); // 根据节点区间的左右区间的节点值得到当前节点的节点值
return root;
}
``````

`root.min = Math.min(root.left.min, root.right.min);`

`root.sum = root.left.sum + root.right.sum;`

## 4.线段树的区间查询

### 4.1. 如何更好的查询Query

``````            [0,3]
(val=4)
/         \
[0,1]         [2,3]
(val=4)       (val=3)
/    \         /    \
[0,0]  [1,1]   [2,2]  [3,3]
(val=1)(val=4) (val=2)(val=3)
``````

`query(1,3) = max(query(1,1), query(2,3)) = max(4,3) = 4`

### 4.2. 如何拆分区间变成线段树上有的小区间：

``````// 区间查询的代码及注释
public int query(TreeNode root, int start, int end) {
if (start <= root.start && root.end <= end) {
// 如果查询区间在当前节点的区间之内,直接输出结果
return root.max;
}
int mid = (root.start + root.end) / 2; // 将当前节点区间分割为左右2个区间的分割线
int ans = Integer.MIN_VALUE; // 给结果赋初值
if (mid >= start) {   // 如果查询区间和左边节点区间有交集,则寻找查询区间在左边区间上的最大值
ans = Math.max(ans, query(root.left, start, end));
}
if (mid + 1 <= end) { // 如果查询区间和右边节点区间有交集,则寻找查询区间在右边区间上的最大值
ans = Math.max(ans, query(root.right, start, end));
}
return ans; // 返回查询结果
}
``````

## 5. 线段树的单点更新

### 更新序列中的一个点

``````            [0,3]
(val=4)
/         \
[0,1]         [2,3]
(val=4)       (val=3)
/    \         /    \
[0,0]  [1,1]   [2,2]  [3,3]
(val=1)(val=4) (val=2)(val=3)
``````

`提问：为什么需要更新这三个区间？`：因为只有这三个在线段树中的区间，覆盖了3这个点。

``````            [0,3]
(val=5)
/         \
[0,1]         [2,3]
(val=4)       (val=5)
/    \         /    \
[0,0]  [1,1]   [2,2]  [3,3]
(val=1)(val=4) (val=2)(val=5)
``````

`如果我们继续把A[2]从2变成4，线段树又该如何更新呢？`

``````            [0,3]
(val=5)
/         \
[0,1]         [2,3]
(val=4)       (val=5)
/    \         /    \
[0,0]  [1,1]   [2,2]  [3,3]
(val=1)(val=4) (val=4)(val=5)
``````

`如果我们继续把A[1]从4变成3，线段树又该如何更新呢？`

``````            [0,3]
(val=5)
/         \
[0,1]         [2,3]
(val=3)       (val=5)
/    \         /    \
[0,0]  [1,1]   [2,2]  [3,3]
(val=1)(val=3) (val=4)(val=5)
``````

``````// 单点更新的代码及注释
public void modify(SegmentTreeNode root, int index, int value) {
if(root.start == root.end && root.start == index) { // 找到被改动的叶子节点
root.max = value; // 改变value值
return ;
}
int mid = (root.start + root.end) / 2; // 将当前节点区间分割为2个区间的分割线
if(index <= mid){ // 如果index在当前节点的左边
modify(root.left, index, value); // 递归操作
root.max = Math.max(root.right.max, root.left.max); // 可能对当前节点的影响
} else {            // 如果index在当前节点的右边
modify(root.right, index, value); // 递归操作
root.max = Math.max(root.left.max, root.right.max); // 可能对当前节点的影响
}
return ;
}
``````

## 6.总结 - 线段树问题解决的框架

• 如果问题带有区间操作，或者可以转化区间操作，可以尝试往线段树方向考虑

• 从面试官给的题目中抽象问题，将问题转化成一列区间操作，注意这步很关键
• 当我们分析出问题是一些列区间操作的时候，我们都可以采用线段树来解决这个问题

• 对区间的一个点的值进行修改
• 对区间的一段值进行统一的修改
• 询问区间的和
• 询问区间的最大值、最小值
• 套用我们前面讲到的经典步骤和写法，即可在面试中完美的解决这些题目！

##### 什么情况下，无法使用线段树？

2004 - 林涛：《线段树的应用》 - 林涛.pdf