线段树

掌握了二叉树不掌握线段树真的有点可惜的,线段树本身并不难,代码也容易理解。这玩意发明出来就是为了提高效率的,例如:你现在要在数组中计算任意一段区间 Li-------Lj 之间的和,如果采用数组遍历,那么时间复杂度肯定是O(n),当然你说你可以前缀处理,可以让时间复杂度达到o(1),问题是你前缀处理的时间加起来中和考虑一波,你还是难以逃脱o(n)的魔掌,并且如果数组中有一个节点的值改变了,你不得将前缀再处理一遍??

所以,线段树运营而生,线段树就是为了来解决这种问题的,线段树的本质是一棵平衡二叉树,一般需要会如下三种操作:1.建树,2.查询。3.改变原数组中某一元素的值以后更新这棵线段树

线段树无论是查找,还是计算某一区间的和,其时间复杂度可以降低到o(log n)

在这里插入图片描述

我们要将数组a这些元素建议一棵线段树。首先我们要明白一点,根节点是左右节点的和

如图所示,根节点表示区间内所有的和,左子树代表0–mid,之间的和,右子树代表mid+1—end之间的和,其中mid=(L+R)/2

首先是建树操作:
我们需要node,指向线段树的当前节点,start–end表示原数组中的范围
那么node2表示其左子树,node2+1表示右子树
然后对左右子树进行递归,然后此时node指向线段树的节点的值就是node指向线段树节点的左右子树根节点之和,递归的出口就是当start==end,当范围缩小到一个点的时候,start=end,此时node所指的节点就等于a[start],a[end]这个元素

 void built_tree(int node, int start, int end) //建树
    {
        if(start==end) //递归出口
            {
            tree[node]=a[start];
            return ;
            }
        int left=node*2;//左边
        int right=node*2+1; //右边
        int mid=(start+end)/2;
        built_tree(left,start,mid);
        built_tree(right,mid+1,end);
        tree[node]=tree[left]+tree[right];
    }

紧接着是更新操作,将原数组中下标是div所对应的值修改为X

同样的,我们需要一个node来指向树的当前的某个节点,start—end表示原数组中的范围,我们需要他来确定我们的div在那块位置,所哟我们将mid=(left+right)/2,如果是左半球就往左边递归,如果是右半球就往右边递归,递归完成以后一定要记得,将node所指节点的值给更新了,因为其发生了改变,递归的出口很显然,当start==end,范围只有一个元素时就是出口,修改原数组中的值,并将tree[]数组中所对应的值tree[node]给修改了即tree[node]=x

oid update_node(int node,int start,int end,int x,int div)
    {
        //修改a[div]里面的值
        if(start==end)
        {
            a[div]=x;
            tree[node]=x;
            return ;
        }
        int left=node*2;
        int right=node*2+1;
        int mid=(start+end)/2;
        if(div>=start&&div<=mid)
        {
            update_node(left,start,mid,x,div);
        }
        else
        {
            update_node(right,mid+1,end,x,div);
        }
        tree[node]=tree[left]+tree[right];//改完左边改完右边需要更新父节点的值
    }

最难的部分应该就是query,这个查询某一段区间上的值~~
首先,我们计算的区间范围是L–R,这一区间的值,所以自然少不了start,end,这两个变量,因为我们求的也是从a[L]—a[R]之间的和,node指向当前线段树某一节点的指针当然也不能少,我们计算某一区间的值是分区来算的,就比如上面的那个图,计算的范围在左半球,右半球没有范围,那么只需要加上左半球的结果即可,反之,如果计算的范围全在右半球,左半球没有计算的范围,那么只需要加到右半球接即可。,如果左右都有一点范围,就要两边分别计算机后相加,将两边的结果加起来即可,就比如上图中的如果是要2–4之间的值,左右两边都有一些范围,结束条件如上图的右边部分,第一个是不在区间范围直接范围0即可,或者start–end之间的范围在L–R之内则可以直接返回tree[node]

int query(int node,int start,int end,int L,int R) //求L--R之间的值
    {
        if(end<L||start>R)//不在区间范围内
        {
            return 0;
        }
        else if(start>=L&&end<=R)
        {
            return tree[node];
        }
        int mid=(start+end)/2;
        int left=node*2;
        int right=node*2+1;
        int left_sum=query(left,start,mid,L,R);
        int right_sum=query(right,mid+1,end,L,R);
        return left_sum+right_sum;
    }

完整代码~~·

#include <bits/stdc++.h>
#define Max 666
int a[6]={2,3,4,6,1,5};
int tree[24]={0};
using namespace std;
void built_tree(int node, int start, int end);
void update_node(int node,int start,int end,int x,int div);
int  query(int node,int start,int end,int L,int R); //求L--R之间的值
    int main()
    {
        int size=6;
        built_tree(1,0,size-1);
        for(int i=1;i<=20;i++)
        {
            cout<<tree[i]<<" ";
        }
        cout<<endl;
        cout<<query(1,0,size-1,2,4)<<endl;
        return 0;
    }


    //node表示tree的当前下标,start表示a数组开始下标,end表示a数组的结束下标

    void built_tree(int node, int start, int end) //建树
    {
        if(start==end) //递归出口
            {
            tree[node]=a[start];
            return ;
            }
        int left=node*2;//左边
        int right=node*2+1; //右边
        int mid=(start+end)/2;
        built_tree(left,start,mid);
        built_tree(right,mid+1,end);
        tree[node]=tree[left]+tree[right];
    }


    //如果某一个叶子节点的值被改变了,那就需要更新node所对应的值
    void update_node(int node,int start,int end,int x,int div)
    {
        //修改a[div]里面的值
        if(start==end)
        {
            a[div]=x;
            tree[node]=x;
            return ;
        }
        int left=node*2;
        int right=node*2+1;
        int mid=(start+end)/2;
        if(div>=start&&div<=mid)
        {
            update_node(left,start,mid,x,div);
        }
        else
        {
            update_node(right,mid+1,end,x,div);
        }
        tree[node]=tree[left]+tree[right];//改完左边改完右边需要更新父节点的值
    }

    int query(int node,int start,int end,int L,int R) //求L--R之间的值
    {
        if(end<L||start>R)//不在区间范围内
        {
            return 0;
        }
        else if(start>=L&&end<=R)
        {
            return tree[node];
        }
        int mid=(start+end)/2;
        int left=node*2;
        int right=node*2+1;
        int left_sum=query(left,start,mid,L,R);
        int right_sum=query(right,mid+1,end,L,R);
        return left_sum+right_sum;
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值