线段树的建立、查询、更新及应用

来源:互联网 发布:js如何控制光标大小 编辑:程序博客网 时间:2024/06/05 17:35

线段树本质上是一棵二叉搜索树(体现在查找阶段),与普通的二叉树不一样,这个二叉树有点特别:用left和right来表示一个区间,即 [left, right],而其值val,则用来表征这个区间的某些特征,例如:区间最大值,区间最小值,区间和;并且容易知道,当left和right相等时,代表叶结点,其值就是某个数组当前位置的值。

一棵区间为[1, 5]上的线段树

这里写图片描述

线段树在处理某些问题时是非常方便的,一个经典的问题是:给你一个数组{ai},总共n个值(数组从1到n排列),然后给你一个区间 [l, r],对于每次查询,给出区间 [l, r]的和(或者最大值,最小值)

聪明的小伙伴一定会马上想到用前缀和来处理这个问题,是的,前缀和在预处理阶段为o(n)复杂度,而每次查询只需要o(1),对于这种静态问题是十分方便的,但是,如果我们在上面的问题中加上 “每次随机更新数组中的某个值” 这一条件时,维护前缀和的开销其实是十分巨大的,因为对于每次修改,你不得不对前缀和进行o(n)的更新,如果数据量过大,这种方式是肯定不行的。所以,我们采用线段树这一数据结构来解决这个问题

典型的线段树可以采用如下定义:

struct TreeNode{    int val;    int left,right;}tree[4*N];

以下为线段树的三个操作,建树,查询,更新
注:代码以求区间和为例

void build(int root,int l,int r)//建树{    tree[root].left=l;    tree[root].right=r;    if(l==r)//叶结点    {        tree[root].val=arr[l];        return;    }    int mid=(l+r)/2;    build(2*root,l,mid);//左子树    build(2*root+1,mid+1,r);//右子树    tree[root].val=tree[2*root].val+tree[2*root+1].val;//求和}
int query(int root,int l,int r)//查询[l,r]的和{    if(tree[root].left==l&&tree[root].right==r)//正好等于该区间    {        return tree[root].val;    }    int mid=(tree[root].left+tree[root].right)/2;    if(mid>=r)        return query(2*root,l,r);//在左子树查找    else if(mid+1<=l)        return query(2*root+1,l,r);//在右子树查找    return query(2*root,l,mid)+query(2*root+1,mid+1,r);//跨区间查找}
void update(int root,int pos,int val)//更新pos位置的值为val{    if(tree[root].left==pos&&tree[root].right==pos)    {        tree[root].val=val;        return;    }    int mid=(tree[root].left+tree[root].right)/2;    if(pos<=mid)        update(2*root,pos,val);    else update(2*root+1,pos,val);    tree[root].val=tree[2*root].val+tree[2*root+1].val;//更新和}

线段树每次查询和更新操作时,其复杂度为O(logn),这对于我们来说,是较为理想的。

应用问题:
第一行给出n,m,分别为数组元素的个数以及请求个数
第二行给出n个数,
随后m行,每行给出一个操作:opt x y
当opt = 1,更新数组中x位置的值为y
当opt = 2,输出[x, y]的区间和
注:下标从1开始

/* test case8 41 2 3 4 5 6 7 81 2 02 1 81 8 02 2 2*//*output340*/
#include <bits/stdc++.h>#define N 1024using namespace std;struct TreeNode{    int val;    int left,right;}tree[4*N];int arr[N];void build(int root,int l,int r){    tree[root].left=l;    tree[root].right=r;    if(l==r)    {        tree[root].val=arr[l];        return;    }    int mid=(l+r)/2;    build(2*root,l,mid);    build(2*root+1,mid+1,r);    tree[root].val=tree[2*root].val+tree[2*root+1].val;}int query(int root,int l,int r){    if(tree[root].left==l&&tree[root].right==r)    {        return tree[root].val;    }    int mid=(tree[root].left+tree[root].right)/2;    if(mid>=r)        return query(2*root,l,r);    else if(mid+1<=l)        return query(2*root+1,l,r);    return query(2*root,l,mid)+query(2*root+1,mid+1,r);}void update(int root,int pos,int val){    if(tree[root].left==pos&&tree[root].right==pos)    {        tree[root].val=val;        return;    }    int mid=(tree[root].left+tree[root].right)/2;    if(pos<=mid)        update(2*root,pos,val);    else update(2*root+1,pos,val);    tree[root].val=tree[2*root].val+tree[2*root+1].val;}int main(){    //freopen("2.txt","r",stdin);    int n,m;    cin>>n>>m;    for(int i=1;i<=n;++i)        cin>>arr[i];    build(1,1,n);    for(int i=0;i<m;++i)    {        int opt,x,y;        cin>>opt>>x>>y;        if(opt==1)            update(1,x,y);        else            cout<<query(1,x,y)<<endl;    }    return 0;}
原创粉丝点击