1.定义
1.1树 树是图论中的一个概念,在图论中,树(英语:Tree)是一种无向图(undirected graph),其中任意两个顶点间存在唯一一条路径。或者说,只要没有回路的连通图就是树。
1.2二叉树 二叉树是每个节点最多有两个子树的树结构。通常子树被称作“左子树”(left subtree)和“右子树”(right subtree)。
1.3二叉查找树 (Binary Search Tree)又称二叉搜索树,二叉排序树。若中序遍历这个二叉查找树,就会得到有序递增的序列。
1.4完全二叉树 除最后一层外,每一层上的节点数均达到最大值;在最后一层上只缺少右 边的若干结点。
1.5满二叉树 除最后一层无任何子节点外,每一层上的所有结点都有两个子结点的二叉树。
1.6线段树:线段树的本质是一棵二叉树,不同于其它二叉树,线段树的每一个节点记录的 是一段区间的信息。有人说线段树是一种二叉搜索树,在树结构上线段树的最后一层是 不一定存在的,即使存在也分布不均匀,它们显然不一样,所以说这种说法是错误的。 又有人说线段树是完全二叉树,同样在树结构上他们也不一样。
2.线段树解决什么问题
区间查询 询问某段区间的某些性质(极值,求和,etc)
区间更新 某些操作影响了某段区间(统一加一个值……)
高效解决连续区间的动态查询问题,由于二叉结构的特性,它基本能保持每个操作的复杂度为O(lgN)!
3.代码&思想
3.1节点信息
用一个结构体记录节点信息
struct Tree
{
int left,right; //区间的端点
int max,sum; //节点信息,视题目要求而定
};
我们用一个数组a记录节点,且根节点的下标为1,
对于任一节点a[k],
它的左儿子为a[2*k]
它的右儿子为a[2*k+1]
这样 一维数组即实现了线段树节点的保存。实际上这是一种顺序存储结构,有比较高的空间利用率。同时直接根据索引可以找到某个节点,以及他的左右儿子,也降低了时间复杂性。
建树的过程主要思想是递归构造,如果当前节点记录的区间只有一个值,则直接赋值,否则递归构造左右子树,最后回溯的时候给当前节点赋值
void build(int id,int l,int r)
{
tree[id].left=l; tree[id].right=r;//初始化区间的左右端点
if (l==r)//如果左端点等于右端点,换句话说就是区间里只有1个值
{
tree[id].sum=a[l];//初始化节点信息
tree[id].max=a[l];
}
else
{
int mid=(l+r)/2;
build(id*2,l,mid);//分别递归的建立左半区间和右半区间
build(id*2+1,mid+1,r);
tree[id].sum=tree[id*2].sum+tree[id*2+1].sum;//初始化节点信息
tree[id].max=max(tree[id*2].max,tree[id*2+1].max;
}
}
void update(int id,int pos,int val)//若更新a[pos]的值为val,调用update(1,pos,val)即可
{
if (tree[id].left==tree[id].right)//如果找到了节点,就更新此节点
{
tree[id].sum=tree[id].max=val;
}
else
{
int mid=(tree[id].left+tree[id].right)/2;
if (pos<=mid) update(id*2,pos,val);//如果要更新的值在左半区间,就递归更新左半区间
else update(id*2+1,pos,val);//否则就更新右半区间
tree[id].sum=tree[id*2].sum+tree[id*2+1].sum;//更新此节点信息
tree[id].max=max(tree[id*2].max,tree[id*2+1].max)
}
}
需要用到延迟标记,每个结点新增加一个标记,记录这个结点是否被进行了某种修改操作(这种修改操作会影响其子结点)。对于任意区间的修改,我们先按照查询的方式将其划分成线段树中的结点,然后修改这些结点的信息,并给这些结点标上代表这种修改操作的标记。在修改和查询的时候,如果我们到了一个结点p,并且决定考虑其子结点,那么我们就要看看结点p有没有标记,如果有,就要按照标记修改其子结点的信息,并且给子结点都标上相同的标记,同时消掉p的标记。(优点在于,不用将区间内的所有值都暴力更新,大大提高效率,因此区间更新是最优用的操作)
主要思想是把所要查询的区间[a,b]划分为线段树上的节点,然后将这些节点代表的区间合并起来得到所需信息
void query(int id,int l,int r)//调用query(1,l,r)即可查询[l,r]区间内元素的总和
{
if (tree[id].left==l&&tree[id].right==r)//如果找到了此区间,直接返回
return tree[id].sum; //询问总和
else
{
int mid=(tree[id].left+tree[id].right)/2;
if (r<=mid) return query(id*2,l,r);//如果目标区间在当前左半区间,就递归左半区间
else if (l>mid) return query(id*2+1,l,r)//否则就递归右半区间
else
return query(id*2,l,mid)+query(id*2+1,mid+1,r);如果目标区间别分成了两部分,就分别计算并返回
}
}