【树】一步一步写线段树(一)——基础操作

线段树

线段树是一棵二叉树,它的每一个节点用于维护一个区间,其叶节点表示一个点或者单位区间。根维护整个区间。所有区间是它父亲结点二等分后的一个子区间。当有n个元素时,对每一个元素的操作的复杂度是 log n
线段树示例
线段树示例
线段树作为一种数据结构,使用范围非常广泛,我们可以在其中添加许多域,令其能够解决更多的问题。

存储

不难得出,我们使用结构体来做最为方便。
由于线段树每一个结点维护的是一个区间,所以,我们可以用两个变量l,r来记录区间;同理,我们就可以在结构体内部加上各种变量来维护这个区间。
线段树的存储方式与二叉树一样,在这里我们采用全局数组存储(当然你也可以使用你的方法)
示例代码:

struct node{
    int l,r;
}Tree[4*Maxn+5];
//经验表明,我们必须使用这么大的数组
//因为线段树有2*N个节点,且生长极不规律
//按这种方法存储,则每个节点的两个儿子分别是2i,2i+1
//否则RE
//当然用指针做更好(不会浪费过多空间)

建树

存储做好了,我们就开始建树。
我们运用递归的思路来建树。
根据线段树的定义,我们可以递归地将区间 [ l , r ] 分成 [ l , l + r 2 ] [ l + r 2 + 1 , r ] 两个子区间,再递归地分下去,直到分成了一个一个的单位区间。
示例代码:

void Build(int i,int l,int r) {
//i-当前结点编号,l,r当前区间左右端点
    Tree[i].l=l;
    Tree[i].r=r;
    //记录左右端点
    if(l==r) {
        //中间N多初始化操作
        return;
    }//处理单位区间
    int mid=(l+r)/2;
    Build(i*2,l,mid);
    Build(i*2+1,mid+1,r);
    //递归地分下去
    PushUp(i);//别忘了把上传标记
}

我们就得到了一棵空的线段树,接下来就是遍历。

遍历

我们可以利用线段树是一棵二叉树的特点,使用递归来遍历它。
伪代码如下:

DFS(i) //i-当前结点编号
    DFS(i*2);//遍历左子树
    DFS(i*2+1);//遍历右子数
    //接下来的N多操作
//当然你也可以加上一些参数,使其变为一个统计函数

更新

线段树的更新可以很方便用地分治实现。
我们可以很快得出以下代码:

void Update(int i,int l,int r,int val) {//i-当前结点编号,[l,r]要修改的区间,val目标值
    int mid=(Tree[i].l+Tree[i].r)/2;
    if(l==Tree[i].l&&r==Tree[i].r) {//到达指定区间[l,r]
        //修改值
        return;
    }
    PushDown(i);//下传标记
    if(r<=mid)
        Update(i*2,l,r,val);
    else if(l>=mid+1)
        Update(2*i+1,l,r,val);
    else {
        Update(i*2,l,mid,val);
        Update(i*2+1,mid+1,r,val);
    }
    //以上if-else嵌套是分治更新
    //如果超出左子区间,则以当前区间的中点二分,递归进行更新
    PushUp(i);//上传标记
}

如果我们不去改变区间 [ l , r ] ,又该如何实现呢?
其实很简单:我们只需不停地遍历整棵树,找出含有区间 [ l , r ] 的区间,打上标记并返回,等到下一次更新时路过这个节点时将其标记下传

void Update(int i,int l,int r,int val) {
    if(r<Tree[i].l||l>Tree[i].r)return;/*未在区间则返回*/
    if(/*已被更新则返回*/)return;
    if(l<=Tree[i].l&&Tree[i].r<=r) {
        //打上标记
        return;//返回
    }
    PushDown(i);
    Update(i*2,l,r);//直接找子区间
    Update(i*2+1,l,r);
    PushUp(i);
}

猜你喜欢

转载自blog.csdn.net/qq_37656398/article/details/79237570