12.线段树

参考视频:喵的编程课 https://www.bilibili.com/video/BV1yF411p7Bt

1.线段树解决的问题:

最大值最小值,加和

2.线段树的底层,一般用数组。

3.线段树的思想:小区间去更新大区间。

一、什么是线段树

  1. 线段树是一种二叉树,广义上也被归类为二叉搜索树。

  2. 对于区间的修改,维护和查询时间复杂度优化为log级别。

  3. 逻辑结构:

    1. 对于一个区间,平均划分为2个区间,两个再划分为两个,依次类推,一直到区间中只有一个元素。
    2. 只有一个元素的,的区间是叶子结点。
    3. 叶子结点存放,真正的元素。有多个元素的区间,可以表示,这个区间的综合属性如:最大值、最小值、区间的和等。
  4. 线段树的维护,只需要用小区间的值,更新大区的值。

  5. 解决的问题需要满足:区间加法

    区间加法 :对于[L,R]的区间,他的答案可以由 [L,M]和[M+1,R]的答案合并求出.

    满足的问题:区间求和、区间最大最小值

    不满足的问:区间的众数 、区间的最长连续问题 、最长不下降问题等

二、线段树解决问题的一般步骤

  1. 建树
  2. 单点修改、区间修改
    • 难点:区间修改后的查询 ,一般会用到lazy标记
  3. 区间查询

三、步骤详解

1.建树

创建一个数组以二叉堆的方式存储数据,对应下标为i的数据,有下列性质:(i是数据下标,从1开始)

  1. i 的左儿子下标:是 2i (如果下标从0开始 2i +1)
  2. i 的右儿子下标: 2i+1(如果下标从0开始 2i+2 )
  3. i 的父结点下标: [i/2] (如果下标从0开始 [i-1/2] , []代表向下取整,整数默认向0取整)
  4. 限度树的数组,一般要申请到 4*n 才能保证,不会出现越界访问。

eg: 代表区间和问题的例子

原始数据:3 、1、 2 、4
					[1,4]10 
				   /       \
             [1,2]4        [3,4]6
             /    \       /     \
          [1]3   [2]1   [3]2     [4]4


2.单点修改

(1)修改树列中下标为i的数据:

  1. 如果当前结点的做儿子的区间[L,R]包含了i(L<=i<=R),就访问左儿子,否则就访问右儿子。
  2. 一直到L=R也就是搜索到了只包含这个数据的结点就可以修改它。
  3. 最后,不要忘记把包含此数据的大区间的值更新。

仅有单点修改的区间查询,不需要处理lazy标记:

3.区间查询

  1. 如果要查询的区间完全覆盖当前区间,直接返回当前区间的值
  2. 如果查询区间和左儿子有交集,搜索左儿子
  3. 如果查询区间和右儿子有交集就搜索右儿子
  4. 最后处理两边查询的数据。
如上图中 [1,3]区间求和
(1)[1,3] 包含[1,2] 区间,返回[1,2]
(2)[1,3] 和右儿子有交集,访问右儿子,从第一步开始。发展 [3] 包含再[1,3] 返回
(3) [4]不包含,放弃。
(4)最终返回 的所有区间 ([1,2]、[3])加合 4+2 = 6

4.区间修改

  1. 覆盖lazy:如果要修改的区间覆盖当前区间直接更新这个区间,打上lazy标记
  2. 下传清除:如果没有完全覆盖,且当前区间有lazy标记,下传lazy标记到子区间,再清除当前区间的lazy标记
    (多次修改后,查询时,才会用到)
  3. 左/右
    1. 如果修改区间和左儿子有交集,搜索左儿子。
    2. 如果修改区间和右儿子有交集搜索右儿子。
  4. 更新:最后当前区间的值更新到父结点。
如果对[2,4]中每个值加1

四、线段树例题和编程实现

如题:已知一个数列,你需要进行下面两种操作:

  1. 将某区间每一个数加上k
  2. 求出某区间所有数的和

原始数据:{1, 5, 4, 2, 3}

  1. 获取 2~4 的和
  2. 2~3 加 2后,获取 3~4的和
  3. 1~5 加1后,获取 1~4的和

主体实现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)
	}
}

你可能感兴趣的:(数据结构系列,数据结构,算法)