一、线段树简介
线段树是一种平衡二叉搜索树(完全二叉树),它将一个线段区间划分成一些单元区间。对于线段树中的每一个非叶子节点,表示区间[a, b]的和,它的左孩子表示的区间为[a,(a+b)/2],右孩子表示的区间为[(a+b)/2+1,b],最后的叶子节点数目为N,与数组下标对应。线段树一般包括建立、查询、插入、更新等操作,建立规模为N的时间复杂度是 O(NlogN),其他操作时间复杂度为O(logN) 。
线段树能够解决什么样的问题:
线段树的适用范围很广,可以在线维护修改以及查询区间上的最值,求和。更可以扩充到二维线段树(矩阵树)和三维线段树(空间树)。对于一维线段树来说,每次更新以及查询的时间复杂度为O(logN)。
二、累加值线段树的结构
由于线段树是完全二叉树,线段树可以使用数组保存,对二叉树进行层次遍历。
假设某个节点下标为i, 它的左孩子下标为2i+1, 右孩子下标为2i+2。如数组 nums = [0, 1, 3, 4, 6] 存储为线段树数组values = [14, 4, 10, 1, 3, 4, 6, 0, 1]。
三、累加值线段树的构造
public class SegmentTree {
int[] value, nums;
int n;
public SegmentTree(int n, int[] nums) {
value = new int[4 * n];
this.n = n;
this.nums = nums;
build(0, 0, n - 1);
}
public void build(int pos, int left, int right) {
if (left == right) {value[pos] = nums[left];return;}
int mid = (left + right) / 2;
build(2 * pos + 1, left, mid);
build(2 * pos + 2, mid + 1, right);
value[pos] = value[2 * pos + 1] + value[2 * pos + 2];
}
}
四、累加值线段树的求和
public class SegmentTree {
int[] value, nums;
int n;
public SegmentTree(int n, int[] nums) {
value = new int[4 * n];
this.n = n;
this.nums = nums;
build(0, 0, n - 1);
}
// value[pos] 存储着nums[left,right]的和 比如query(0,0,4,1,4)
public int query(int pos, int left, int right, int qleft, int qright) {
if (qleft > right || qright < left) return 0;// 查询区间在当前区间之外
if (qleft <= left && qright >= right) return value[pos];// 查询区间完全包含当前区间
int mid = (left + right) / 2;
int leftSum = query_sum(2 * pos + 1, left, mid, qleft, qright);// 查询左子树
int rightSum = query_sum(2 * pos + 2, mid + 1, right, qleft, qright);// 查询右子树
return leftSum + rightSum;
}
}
五、累加值线段树的更新
public class SegmentTree {
int[] value, nums;
int n;
public SegmentTree(int n, int[] nums) {
value = new int[4 * n];
this.n = n;
this.nums = nums;
build(0, 0, n - 1);
}
// 在nums[left,right]范围内更新index的值为newValue
public void update(int pos, int left, int right, int index, int newValue) {
if (left == right && left == index) {
value[pos] = newValue;return;
}
int mid = (left + right) / 2;
if (index <= mid) {
update(2 * pos + 1, left, mid, index, newValue);
} else {
update(2 * pos + 2, mid + 1, right, index, newValue);
}
value[pos] = value[pos * 2 + 1] + value[pos * 2 + 2]
}
}
六、最小值线段树构建
class SegTree { // 定义线段树,采用数组存储
int[] value, nums;
int n;// 表示nums数组的长度
SegTree(int n, int[] nums) {
value = new int[4 * n];this.n = n;this.nums = nums;
build(1, 0, n - 1);//注意bulid pos的初值是1 后序的坐标也要跟着变
}
// pos表示 nums[left,right]最小值坐标在value中存储的位置
public void build(int pos, int left, int right) {
if (right == left) {value[pos] = left;return;}// 注意存储的是坐标
build(2 * pos, left, (left + right) / 2);// 建立左子树
build(2 * pos + 1, (right + left) / 2 + 1, right);// 建立右子树
int leftIndex=value[2 * pos],rightIndex=value[2 * pos + 1];
value[pos] = nums[leftIndex] > nums[rightIndex] ? rightIndex : leftIndex;
}
// pos 为nums[left,right]最小值的坐标在value中存储的位置
int query(int pos, int qleft, int qright, int left, int right) {// 递归的找到给定区间的最小值
if (left > right) return -1;
if (qright == right && qleft == left) return value[pos];// 递归出口
int mid = (qright + qleft) / 2;
int leftIndex = query(2 * pos, qleft, mid, left, Math.min(mid, right));//左侧区间最小值的坐标
int rightIndex = query(2 * pos + 1, mid + 1, qright, Math.max(left, mid + 1), right);
if (leftIndex == -1) return rightIndex;
if (rightIndex == -1) return leftIndex;
return nums[leftIndex] > nums[rightIndex] ? rightIndex : leftIndex;
}