参考视频:喵的编程课 https://www.bilibili.com/video/BV1yF411p7Bt
1.线段树解决的问题:
最大值最小值,加和
2.线段树的底层,一般用数组。
3.线段树的思想:小区间去更新大区间。
线段树是一种二叉树,广义上也被归类为二叉搜索树。
对于区间的修改,维护和查询时间复杂度优化为log级别。
逻辑结构:
线段树的维护,只需要用小区间的值,更新大区的值。
解决的问题需要满足:区间加法
区间加法 :对于[L,R]的区间,他的答案可以由 [L,M]和[M+1,R]的答案合并求出.
满足的问题:区间求和、区间最大最小值
不满足的问:区间的众数 、区间的最长连续问题 、最长不下降问题等
创建一个数组以二叉堆的方式存储数据,对应下标为i的数据,有下列性质:(i是数据下标,从1开始)
eg: 代表区间和问题的例子
原始数据:3 、1、 2 、4
[1,4]10
/ \
[1,2]4 [3,4]6
/ \ / \
[1]3 [2]1 [3]2 [4]4
(1)修改树列中下标为i的数据:
仅有单点修改的区间查询,不需要处理lazy标记:
如上图中 [1,3]区间求和
(1)[1,3] 包含[1,2] 区间,返回[1,2]
(2)[1,3] 和右儿子有交集,访问右儿子,从第一步开始。发展 [3] 包含再[1,3] 返回
(3) [4]不包含,放弃。
(4)最终返回 的所有区间 ([1,2]、[3])加合 4+2 = 6
如果对[2,4]中每个值加1
如题:已知一个数列,你需要进行下面两种操作:
原始数据:{1, 5, 4, 2, 3}
主体实现segtree.go
package segtree
type SegTree struct {
Date []int
Tree []SegNode
}
type SegNode struct {
L, R int //L和R表示区间范围
Date int
Lazy int
}
func (s *SegTree) NewSegTree(d []int) {
s.Date = []int{0}
s.Date = append(s.Date, d...)
s.Tree = make([]SegNode, len(d)*4+1)
s.Build(1, 1, len(d))
}
func (s *SegTree) Build(i, l, r int) {
s.Tree[i].L = l //L和R表示区间范围
s.Tree[i].R = r
if s.Tree[i].R == s.Tree[i].L { //如果是叶子结点保存数据
s.Tree[i].Date = s.Date[r]
} else {
mid := (l + r) / 2 //整除
s.Build(i*2, l, mid)
s.Build(i*2+1, mid+1, r) //左儿子和右儿子,递归思想
s.Tree[i].Date = s.Tree[i*2].Date + s.Tree[i*2+1].Date //父结点区间和 = 左儿子区间和 + 右儿子区间和。
}
}
//Change 区间修改
func (s *SegTree) Change(i, l, r int, add int) { // i == 1时表示从根节点开始搜索。
if s.Tree[i].R <= r && s.Tree[i].L >= l {
s.Tree[i].Date = s.Tree[i].Date + add * (s.Tree[i].R - s.Tree[i].L + 1)
s.Tree[i].Lazy = s.Tree[i].Lazy + add
} else {
if s.Tree[i].Lazy != 0 {
s.PushDown(i)
}
if s.Tree[i*2].R >= l {
s.Change(i*2, l, r, add)
}
if s.Tree[i*2+1].L <= r {
s.Change(i*2+1, l, r, add)
}
s.Tree[i].Date = s.Tree[i*2].Date + s.Tree[i*2+1].Date
}
}
//Ask 区间查询
func (s *SegTree) Ask(i, l, r int) int {
if s.Tree[i].R <= r && s.Tree[i].L >= l { //如果当前区间覆盖边界
return s.Tree[i].Date
}
if s.Tree[i].Lazy != 0 { //如果当前区间有懒标记,下传标记
s.PushDown(i)
}
t := 0 //用来合并询问到的值
if s.Tree[i*2].R >= l { //如果要询问的区间与左儿子有交集
t = t + s.Ask(i*2, l, r)
}
if s.Tree[i*2+1].L <= r { //如果要询问的区间与右儿子有交集
t = t + s.Ask(i*2+1, l, r)
}
return t
}
// PushDown 下传标记
func (s *SegTree) PushDown(i int) {
s.Tree[i*2].Lazy = s.Tree[i*2].Lazy + s.Tree[i].Lazy
s.Tree[i*2+1].Lazy = s.Tree[i*2+1].Lazy + s.Tree[i].Lazy
mid := (s.Tree[i].L + s.Tree[i].R) / 2
s.Tree[i*2].Date = s.Tree[i*2].Date + s.Tree[i].Lazy*(mid-s.Tree[i].L+1)
s.Tree[i*2+1].Date = s.Tree[i*2+1].Date + s.Tree[i].Lazy*(s.Tree[i].R-mid)
s.Tree[i].Lazy = 0 // 消除父亲的lazy标记。
}
单元测试segtree_test.go
package segtree
import (
"testing"
)
func TestSegTree(t *testing.T) {
var tree SegTree
tree.NewSegTree([]int{1, 5, 4, 2, 3})
res := tree.Ask(1, 2, 4)
if res != 11 {
t.Fail()
} else {
t.Log(res)
}
tree.Change(1, 2, 3, 2)
res = tree.Ask(1, 3, 4)
//fmt.Printf("%+v\n",tree.Tree)
if res != 8 {
t.Fail()
} else {
t.Log(res)
}
tree.Change(1, 1, 5, 1)
res = tree.Ask(1, 1, 4)
if res != 20 {
t.Fail()
} else {
t.Log(res)
}
}