线段树算法入门

什么是线段树

线段树是一种二叉搜索树,借助分治算法思想,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点。 -----------------改编自百度百科
在这里插入图片描述

线段树的作用

问题:给你 n n n个无序数字, q q q次操作,询问某段区间 [ l , r ] [l, r] [l,r]的和或是修改这段区间的值。假定 1 < = n , q < = 100 , 000 1 <= n, q <= 100,000 1<=n,q<=100,000,给你的时间限制是 1 s 1s 1s
如果使用暴力?假设每次修改、查询的区间都是 [ 1 , n ] [1, n] [1,n],则最坏时间复杂度需要 O ( q ∗ n ) O(q*n) O(qn),显然会超时。
这个时候就需要借助神奇的线段树。

观察它的结构:
在这里插入图片描述

建树:

观察其节点下标规律,可得出当前节点p的两个儿子节点下标分别为 p ∗ 2 p*2 p2 p ∗ 2 + 1 p*2 +1 p2+1
利用递归建树,与归并排序类似。时间复杂度 O ( n ∗ l o g 2 ( n ) ) O (n*log2(n)) O(nlog2(n))

void build(int l, int r, int p){
	if(l ==r){
		tree [p] = a[l]; //叶子节点信息
		return ;
	}
	int mid = (l + r) >> 1;	// 相当于mid = (l + r) / 2,
	build(l, mid, p << 1); //往左区间
	build(mid + i, r, p << 1|1); //往右区间, p << 1 | 1 相当于 p / 2 + 1
	tree[p] = tree[p << 1] + tree[p << 1|1]; //将儿子节点信息上传
}

查询区间[L,R]的信息:

假设每次访问到节点 p p p存的是区间 [ l , r ] [l, r] [l,r]的信息, m i d = ( l + r ) / 2 mid = (l + r)/ 2 mid=(l+r)/2
如果 [ l , r ] [l, r] [l,r]刚好被 [ L , R ] [L,R] [L,R]包含(即 L ≤ l L≤l Ll && r ≤ R ) r ≤ R) rR),则说明 p p p节点的信息我们都要。
在此之外的情况,则需要判断 m i d mid mid L 、 R L、R LR的关系:
1.如果 L ≤ m i d L ≤ mid Lmid,说明我们需要其左区间的部分信息。
2.如果 R > m i d R > mid R>mid,说明我们需要其右区间的部分信息。·最后返回以上几种情况所有信息合集。

int query(int l, int r, int p, int L, int R){
	if (L <= l && r <= R) return tree[p]; //区间包含则返回该节点所有信息
	int mid = (l + r) >> 1, ret = 0;// ret用来保存信息
	if (L <= mid) ret += query(l, mid, p << 1, L, R);//取左区间的部分信息
	if (R > mid) ret += query(mid + 1, r, p << 1|1,L, R);//取右区间的部分信息
	return ret;//将以上结果返回
}

修改某个位置k的信息:

很简单,只要找到并修改对应叶子节点的信息即可。

void update(int l, int r, int p, int k, int s) {
	if(l == r){
		tree[p] = s;
		return ;
	}
	int mid = (l + r) >> 1;
	if(k <= mid) update(l, mid, p << 1, k, s); // k <= mid 说明k在左区间
	else update(mid + 1, r, p << 1 | 1,k, s); // 反之则在右区间
	tree[p] = tree[p << 1]+ tree[p << 1|1]; // 别忘了要更新 p节点的信息哦~
}

时间复杂度为 O ( l o g 2 ( n ) ) O(log2(n)) O(log2(n))

修改区间[L,R]

问题来了,如果区间[L,R]修改信息怎么办?
继续暴力的思想,[L,R]一个个单点修改。
则修改—次的时间复杂度为 O ( n ∗ l o g 2 ( n ) ) O(n*log2(n)) O(nlog2(n)),修改 q q q次的话…
时间复杂度高达 O ( q ∗ n ∗ l o g 2 ( n ) ) O (q*n*log2(n)) O(qnlog2(n)),反而不如朴素的暴力?
那么是否可以转化成查询的时间复杂度?
即我们只要修改区间查询可以查询到的节点。
比如总区间 [ 1 , 10 ] [1,10] [1,10],要修改 [ 4 , 9 ] [4,9] [4,9]区间,我们其实只需要修改 [ 4 , 5 ] [4,5] [4,5] [ 6 , 8 ] [6,8] [6,8] [ 9 , 9 ] [9,9] [9,9]所在的节点的信息即可。
那对于这些节点的子节点怎么办?
对于修改到的区间,打上lazy标记,如果下次访问到这个区间的子区间,则下放这个标记。

神奇的lazy标记,用以区间修改。时间复杂度优化至 O ( l o g 2 ( n ) ) O(log2(n)) O(log2(n))

void update(int l, int r,int p, int L, int R, int s) {
	if(L <= l && r <= R) { //如果当前访问区间被要访问的区间所包含
		tree[p] += (r - l + 1) * s; //首先更新当前节点的值
		lazy[p] += s; //打上lazy标签
		return ;
	}
	int mid = (l + r) >> 1;
	if(lazy[p] != 0){ //如果当前区间有lazy标记,则需要下放
		tree[p << 1] += (mid - l + 1) * lazy[p];
		tree[p << 1 | 1] += (r - mid) * lazy[p];  
		//先更新两个儿子节点的值
		lazy[p << 1] += lazy[p];
		lazy[p << 1 | 1] += lazy[p]; 
		//标记下放到两个儿子节点
		lazy[p] = 0; //当前节点标记取消
	}
	if(L <= mid) update(l, mid, p << 1, L, R, s);
	// L <= mid 说明需要更新左区间
	if(R > mid)update(mid + 1, r, p << 1 | 1,L,R, s);
	// R > mid 说明需要更新右区间
	tree[p] = tree[p << 1] + tree[p << 1|1];
	// 别忘了要更新 p 节点的信息哦
}

神奇的lazy标记,用以区间修改。时间复杂度优化至 O ( l o g 2 ( n ) ) O (log2(n)) O(log2(n))

int query (int l, int r, int p, int L, int R){
	if(L <= l && r <= R) return tree[p];
	// 区间包含则返回该节点所有信息
	int mid = (l + r) >> 1, ret = 0; // ret用来保存信息
	if(lazy [p] != 0) {
	//如果当前区间有lazy标记,则需要下放
		tree[p << 1] += (mid - l + 1) * lazy[p];
		tree[p << 1|1] += (r - mid) * lazy[p];//先更新两个儿子节点的值
		lazy[p << 1] += lazy[p];
		lazy[p << 1|1] += lazy[p];//标记下放到两个儿子节点
		lazy[p] = 0;//当前节点标记取消
	}
	if(L <= mid) ret += query(l, mid, p << 1, L, R);
	//取左区间的部分信息
	if(R > mid) ret += query(mid + 1, r, p << 1|1, L,R);
	//取右区间的部分信息
	return ret;//将以上结果返回
}

总结&注意事项:

建树时间复杂度 O ( n ∗ l o g 2 ( n ) ) O (n*log2(n)) O(nlog2(n))
单次的单点/区间的更新/查询时间复杂度 O ( l o g 2 ( n ) ) O(log2(n)) O(log2(n))
时间复杂度极其优秀,空间需要开 4 ∗ n 4* n 4n
用于处理一些需要可分治区间信息的区间询问、修改

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值