线段树 1(基础 单点更新)

首推一个博客点击打开链接

;还有这个点击打开链接

一 概述

线段树,类似区间树,它在各个节点保存一条线段(数组中的一段子数组),主要用于高效解决连续区间的动态查询问题,由于二叉结构的特性,它基本能保持每个操作的复杂度为O(logn)。

线段树的每个节点表示一个区间,子节点则分别表示父节点的左右半区间,例如父亲的区间是[a,b],那么(c=(a+b)/2)左儿子的区间是[a,c],右儿子的区间是[c+1,b]。

二 从一个例子理解线段树

下面我们从一个经典的例子来了解线段树,问题描述如下:从数组arr[0...n-1]中查找某个数组某个区间内的最小值,其中数组大小固定,但是数组中的元素的值可以随时更新。

对这个问题一个简单的解法是:遍历数组区间找到最小值,时间复杂度是O(n),额外的空间复杂度O(1)。当数据量特别大,而查询操作很频繁的时候,耗时可能会不满足需求。

另一种解法:使用一个二维数组来保存提前计算好的区间[i,j]内的最小值,那么预处理时间为O(n^2),查询耗时O(1), 但是需要额外的O(n^2)空间,当数据量很大时,这个空间消耗是庞大的,而且当改变了数组中的某一个值时,更新二维数组中的最小值也很麻烦。

我们可以用线段树来解决这个问题:预处理耗时O(n),查询、更新操作O(logn),需要额外的空间O(n)。根据这个问题我们构造如下的二叉树

  • 叶子节点是原始组数arr中的元素
  • 非叶子节点代表它的所有子孙叶子节点所在区间的最小值

例如对于数组[2, 5, 1, 4, 9, 3]可以构造如下的二叉树(背景为白色表示叶子节点,非叶子节点的值是其对应数组区间内的最小值,例如根节点表示数组区间arr[0...5]内的最小值是1):                                                                                                                           本文地址

由于线段树的父节点区间是平均分割到左右子树,因此线段树是完全二叉树,对于包含n个叶子节点的完全二叉树,它一定有n-1个非叶节点,总共2n-1个节点,因此存储线段是需要的空间复杂度是O(n)。那么线段树的操作:创建线段树、查询、节点更新 是如何运作的呢(以下所有代码都是针对求区间最小值问题)?

2.1 创建线段树

对于线段树我们可以选择和普通二叉树一样的链式结构。由于线段树是完全二叉树,我们也可以用数组来存储,下面的讨论及代码都是数组来存储线段树,节点结构如下(注意到用数组存储时,有效空间为2n-1,实际空间确不止这么多,比如上面的线段树中叶子节点1、3虽然没有左右子树,但是的确占用了数组空间,实际空间是满二叉树的节点数目: ,  是树的高度,但是这个空间复杂度也是O(n)的 )。

struct SegTreeNode

{

  int val;

};

定义包含n个节点的线段树 SegTreeNode segTree[n],segTree[0]表示根节点。那么对于节点segTree[i],它的左孩子是segTree[2*i+1],右孩子是segTree[2*i+2]。

我们可以从根节点开始,平分区间,递归的创建线段树,线段树的创建函数如下:


emmmm后面些看那个博客的就挺好的点击打开链接,一步一步讲;


我在这罗列一些题目,实战一下

下面上一个简单的HDU 1754

建树+单子叶更新的板子,(没有存是否更新的状态)

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cctype>
#include <list>
#include <algorithm>
#include<queue>
#include<vector>
#include<set>
#include<cmath>

using namespace std;
const int maxn=200000+5;
const int inf=0x3f3f3f3f;
int n,m;
int tree[maxn*2];
int arr[maxn];
void build(int root,int istart,int iend)
{
    if(istart==iend)
        tree[root]=arr[istart];
    else
    {
        int mid=(istart+iend)/2;
        build(root*2,istart,mid);
        build(root*2+1,mid+1,iend);
        tree[root]=max(tree[root*2],tree[root*2+1]);
    }
}
int que(int root,int istart,int iend,int l,int r)
{
    if(r<istart||l>iend)
        return 0;
    else if(l<=istart&&r>=iend)
        return tree[root];
    else
    {
        int mid=(istart+iend)/2;
        int ans=0;
        if(l<=mid) ans=max(ans,que(root*2,istart,mid,l,r));
        if(r>=mid) ans=max(ans,que(root*2+1,mid+1,iend,l,r));
        return ans;
    }
}
void update(int root,int istart,int iend,int t,int a)
{
    if(istart==iend)
        tree[root]=a;
    else
    {
        int mid=(istart+iend)/2;
        if(t<=mid) update(root*2,istart,mid,t,a);
        else update(root*2+1,mid+1,iend,t,a);
        tree[root]=max(tree[root*2],tree[root*2+1]);
    }
}
int main()
{
    while(cin>>n>>m)
    {
        for(int i=1;i<=n;i++)
            scanf("%d",&arr[i]);
        build(1,1,n);
        for(int i=0;i<m;i++)
        {
            char ch;
            int a,b;
            cin>>ch>>a>>b;
            //cout<<a<<" "<<b<<endl;
            if(ch=='Q')
                printf("%d\n",que(1,1,n,a,b));
            if(ch=='U')
               update(1,1,n,a,b);
        }
    }
    return 0;
}

下一个简单线段树 hdu1166

#include <iostream>
#include <cstdio>
#include<string>
#include <cstring>
#include <cctype>
#include <list>
#include <algorithm>
#include<queue>
#include<vector>
#include<set>
#include<cmath>

using namespace std;
const int maxn=50000+5;
const int inf=0x3f3f3f3f;
int n,m;
int tree[maxn*2];
int arr[maxn];
int T;
void build(int root,int istart,int iend)
{
    if(istart==iend)
        tree[root]=arr[istart];
    else
    {
        int mid=(istart+iend)/2;
        build(root*2,istart,mid);
        build(root*2+1,mid+1,iend);
        tree[root]=tree[root*2]+tree[root*2+1];
    }
}
int que(int root,int istart,int iend,int l,int r)
{
    if(r<istart||l>iend)
        return 0;
    else if(l<=istart&&r>=iend)
        return tree[root];
    else
    {
        int mid=(istart+iend)/2;
        int ans=0;
        if(l<=mid) ans+=que(root*2,istart,mid,l,r);
        if(r>=mid) ans+=que(root*2+1,mid+1,iend,l,r);
        return ans;
    }
}
void update(int root,int istart,int iend,int t,int a)
{
    if(istart==iend)
        tree[root]+=a;
    else
    {
        int mid=(istart+iend)/2;
        if(t<=mid) update(root*2,istart,mid,t,a);
        else update(root*2+1,mid+1,iend,t,a);
        tree[root]=tree[root*2]+tree[root*2+1];
    }
}
int main()
{
    cin>>T;
    for(int ka=1;ka<=T;ka++)
    {
        cin>>n;
        for(int i=1;i<=n;i++)
            scanf("%d",&arr[i]);
        build(1,1,n);
        printf("Case %d:\n",ka);
       while(1)
        {
            string ch;
            int a,b;
            cin>>ch;
            if(ch[0]=='E')
                break;
            cin>>a>>b;
            if(ch[0]=='Q')
                printf("%d\n",que(1,1,n,a,b));
            if(ch[0]=='S')
               update(1,1,n,a,-b);
            if(ch[0]=='A')
                update(1,1,n,a,b);
        }
    }
    return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值