线段树可以动态地处理可映射在坐标轴上的一些线段(区间),同时可以维护这些区间的某些性质。它的优点是可以随时插入一个区间或删除一个已有区间,并用较低的代价来维护需要的性质。一个线段是对应于一个区间的。线段树是区间树的一种。
1. 线段树是一棵二叉树。
2.叶结点表示一个初等区间[a, a];每一个内部结点(a,b)有b-a>=1;
3. 根为[a,b]的线段树为T(a,b),其左子树为T(a,(a+b)/2),右子树为T((a+b)/2+1,b),直至细分为一个初等区间(叶结点)为止。
4.树上的每个节点对应于一个线段(还是叫“区间”更容易理解,区间的起点和终点通常为整数),同一层的节点所代表的区间,相互不会重叠。
5.线段树是平衡树,它的深度为log(b-a)。
例如:
【0,7】
/ \
【0,3】 【4,7】
/ \ / \
【0,1】 【2,3】 【4,5】 【6,7】
/ \ / \ / \ / \
【0,0】 【1,1】 【2,2】 【3,3】 【4,4】 【5,5】 【6,6】 【7,7】
一般情况下都由数据域来执行线段树的用途。
一、线段树的指针表示形式
1.线段树基本性质和操作
线段树是一棵二叉树,记为T(a, b),参数a,b表示区间[a,b],其中b-a称为区间的长度,记为L。
线段树T(a,b)也可递归定义为:
若L>1 : [a, (a+b) div 2]为 T的左儿子; [(a+b) div 2 + 1,b]为T 的右儿子。 若L=1 : T为叶子节点。
线段树中的结点一般采取如下数据结构:
struct Node
{
int left,right; //区间左右值
Node *leftchild;
Node *rightchild;
};
线段树的建立:
Node *build(int l , int r ) //建立二叉树
{
Node *root = new Node;
root->left = l;
root->right = r; //设置结点区间
root->leftchild = NULL;
root->rightchild = NULL;
if ( l < r )
{
int mid = (r+l) >>1;
root->leftchild = build ( l , mid ) ;
root->rightchild = build ( mid+1 , r) ;
}
return root;
}
线段树中的线段插入和删除:
增加一个cover的域来计算一条线段被覆盖的次数,因此在建立二叉树的时候应顺便把cover置0。
插入一条线段[c,d]:
void Insert(int c, int d , Node *root )
{
if(c<= root->left && d>= root->right)
root-> cover++;
else
{
if(c <= (root->left+ root->right)/2 ) Insert (c,d, root->leftchild );
if(d > (root->left+ root->right)/2 ) Insert (c,d, root->rightchild );
}
}
删除一条线段[c,d]:
void Delete (int c , int d , Node *root )
{
if(c<= root->left&&d>= root->right)
root-> cover= root-> cover-1;
else
{
if(c <= (root->left+ root->right)/2 ) Delete ( c,d, root->leftchild );
if(d > (root->left+ root->right)/2 ) Delete ( c,d, root->rightchild );
}
}
转自:http://www.cnblogs.com/shuaiwhu/archive/2012/04/22/2464583.html
举例说明:已知线段[2,5] [4,6] [0,7];求点2,4,7分别出现了多少次
三条已知线段插入过程:
[2,5]
--[2,5]插入【0,7】与3比较,分成两部分:插到左儿子【0,3】,插到右儿子【4,7】
--[2,5]插入【0,3】与1比较,插到右儿子【2,3】;[2,5]插入【4,7】和5比较,插到左儿子【4,5】
--[2,5]覆盖【2,3】区间,【2,3】记录+1;[2,5]覆盖【4,5】,【4,5】记录+1
[4,6]
--[4,6]插入【0,7】与3比较,插到右儿子【4,7】;
--[4,6]插入【4,7】与5比较,分成两部分,插到左儿子【4,5】,插到右儿子【6,7】
--[4,6]覆盖【4,5】,【4,5】记录+1;[4,6]插入【6,7】与6比较,插到左儿子【6,6】
--[6,6]覆盖【6,6】,【6,6】记录+1
[0,7]
--[0,7]覆盖【0,7】匹配,【0,7】记录+1
插入过程结束,线段树上的记录如下(红色数字为每条线段的记录n):
【0,7】
1
/ \
【0,3】 【4,7】
0 0
/ \ / \
【0,1】 【2,3】 【4,5】 【6,7】
0 1 2 0
/ \ / \ / \ / \
【0,0】 【1,1】 【2,2】 【3,3】 【4,4】 【5,5】 【6,6】 【7,7】
0 0 0 0 0 0 1 0
询问操作和插入操作类似,也是递归过程,略
2——依次把【0,7】 【0,3】 【2,3】 【2,2】的记录n加起来,结果为2
4——依次把【0,7】 【4,7】 【4,5】 【4,4】的记录n加起来,结果为3
7——依次把【0,7】 【4,7】 【6,7】 【7,7】的记录n加起来,结果为1
不管是插入操作还是查询操作,每次操作的执行次数仅为树的深度——logN
线段树的题目:
http://blog.renren.com/share/279182900/1290893180
http://www.doc88.com/p-790936693134.html
http://poj.org/problem?id=2528
http://www.cppblog.com/sicheng/archive/2008/01/09/40791.html
http://www.cnblogs.com/Lyush/archive/2011/09/11/2173505.html
http://blog.csdn.net/shiqi_614/article/details/8228102
http://ichirin.blog.hexun.com/29222028_d.html