[转]线段树在程序设计中的应用 | ||||||
2005年第4期(总第78期)(转自:http://www.mscenter.edu.cn/blog/like/articles/6106.html) | ||||||
| ||||||
林盛华
(韶关市第一中学,广东 韶关 512000)
[摘 要]文章主要介绍了线段树的定义及构造、线段树的动态数据结构和静态数据结构,以及线段树的基本操作。还结合具体实例,具体阐述了线段树在程序设计中的应用。
[关键词]线段树;动态数据结构;静态数据结构;线段树的基本操作
[中图分类号] TP3 [文献标识码]A [文章编号]1008-1151(2005)04-
[收稿日期] 2005-
[作者简介] 林盛华(1973-),男,江西南康人,广东省韶关市第一中学一级教师,研究方向:计算机程序设计。
在中学信息学竞赛中,经常遇到与区间、图形的面积、周长等有关的问题。处理涉及这些知识的问题,并不需要依赖很深的数学知识,但要提高处理此类问题的效率却又十分困难,为了提高程序的效率,经常需要使用一种特殊的数据结构:线段树。一个线段是对应于一个区间的,因此线段树也叫区间树。
1
线段树的构造信息
1.1
线段树的定义
线段树是一棵二叉树,将一个区间划分为一个个[i,i+1]的单元区间,每个单元区间对应线段树中的一个叶子结点。每个结点用一个变量count来记录覆盖该结点的线段条数。
设根为[a,b]的线段树记为T(a,b),区间的长度
b-
a记为
L。递归定义T[
a,
b]:
若
L>1 :[
a, (
a+
b) div 2]为 T的左儿子;
[(
a+
b) div 2,
b]为T的右儿子。
若
L=1 :T为一个叶子节点。
区间[1,7]所对应的线段树如下图所示。区间上有一条线段[3,6]。
1.2
线段树的数据结构
1.2.1动态数据结构
如果采用动态的数据结构来实现线段树,结点的构造可以用如下数据结构:
其中B和E表示了该区间为[B,E];Count为一个计数器,通常记录覆盖到此区间的线段的个数。Lchild和Rchild分别是左右子树的根。
1.2.2静态的数据结构
有时为了方便,我们也采用静态的数据结构—完全二叉树。
前面所讲的内容都只是线段树的基本结构。通常利用线段树的时候需要在每个结点上增加一些特殊的数据域,并且它们是随线段的插入删除进行动态维护的。
2
线段树的基本操作
为了叙述的方便,这里以完全二叉树为例,说明线段树的基本操作。
2
.1
线段树的插入算法
对此算法的解释:如果[a,b]完全覆盖了当前线段,那么显然该结点上的基数(即覆盖线段数)为1(作标记符号)。否则,如果[a,b]不跨越区间中点,就只对左树或者右树上进行插入。否则,在左树和右树上都要进行插入。注意观察插入的路径,一条待插入区间在某一个结点上进行“跨越”,此后两条子树上都要向下插入,但是这种跨越不可能多次发生。插入、删除区间的时间复杂度均为O(logn)。
2.2
线段树的统计算法
我们先给出一个概念:结点的测度。结点的测度m指的是结点所表示区间中线段覆盖过的长度。
比如,区间[1,7]所对应的线段树如下图所示,区间上有一条线段[3,6]。
结点的测度m如下式计算:
根据此式,我们很容易地算出上述线段树各结点的测度。
先算出叶子结点的测度:
COUNT[1,2]=0,COUNT[2,3]=0,COUNT[3,4]=1,CONUT[4,5]=1,COUNT[5,6]=1,COUNT[6,7]=0。接着依次算出内部结点的测度:
CONUT[2,4]=COUNT[2,3]+COUNT[3,4]=0+1=1
CONUT[1,4]=COUNT[1,2]+COUNT[2,4]=0+1=1
COUNT[5,7]=COUNT[5,6]+COUNT[6,7]=1+0=1
COUNT[4,7]=COUNT[4,5]+COUNT[5,7]=1+1=2
COUNT[1,7]=COUNT[1,4]+COUNT[4,7]=1+2=3
计算叶子结点的测度算法如下:
3
线段树的应用
3.1
盒子问题
在一个1000米长的桌子上放着很多盒子,桌子的后方有一堵墙,如下图所示。假设人站得足够远,问:从桌子前方可以看到多少个盒子?
WALL
[分析]我们可以把上述转换成这样的一道题:x轴上有若干条不同线段,将它们依次染上不同的颜色,问最后能看到多少种不同的颜色?(后染的颜色会覆盖原先的颜色)
我们可以这样规定:x轴初始是颜色0,第一条线段染颜色1,第二条线段染颜色2,以此类推。
原先构造线段树的方法不再适用,但是我们可以通过修改线段树的cover域的定义,使得这道题也能用线段树来解。
定义cover如下:cover=-1表示该区间由多种颜色组成。cover>=0表示该区间只有一种单一的颜色cover。
[插入算法如下]
[统计算法如下]
使用一个数组Flag,初始化为0。遍历线段树,对于每种颜色c对Flag[c]赋值1。最后统计Flag中1的个数即可。(注意颜色0应该排除在外,可以在最后减1)
3.2
染色问题
我们把上题略加改动:x轴上有若干条不同线段,将它们依次染上颜色(可以相同),连续的相同颜色被视作一段。问x轴被分成多少段?
[分析]我们仍然能够利用类似上题的做法,利用线段树解决。我们需要修改线段树域的定义:
原先的Cover域取消,增设两个域:lcolor、rcolor,分别表示该区间两个端点的颜色。颜色种类分别用0-n的整数表示,0表示底色。插入算法稍作变化,记录下每个结点的两个端点的颜色种类。而在统计结点的值时,如果该结点的左孩子的右端点的颜色与右孩子的左端点颜色相同,则结点的值则为:左孩子的值+右孩子的值-1。
假设用m表示该区间的线段数,则计算式如下:
程序略。
线段树的构造利用了二分法的思想,在具体的解题中,对域的设置是相当灵活的,并且随着线段的插入进行动态地维护。这是线段树解题的关键、是解题的灵魂。
|
线段树
最新推荐文章于 2021-03-11 12:45:37 发布