数据结构(七)线段树

Posted by YaPi on August 25, 2019

线段树

平衡树

平衡树,即平衡二叉树(Balanced Binary Tree),具有以下性质:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。 平衡二叉树的常用算法有红黑树、AVL、Treap、伸展树、SBT等

线段树不是一个完全二叉树,但是是一颗平衡二叉树,如果把最下层空节点看成空的节点,那么它也是一颗满二叉树。

对于一颗满二叉树来说有以下特点

  • h层,一共有2^h - 1个节点(大约是2^h)
  • 最后一层 h-1层,有2^(h-1)个节点
  • 最后一层的节点数大致等于前面所有层节点之和

若我们想用一个数组去存储线段数,如果区间有n个元素,那么,如果线段树的节点数量n是2的整数倍,因为最后一层的节点数等于最后一层节点数减1,所以,就需要2n的数组空间才能存储下来,在最坏的情况下,如果n的数量为基数,那么就需要新加一层才能存储下来,所以需要4n的空间。

我们的线段树不考虑添加元素,即区间固定,使用4n的静态空间即可。会存在浪费

对于给定的区间支持相应的两种操作

  • 更新: 更新区间种的一个元素或者一个区间的值
  • 查询一个区间[i,j]的最大值,最小值或者区间数字和

不考虑添加元素和删除元素

实现

/*
@date : 2019/09/11
@author : YaPi
@desc :
*/
package tree

import "dtSt/common"

type Segment interface {
	// 输入两个节点a,b,根据业务需求返回合适的节点,如:
	// 若线段树是用于存储一段区间的最大值,那么就应该返回阿a 和 b中的最大值
	// 若线段树是用于存储一段区间的最小值,那么就应该返回阿a 和 b中的最小值
	// 根据具体的结果返回
	Merge(b Segment)Segment
	String()string
}


type SegmentTree struct {
	data		[]common.Segment
	tree 		[]common.Segment
}

func (s *SegmentTree) String() string {
	ss := "[ "
	for _,v := range s.tree{
		if v != nil {
			ss += " "+ v.String()
		}
	}
	ss += " ]"
	return ss
}

func NewSegmentTree(d []common.Segment) *SegmentTree {
	// 需要 4n的元素才能存储完长度为len的数组所生成的线段树
	tre := &SegmentTree{
		data: d,
		tree: make([]common.Segment,len(d) * 4)}

	tre.buildSegmentTree(0,0,len(d) - 1)
	return tre
}

func (s *SegmentTree)IsEmpty()bool{
	return len(s.data) == 0
}

func (s *SegmentTree)GetSize()int{
	return len(s.data)
}

func (s *SegmentTree)Get(index int)common.Segment{
	if index < 0 || index >= len(s.data){
		return nil
	}
	return s.data[index]
}

// 数组从0开始
// 父亲节点对应数组下标
func parent(n int)int  {
	if n == 0{
		return n
	}
	return (n-1)/2
}
// 左孩子节点对应数组下标
func leftChild(n int)int{
	return 2 * n +1
}
// 右孩子节点对应数组下标
func rightChild(n int)int{
	return 2 * n +2
}

// 构建线段树
// 在位置为i的地方构建区间为[l,r]的线段树
func (s *SegmentTree)buildSegmentTree(i,l,r int)  {
	// 说明已经不能再分了,就是一个单元素了
	if l == r{
		s.tree[i] = s.data[l]
		return
	}
	leftTressIndex := leftChild(i)
	rightTreeIndex := rightChild(i)
	// 不使用 (l + r) /2 避免越界
	mid := l + (r-l) / 2
	s.buildSegmentTree(leftTressIndex,l,mid)
	s.buildSegmentTree(rightTreeIndex,mid+1,r)

	s.tree[i] = s.tree[leftTressIndex].Merge(s.tree[rightTreeIndex])
}

func (s *SegmentTree)query(queryL,queryR int)common.Segment  {
	if queryL < 0 || queryR < 0 || queryL >=len(s.data) || queryR >= len(s.data){
		return nil
	}

	return s.queryInTree(0,0,len(s.data)-1,queryL,queryR)
}

// 在以treeIndex为根的,范围为l到r的线段树中查找区间为queryL,到queryR的数据
func (s *SegmentTree)queryInTree(treeIndex,l,r,queryL,queryR int)common.Segment  {
	if l == queryL && r == queryR{
		return s.tree[treeIndex]
	}
	mid := l + (r -l)/2
	leftTressIndex := leftChild(treeIndex)
	rightTreeIndex := rightChild(treeIndex)

	// 需要查找的和左子树没关系
	if queryL >= mid +1 {
		return s.queryInTree(rightTreeIndex,mid + 1,r,queryL,queryR)
	}else if queryR <= mid {
		// 右子树没关系 在左孩子中查找
		return s.queryInTree(leftTressIndex,l,mid,queryL,queryR)
	}else {
		// 意味着所关注的区间一部分在左孩子,一部分在右孩子

		leftResult := s.queryInTree(leftTressIndex,l,mid,queryL,mid)

		rightResult := s.queryInTree(rightTreeIndex,mid+1,r,mid+1,queryR)

		return leftResult.Merge(rightResult)
	}
}

func (s *SegmentTree)Set(index int,e common.Segment)  {

	if index <0 || index >= len(s.data){
		return
	}
	s.set(0,0, len(s.data)-1,index,e)
}


func (s *SegmentTree)set(treeIndex,l,r,index int,e common.Segment) {
	if l == r {
		s.tree[treeIndex] = e
		return
	}
	mid := l + (r -l)/2

	leftTreeIndex := leftChild(treeIndex)
	rightTreeIndex := rightChild(treeIndex)
	if index >= mid +1 {
		s.set(rightTreeIndex,mid + 1,r,index,e)
	}else {
		s.set(leftTreeIndex,l,mid,index,e)
	}
	s.tree[treeIndex] = s.tree[leftTreeIndex].Merge(s.tree[rightTreeIndex])
}

经典题目 区间染色

有一面墙,长度为n,每次选择一段儿墙进行染色,每一次染色过后,可以覆盖染色,在m次操作过后,我们可以看见多少种颜色?m次操作过后,我们可以在[i,j]这段种有多少种颜色

若使用数组,在某一段之间染色,将对应数组设置为对应颜色,那么染色,和查询的时间复杂度都是O(n)

经典题目 区间查询

查询一个区间[i,j]的最大值、最小值、和等。