[线段树]线段树 入门

来源:互联网 发布:淘宝网针织内衣高领 编辑:程序博客网 时间:2024/05/15 02:48
 

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;   //节点信息,视题目要求而定};


3.2节点的存储结构

我们用一个数组a记录节点,且根节点的下标为1,

对于任一节点a[k],

它的左儿子为a[2*k]

它的右儿子为a[2*k+1]

这样 一维数组即实现了线段树节点的保存。实际上这是一种顺序存储结构,有比较高的空间利用率。同时直接根据索引可以找到某个节点,以及他的左右儿子,也降低了时间复杂性。

 

3.3建立一棵线段树,并记录原数组信息

建树的过程主要思想是递归构造,如果当前节点记录的区间只有一个值,则直接赋值,否则递归构造左右子树,最后回溯的时候给当前节点赋值

 

 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;}}

3.4更新某个点的数值,并维护相关点的信息

3.4.1单点更新

  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)}}


3.4.2区间更新

需要用到延迟标记,每个结点新增加一个标记,记录这个结点是否被进行了某种修改操作(这种修改操作会影响其子结点)。对于任意区间的修改,我们先按照查询的方式将其划分成线段树中的结点,然后修改这些结点的信息,并给这些结点标上代表这种修改操作的标记。在修改和查询的时候,如果我们到了一个结点p,并且决定考虑其子结点,那么我们就要看看结点p有没有标记,如果有,就要按照标记修改其子结点的信息,并且给子结点都标上相同的标记,同时消掉p的标记。(优点在于,不用将区间内的所有值都暴力更新,大大提高效率,因此区间更新是最优用的操作)

3.5查询某区间内元素的和或最大值(以总和为例)

主要思想是把所要查询的区间[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);如果目标区间别分成了两部分,就分别计算并返回}}

0 0