下图是对线段[1,10)建立的一棵线段树基本结构
1.每个节点都是一个[a,b)的区间,根节点代表了整个所要处理的区间
2. 对于每个非叶节点[a,b),令mid = (a + b) / 2;则其左右儿子节点代表的区间为[a,mid),[mid,b)
3.二叉的组织结构
4. 线段树是一个平衡树,树的高度为logN
5. 线段树把区间上的任意一条长度为L的线段都分成不超过2logL条线段的并
6. 任两个结点要么是包含关系要么没有公共部分,不可能部分重叠,每层节点的区间并即为全体区间
线段树是一个树形结构,其上的信息都是保存在树的节点中。用结构体的方式建立节点,一个节点的基本结构如下:
struct node
{
int left,right,mid;
};
其中left 和 right 分别代表该节点所 表示线段的左右端点,即当前节点所表示的线段为 [left ,right) 。而 mid = (left + right) / 2 ,为当前线段的中点。
这只是基本结构,在具体解题中,需要在节点中添加其他数据域保存信息。
堆式存储的特点:
1. N的左儿子是 2N
2. N 的右儿子是 2N + 1
3. N 的父亲是 N / 1
结构体数组保存线段树,根节点下标为1。对于非叶节点num,其左右子节点下标分别为2*num和2*num+1。
node seg_tree[3*MAXN];
//由线段树的性质可知,建树需要的空间大概是所需要处理的最长线段的两倍多,所以需要开3倍大小的数组
void make(int l,int r,int num)
{
seg_tree[num].left = l;
seg_tree[num].right = r;
seg_tree[num].mid = (l +r)/2;
if (l+1!= r) return;
make(l,seg_tree[num].mid,2*num);
make(seg_tree[num].mid,r,2*num+1);
}
为了记录节点中的线段是否被完全覆盖过,需要在节点中添加一个数据域cover。若cover为1则表示此条线段已经被完全覆盖过,否则未被覆盖。
插入操作的代码
void insert(int l,int r,int num)
{
//l,r分别为插入当前节点线段的左右端点,num为节点在数组中的编号
if (seg_tree[num].left ==l&& seg_tree[num].right == r)
{
//若插入的线段完全覆盖当前所表示的线段
seg_tree[num].cover = 1;
return;
}
if (r <=seg_tree[num].mid)
//当前节点的左子节点所代表的线段完全包含插入的节点
insert(l,r,2*num);
elseif(l>=seg_tree[num].mid)
//当前节点的右子节点所代表的线段完全包含插入的节点
insert(l,r,2*num+1);
else {
//插入线段跨越当前节点所表示的中点
insert(l,seg_tree[num].mid,2*num);
insert(seg_tree[num].mid,r,2*num+1);
}
}
一样采用递归的方法对线段进行删除,如果当前节点所代表的线段未被覆盖,则递归进入此节点的左右子节点进行删除。否则要考虑两种情况。一是删除的线段完全覆盖当前节点所代表的线段,则将当前节点的cover值置0。应该递归的在当前节点的子树上所有节点删除线段。另一种情况是删除的线段未完全覆盖当前节点所代表的线段,通常采用的方法是,将当前节点的cover置0,并将其左右子节点的cover置1,然后递归的进入左右子节点进行删除。
删除操作的代码
void del(intl,int r,int num)
{
if (seg_tree[num].left ==l&&seg_tree[num].right == r)
{
seg_tree[num].cover = 0;
return;
}
if (seg_tree[num].cover)
{
seg_tree[num*2].cover = 1;
seg_tree[num*2 + 1].cover = 1;
seg_tree[num] = 0;
}
if (r <= seg_tree[num].mid)
del(l,r,2*num);
else if (l>=seg_tree[num].mid)
del(l,r,2*num+1);
else {
del(l,seg_tree[num].mid,2*num);
del(seg_tree[num].mid,r,2*num+1);
}
}
对应不同的问题,线段树会统计不同的数据,比如线段覆盖的长度,线段覆盖连续区间的个数等等,其实现思路不尽相同。
一般来说,统计都是在区间中进行的,所以依然需要采用递归的方式进行统计。
int cal(int num)
{
if (seg_tree[num].cover)
return seg_tree[num].right -seg_tree[num].left + 1;
if (seg_tree[num].left + 1 == seg_tree[num].right)
return 0;
return cal(2*num) + cal(2*num+1);
}
——本文摘自陈宇老师将要出版的算法图书,转载请标明出处