线段树【总结】

 

目录

一、线段树的概论

二、线段树的性质

三、线段树的建树

四、线段树的单点修改

五、线段树的区间查询

六、线段树的区间修改

七、代码实现


一、线段树的概论

假设有编号从1到n的n个点,每个点都存了一些信息,用[L,R]表示下标从L到R的这些点。
线段树的用处就是,对编号连续的一些点进行修改或者统计操作,修改和统计的复杂度都是O(log2(n)).

线段树的原理,就是,将[1,n]分解成若干特定的子区间,然后,将每个区间[L,R]都分解为少量特定的子区间,通过对这些少量子区间的修改或者统计,来实现快速对[L,R]的修改或者统计。

由此看出,用线段树统计的东西,必须符合区间加法,否则不可能通过分成的子区间来得到[L,R]的统计结果。

符合区间加法的例子:
数字之和——总数字之和 = 左区间数字之和 + 右区间数字之和
最大值——总最大值=max(左区间最大值,右区间最大值)
不符合区间加法的例子:
众数——只知道左右区间的众数,没法求总区间的众数

二、线段树的性质

如果观察一下线段树(无奈这里木有图),会发现除去线段树的最后一层,整颗线段树是一颗完全二叉树,树深为O(logN),因此我们可以用“父子二倍”标号方法:

1.根节点编号为1

2.编号为x的节点的左子节点编号为x*2,右子节点编号为x*2+1

三、线段树的建树

线段树的基本用途是对序列进行维护,支持查询与修改指令。给定一个长度为N的序列A,我么可以在序列[1,N]上建立一颗线段树,每个叶节点[i,]保存A[i]的值。线段树的二叉树结构可以很方便地从下往上传递信息。以区间最大值为例,d(l,r)等于max{A[i]},显然d(l,mid)=max(d(l,mid),d(mid+1,r))

下面的代码建立了线段树并保留了最大值:

四、线段树的单点修改

单点修改是一条形如“C x v"的指令,表示把A[x]的值修改为v

在线段树中,根节点(编号为1的节点)是执行各种指令的入口,我们得从根节点入手,递归找到[x,x]叶节点,然后从下往上更新[x,x],以及所有祖先节点上保留的信息

时间复杂度为O(logN)

五、线段树的区间查询

区间查询是一条形如”Q l r"的指令,例如查询序列A在区间[l,r]上的最大值,即max{A[i]}.我们只需要从根节点开始

1.若“l,r"完全覆盖了当前节点代表的空间,则立即回溯,并且将该节点的值为候选答案

2.若左子节点与[l,r]有重叠部分,则递归左子节点

3.若右子节点与[l,r]有重叠部分,则递归右子节点

六、线段树的区间修改

线段树的区间修改也是将区间分成子区间,但是要加一个标记,称作懒惰标记(lazy_tag)
标记的含义:本节点的统计信息已经根据标记更新过了,但是本节点的子节点仍需要进行更新。
即,如果要给一个区间的所有值都加上k,那么,实际上并没有给这个区间的所有值都加上k,而是打个标记,记下来,这个节点所包含的区间需要加k.打上标记后,要根据标记更新本节点的统计信息,比如,如果本节点维护的是区间和,而本节点包含5个数,那么,打上+k的标记之后,要给本节点维护的和+5k。

七、代码实现

(1)定义

#define N 100005
int A[N],n,N;//原数组
int Sum[N<<2];
int Add[N<<2];//懒惰标记 

(2)建树

void PushUp(int rt){Sum[rt]=Sum[rt<<1]+Sum[rt<<1|1];} 
void Build(int l,int r,int rt)
{ //l,r表示当前节点区间,rt表示当前节点编号
	if(l==r) 
    {//若到达叶节点 
		Sum[rt]=A[l];//储存数组值 
		return;
	}
	int m=(l+r)>>1;
	//左右递归 
	Build(l,m,rt<<1);
	Build(m+1,r,rt<<1|1);
	//更新 
	PushUp(rt);
}

(3)点修改

void Update(int L,int C,int l,int r,int rt)
{//l,r表示当前节点区间,rt表示当前节点编号
	if(l==r)
    {//到叶节点,修改即可 
		Sum[rt]+=C;
		return;
	}
	int m=(l+r)>>1;
	//根据条件判断往左子树调用还是往右 
	if(L <= m) Update(L,C,l,m,rt<<1);
	else Update(L,C,m+1,r,rt<<1|1);
	PushUp(rt);//子节点更新了,本节点也需要更新信息 
} 

(4)下推标记

void PushDown(int rt,int ln,int rn)
{
	//ln,rn为左子树,右子树的数字数量。 
	if(Add[rt])
    {
		//下推标记 
		Add[rt<<1]+=Add[rt];
		Add[rt<<1|1]+=Add[rt];
		//修改子节点的Sum使之与对应的Add相对应 
		Sum[rt<<1]+=Add[rt]*ln;
		Sum[rt<<1|1]+=Add[rt]*rn;
		//清除标记 
		Add[rt]=0;
	}
}

(5)区间修改

void Update(int L,int R,int C,int l,int r,int rt)
{//L,R表示操作区间,l,r表示当前节点区间,rt表示当前节点编号 
	if(L <= l && r <= R)
    {//如果本区间完全在操作区间[L,R]以内 
		Sum[rt]+=C*(r-l+1);//更新
		Add[rt]+=C;//增加懒惰标记,表示本区间的Sum正确,子区间的Sum仍需要根据懒惰的值来调整
		return; 
	}
	int m=(l+r)>>1;
	PushDown(rt,m-l+1,r-m);//下推标记
	//判断左右子树跟[L,R]有无交集 
	if(L <= m) Update(L,R,C,l,m,rt<<1);
	if(R >  m) Update(L,R,C,m+1,r,rt<<1|1); 
	PushUp(rt);//更新
} 

(6)区间查询

int Query(int L,int R,int l,int r,int rt)
{//L,R表示操作区间,l,r表示当前节点区间,rt表示当前节点编号
	if(L <= l && r <= R)
    {
		//在区间内
		return Sum[rt];
	}
	int m=(l+r)>>1;
	//下推标记
	PushDown(rt,m-l+1,r-m); 
	int ANS=0;
	if(L <= m) ANS+=Query(L,R,l,m,rt<<1);
	if(R > m) ANS+=Query(L,R,m+1,r,rt<<1|1);
	return ANS;
} 

(7)函数调用

//建树 
Build(1,n,1); 
//点修改
Update(L,C,1,n,1);
//区间修改 
Update(L,R,C,1,n,1);
//区间查询 
int ANS=Query(L,R,1,n,1);

 

你可能感兴趣的:(数据结构,线段树,总结,数据结构,模板)