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

本文详细介绍了一种高效的数据结构——线段树,通过实例演示了如何使用线段树处理区间查询和更新问题,并提供了完整的代码实现。

线段树本质上是一棵二叉搜索树(体现在查找阶段),与普通的二叉树不一样,这个二叉树有点特别:用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 case
8 4
1 2 3 4 5 6 7 8
1 2 0
2 1 8
1 8 0
2 2 2
*/

/*output
34
0
*/
#include <bits/stdc++.h>
#define N 1024
using 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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值