概念:
线段树:线段树是一种二叉搜索树,与区间树相似,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点。
使用线段树可以快速的查找某一个节点在若干条线段中出现的次数,时间复杂度为O(logN)。而未优化的空间复杂度为2N,实际应用时一般还要开4N的数组以免越界,因此有时需要离散化让空间压缩。
线段树是善于处理区间问题的。线段树是一棵完美的二叉树(所有的叶子的深度都一样,并且每个节点要么是叶子要么有两个儿子的树),树上的每一个节点都维护一个区间。根维护的是整个区间,每一个节点维护的是父亲的区间二等分后的其中的一个子区间。当有n个元素时,对区间的操作可以在O(log n)的时间内完成。
根据节点中维护的数据的不同,线段树可以提供不同的操作。下面实现了Range Minimum Query(RMQ)操作的线段树为例,进行说明。
基于线段树的RMQ的结构
一. 基于线段树的MRQ的查询:
查询一个较大的区间,由于靠上的节点对应较大的区间,通过这些区间就可以知道大部分的值的最小值,从而只需要访问很少的节点就可以求出最小值。
(1)如果要查询的区间和当前的节点对应的区间完全没有交集,那么久返回一个不影响结果的值INT_MAX;
(2)如果所查询的区间完全包含了当前结点对应的区间,那么就直接返回当前结点的值;
(3)以上两种情况都不满足的话,那么就对两个儿子递归处理,返回两个结果中的最小值;
int query(int a,int b,int k,int l,int r)//查询最小值;
{
if(r<=a||b<=l)//不包含在这个区间内;
return INT_MAX;
if(a<=l&&r<=b)//完全包含在这个区间内;
return dat[k];
else
{
int v1=query(a,b,k*2+1,l,(l+r)/2);
int v2=query(a,b,k*2+2,(l+r)/2,r);
return min(v1,v2);//两个值取最小的值;
}
}
二 .基于线段树的RMQ的值的更新:
在更新值后,对于线段树要进行全局的维护:从下向上进行值的更新,更新为两个子节点中的最小值就可以了。
void update(int k,int a)//更新值为a;
{
k+=n-1;
dat[k]=a;
while(k>0)
{
k=(k-1)/2;
dat[k]=min(dat[k*2+1,dat[k*2+2]);//上面的依次更新;
}
}
代码演示:
#include<set>
#include<map>
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
const int N=1<<17;
const int INT_MAX=99999999;
int n;
int dat[N*2-1];//存储线段树的数组;
void init(int n_)//初始化;
{
n=1;
while(n<n_)
n*=2;
for(int i=0; i<2*n-1; i++)
{
dat[i]=INT_MAX;
}
}
void update(int k,int a)//更新值为a;
{
k+=n-1;
dat[k]=a;
while(k>0)
{
k=(k-1)/2;
dat[k]=min(dat[k*2+1,dat[k*2+2]);//上面的依次更新;
}
}
int query(int a,int b,int k,int l,int r)//查询最小值;
{
if(r<=a||b<=l)//不包含在这个区间内;
return INT_MAX;
if(a<=l&&r<=b)//完全包含在这个区间内;
return dat[k];
else
{
int v1=query(a,b,k*2+1,l,(l+r)/2);
int v2=query(a,b,k*2+2,(l+r)/2,r);
return min(v1,v2);//两个值取最小的值;
}
}