线段树基础

一 概述

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

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

二。从一个例子来理解线段树

       给定一个数字n ,初始化数组arr(下标从1开始)为1 2 3 4 5 6........n,求任意区间段的数字之和

例如n为100   1到100 的和为5050;1到2 的和为 3;50到51的和为101

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

另一种方法:使用一个数组(num)在存入数值时求出当前位置(m)之前的和,求a到b区间数字的和直接就num[b]-num[a];

 但是多次更新arr数组某个位置的值,会使操作很麻烦,而且费时间

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

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

1.创建线段数

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

树结点

struct node{
    int vel;
}

代码:

#include<stdio.h>
#include<algorithm>
#define MAX 1000
#define Inf 0x3f3f3f3f
using namespace std;
struct node{
    int vel;
}tree[MAX*4];    //定义线段树
/*
功能:新建线段树
root:当前线段树的根结点下标
arr: 用来构造线段树的数组
left:树组起始下标
right:数组结束下标
*/
void build(int root,int arr[],int left,int right){
    if(left==right){   //找到叶结点
        tree[root].vel=arr[left];
        return ;
    }
    int mid=(left+right)/2;
    build(root*2,arr,left,mid);  //递归构造左子树
    build(root*2+1,arr,mid+1,right);//递归构造右子树
    //根据左右子树根节点的值,更新当前根节点的值
    tree[root].vel=tree[root*2].vel+tree[root*2+1].vel;  //存放对应区间段的和
}
/*
功能:线段树的区间查询
root:当前线段树的根节点下标
[nleft, nright]: 当前节点所表示的区间
[qletf, qright]: 此次查询的区间
*/
int query(int root,int nleft,int nright,int qleft,int qright){
    if(qleft>nright||qright<nleft) return 0;     //查询区间于当前区间没有交集
    if(qleft<=nleft&&qright>=nright) return tree[root].vel; //当前区间在查询区间内
    int mid=(nleft+nright)/2;
    if(qleft>mid) return query(root*2+1,mid+1,nright,qleft,qright); //查询区间在当前区间的右区间内
    if(qright<=mid) return query(root*2,nleft,mid,qleft,qright);//查询区间在当前区间的左区间内
    //查询区间在当前区间的中间区间内
    //先求在当前区间左边部分的和,再求在当前区间右边部分的和 二者相加
    else return query(root*2,nleft,mid,qleft,mid)+query(root*2+1,mid+1,nright,mid+1,qright);
}
/*
功能:更新线段树中某个叶子节点的值
root:当前线段树的根节点下标
[nleft, nright]: 当前节点所表示的区间
pos: 待更新节点在原始数组arr中的下标
newnum: 更新的值(原来的值换成newnum)
*/
void updateOne(int root,int nleft,int nright,int pos,int newnum){
    if(nleft==nright){  //找到相应的结点
        tree[root].vel=newnum; //更新结点的值
        return;
    }
    int mid=(nleft+nright)/2;
    if(pos<=mid) //在左子树中更新
        updateOne(root*2,nleft,mid,pos,newnum);
    else  //在右子树中更新
        updateOne(root*2+1,mid+1,nright,pos,newnum);
    //根据左右子树的值回溯更新当前节点的值
     tree[root].vel=tree[root*2].vel+tree[root*2+1].vel;
}
int main(){
    int i,n;
    int arr[MAX];
    scanf("%d",&n);
    for(i=1;i<=n;i++)//输入arr数组的值从1到n
        arr[i]=i;
    build(1,arr,1,n);
   /* for(i=1;i<50;i++)
        printf("tree[%d] : %d\n",i,tree[i].vel);*/
 //   printf("请输入要查询的次数:\n");
    int m;
    scanf("%d",&m); //输入要查询的次数
    for(i=0;i<m;i++){
        int a,b;
        printf("请输入要查询的区间:\n");
        scanf("%d%d",&a,&b);
        printf("%d--%d sum : %d\n",a,b,query(1,1,n,a,b));
    }
    int pos,num;
    printf("请输入要更新点的位置及新值:\n");
    scanf("%d%d",&pos,&num);
    updateOne(1,1,n,pos,num);
     printf("请输入要查询的次数:\n");
    scanf("%d",&m); //输入要查询的次数
    for(i=0;i<m;i++){
        int a,b;
        printf("请输入要查询的区间:\n");
        scanf("%d%d",&a,&b);
        printf("%d--%d sum : %d\n",a,b,query(1,1,n,a,b));
    }
return 0;
}





                       


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值