线段树学习笔记

本文笔记在参考一步一步理解线段树-tenos的基础上形成

线段树,也是二叉搜索树的一种,是基于数组,但是优于数组的一种数据结构。同时结合预处理(时间复杂度一般在O(n))使得从原来数组的O(n)的查询和更新复杂度降到了O(logn),在处理很大数据量的数据更新和查询最值方面变得简单,值得一提的是,它的构建也很简单。这里从最小值的问题讨论(一般,线段树用在查询最值比较方便)

1.构建线段树:

构建的思想是从根节点开始,逐步接近叶子节点,这里采用基于数组的方式构建线段树。

树的编号是从上大下,从左到右依次增大的,管理的区间长度随着树的层次增加而每次缩短。比如从根节点到叶子的管理的区间变化是:

[0, N) --> [0, N/2) & [N/2, N), -->......-->0, 1, 2, 3,....., N-1

所以构建线段树(C++)的代码如下:

#include <iostream>
#include <algorithm>

using namespace std;
const int maxn = 1000;
const int INF = numeric_limits<int>::max();

struct LinearTree {
	int v;
}lt[maxn];

void construct(int root, int* arr, int l, int r) {
	if (l + 1 == r) lt[root].v = arr[s];
	else {
		//递归构造左右子树,区间为[left, right)
		construct(2*root + 1, arr, l, (l + r) / 2);
		construct(2*root + 2, arr, (l + r) / 2, r);
		//当前节点最值
		lt[root].v = min(lt[root*2 + 1].v, lt[root*2 + 2].v);
	}
}

2.线段树查询操作:

对于给定区间(区间最小长度为1,这里统一都是采用左闭右开的方式),要查询它的最小值,可以从根节点开始,不断将要查询的区间进行切割,从而得到查询结果,主要是这样:

1)对于当前访问区段,如果该区段与查询区段没有交集,那么返回一个INF;

2)对于当前访问区段,如果该区段包含在查询区段里,那么当前访问区段的最小值;

3)如果以上两种情况都不是,那么对当前区段二分,通过访问其子区段,来查询结果;

以上三种就是全部情况了,实际上第二种情况是第三种情况的子情况,同时对于之前所说的切割也是在这两种情况之下进行的,比如在第二种就说明访问区间是当前区间的子区间,第三种情况就可以把部分相交的访问区间切割成第一和第二种情况,第一种情况可以略掉,第二种就变成它的子情况了,再通过这些子区间的比较就可以得到查询结果:

int requir(int root, int cl, int cr, int ql, int qr) {
	//如果查询区间与当前区间不相交,那么返回INF
	if (qr <= cl || ql >= cr) return INF;
	//如果查询区间包含在当前区间内
	if (ql <= cl && qr >= cr) return lt[root].v;
	//如果查询区间和当前区间有交集
	int mid = (cl + cr) / 2;
	return min(requir(root*2 + 1, cl, mid, ql, qr), requir(root*2 + 2, mid, cr, ql, qr);
}

3.线段树更新:

更新分为两种,一种是对单个元素更新,一种是对单个区间更新。我参考的那篇文章的作者设计了一套方法,可以方便更新,但是对于这种求最值的问题似乎没有这个必要,如果是求和的话,或者说进行区间比较就很方便了(以下为Advanced版本):

#include <iostream>
#include <algorithm>

using namespace std;
const int maxn = 1000;
const int INF = numeric_limits<int>::max();

struct LinearTree {
	int v;
	int add;//每个区间每个元素的更新值
}lt[maxn];

void send(int root) {
	if (lt[root].add != 0) {
		int e = lt[root].add;
		lt[root*2 + 1].v += e;
		lt[root*2 + 1].add += e;
		lt[root*2 + 2].v += e;
		lt[root*2 + 2].add += e;
	}
}

void construct(int root, int* arr, int l, int r) {
	lt[root].add = 0;
	if (l + 1 == r) lt[root].v = arr[s];
	else {
		//递归构造左右子树,区间为[left, right)
		construct(2*root + 1, arr, l, (l + r) / 2);
		construct(2*root + 2, arr, (l + r) / 2, r);
		//当前节点最值
		lt[root].v = min(lt[root*2 + 1].v, lt[root*2 + 2].v);
	}
}

int requir(int root, int cl, int cr, int ql, int qr) {
	//如果查询区间与当前区间不相交,那么返回INF
	if (qr <= cl || ql >= cr) return INF;
	//如果查询区间包含在当前区间内
	if (ql <= cl && qr >= cr) return lt[root].v;
	//如果查询区间和当前区间有交集
	send(root);
	int mid = (cl + cr) / 2;
	return min(requir(root*2 + 1, cl, mid, ql, qr), requir(root*2 + 2, mid, cr, ql, qr);
}

void renewSing(int idx, int x, int root, int l, int r) {
	if (l + 1 == r && l == idx) lt[root].v += x;
	else {
		send(root);
		int mid = (l + r) / 2;
		if (mid > idx) renew(idx, x, root*2 + 1, l, mid);
		else renew(idx, x, root*2 + 2, mid, r);
		lt[root].v = min(lt[root* 2 + 1].v + , lt[root*2 + 2].v);
	}
}

viod renewSeg(int root, int alter, int L, int R, int alterL, int alterR) {
	if (R <= alterL || L >= alterR) return ;
	if (R <= alterR && L >= alterL) {
		lt[root].v += alter;
		lt[root].add += alter;
		return ;
	}
	//将之前的更新值向下传递
	send(root);
	int mid = (L + R) / 2;
	renewSeg(root*2 + 1, alter, L, mid, alterL, alterR);
	renewSeg(root*2 + 2, alter, mid, R, alterL, alterR);
	//回溯更新
	lt[root] = min(lt[root*2 + 1], lt[root*2 + 2]);
}



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值