zoukankan      html  css  js  c++  java
  • 『数据结构』线段树

    线段树原理

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

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

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

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

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

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

    叶子节点是原始组数(a)中的元素
    非叶子节点代表它的所有子孙叶子节点所在区间的最小值
    例如对于数组([2, 5, 1, 4, 9, 3])可以构造如下的二叉树(背景为白色表示叶子节点,非叶子节点的值是其对应数组区间内的最小值,例如根节点表示数组区间(a[0...5])内的最小值是1):

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

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

    线段树的代码实现

    建线段树的过程

    void build(int l, int r, int root)
    {
    	if (l==r)
    	{
    		sum[root]=a[l];
    		return;
    	}
    	int mid=(l+r)>>1;
    	build(l,mid,root<<1);
    	build(mid+1,r,root<<1|1);
    	pushup(root);
    }
    

    pushup过程

    void push(int root)
    {
    	sum[root]=sum[root<<1]+sum[root<<1|1];
    }
    

    查询线段树

    int query(int ansl, int ansr, int l, int r,int root)
    {
    	if (ansl<=l && r<=ansr) return sum[root];
    	int mid=(l+r)>>1;
    	int ans=0;
    	if (ansl<=mid) ans+=query(ansl,ansr,l,mid,root<<1);
    	if (ansr>mid) ans+=query(ansl,ansr,mid+1,r,root<<1|1);
    	return ans;
    }
    

    单节点更新

    void update(int pos, int c, int l, int r, int root)
    {
    	if (l==r)
    	{
    		sum[root]+=c;
    		return;
    	}
    	int mid=(l+r)/2;
    	if (pos<=mid) update(pos,c,l,mid,root<<1); else update(pos,c,mid+1,r,root<<1|1);
    	pushup(root);
    }
    
  • 相关阅读:
    自定义vue必填验证指令
    福珑2日常存活策略
    树莓派系统安装和配置 WiringPi
    代码实现RabbitMQ死信队列的创建
    博客已经搬家
    浅谈PHP序列化与反序列化
    prometheus监控golang服务实践
    ETCD核心机制解析
    N1BOOK 记录
    利用Ubuntu虚拟机制作F2FS文件系统镜像
  • 原文地址:https://www.cnblogs.com/shenxiaohuang/p/10162292.html
Copyright © 2011-2022 走看看