本文笔记在参考一步一步理解线段树-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]);
}