数据结构之线段树

线段树

前言

  线段树这种数据结构主要的用来对区间的查询更新同时进行优化的。
  如果仅仅需要对查询进行优化,那么使用前缀和就可以使单次查询的时间复杂度达到O(1)。
  如果是考虑更新操作那么要分成两种情况:单点更新,区间更新。无论是单点更新还是区间更新,最坏的时间复杂度达到了O(n)。
  那么我们就考虑使用线段树这种数据结构来优化查找更新操作。
  在线段树中查找的时间复杂度为:O(logn);更新(单点更新和区间更新)的时间复杂度均为:O(logn)。
  线段树的空间复杂度为O(n),但是一般需要开2倍的空间,通常会开4倍的空间,防止越界。

适用情况

  必须满足法f(a + b) = f(a) + f(b)这种形式,也就是满足可加性。
  比如维护区间的最小(大)值,满足min(a + b) = min(min(a) , min(b)),解释:区间a + b的最小值等于区间a的最小值,区间b中的最小值中两者中的最小值。
  区间的和,满足sum(a + b) = sum(sum(a),sum(b)),解释:区间a + b的和等于区间a的和以及区间b的和两者之和。

补充

本人平时使用线段树的场景并不是太多,对于线段树的理解暂时还处于早期阶段,个人理解为线段树的作用更多是为了以空间换取时间,进而优化查找以及更新的时间复杂度。

代码

单点更新和查找
#include <bits/stdc++.h>
using namespace std;

const int maxn = 1e5 + 10;
typedef struct Node{
	// l:区间的左端点;r:区间的右端点;data:区间中存放的数据。
	// mid:(方法)取区间中点。
    int l,r;
    int data;
    int mid(void){
        return l + ((r - l) >> 1);
    }
} Node;
Node sum[maxn << 2]; // 注意不能把这个数组放到类中!!!

class SegmentTree{
    vector<int> nums;
    // 上浮
    void push_up(int root){
        sum[root].data = sum[root << 1].data + sum[root << 1 | 1].data;
    }
public:
    SegmentTree(vector<int>& nums){
        this->nums = nums;
    };
    // 建立线段树
    void build(int root,int l,int r){
        sum[root].l = l,sum[root].r = r;
        if (sum[root].l == sum[root].r){
            sum[root].data = nums[sum[root].l];
            return ;
        }
        int mid = sum[root].mid();
        build(root << 1,l,mid);
        build(root << 1 | 1,mid + 1,r);
        push_up(root);
    }
    // 在p位置加v(单点更新)
    void update(int root,int p,int v){
        if (sum[root].l == sum[root].r){
            sum[root].data += v;
            return ;
        }
        int mid = sum[root].mid();
        if (p <= mid) update(root << 1,p,v);
        else if (p > mid) update(root << 1 | 1,p,v);
        push_up(root);

    }
    // 查询
    int query(int root,int l,int r){
        int ans = 0;
        if (l <= sum[root].l && sum[root].r <= r) return sum[root].data;
        int mid = sum[root].mid();
        if (l <= mid) ans += query(root << 1,l,r);
        if (mid + 1 <= r) ans += query(root << 1 | 1,l,r);
        return ans;
    }

};

int main(void){
    vector<int> nums{1,2,3,4,5,6,7};
    SegmentTree tree(nums);
    tree.build(1,0,6);
    cout << "2-5: " << tree.query(1,2,5) << endl;
    cout << "1-7: " << tree.query(1,1,7) << endl;
    cout << "3-3: " << tree.query(1,3,3) << endl;
    tree.update(1,3,10);
    cout << "------------------------" << endl;
    cout << "2-5: " << tree.query(1,2,5) << endl;
    cout << "1-7: " << tree.query(1,1,7) << endl;
    cout << "3-3: " << tree.query(1,3,3) << endl;
	return 0;
}
区间更新和查找
#include <bits/stdc++.h>
using namespace std;

const int maxn = 1e5 + 10;
typedef struct Node{
	int l,r;
	int data;
	int mid(void){
		return l + ((r - l) >> 1);
	}
} Node;
Node sum[maxn << 2];
int lazy[maxn] = {0}; 
// 懒惰标记,每次只更新当前区间的数据,然后将lazy下沉。
// 类似于redis中惰性删除的思想,也很像copy on write的思想。
// 即查找到当前区间才更新当前更新,减少无谓的更新,提高效率。

class SegmentTree{
	vector<int> nums;
	void push_up(int root){
		sum[root].data = sum[root << 1].data + sum[root << 1 | 1].data;
		return ;
	}
	// 下沉函数
	void push_down(int root){
		if (lazy[root]){
			lazy[root << 1] = lazy[root];
			lazy[root << 1 | 1] = lazy[root];
			sum[root << 1].data = lazy[root] * (sum[root << 1].r - sum[root << 1].l + 1);
			sum[root << 1 | 1].data = lazy[root] * (sum[root << 1 | 1].r - sum[root << 1 | 1].l + 1);
			lazy[root] = 0;
		}
		return ;
	}
public:
	SegmentTree(vector<int>& nums){
		this->nums = nums;
	}
	void build(int root,int l,int r){
		sum[root].l = l,sum[root].r = r;
		if (sum[root].l == sum[root].r){
			sum[root].data = nums[sum[root].l];
			return ;
		}
		int mid = sum[root].mid();
		build(root << 1,l,mid);
		build(root << 1 | 1,mid + 1,r);
		push_up(root);
	}
	// [l,r]区间内的值修改为v(区间更新)
	// lazy[i] = 0:默认没有标记
	void update(int root,int l,int r,int v){
		if (l <= sum[root].l && sum[root].r <= r){
			sum[root].data = (sum[root].r - sum[root].l + 1) * v;
			lazy[root] = v;
			return ;
		}
		push_down(root);
		int mid = sum[root].mid();
		if (l <= mid) update(root << 1,l,r,v);
		if (mid + 1 <= r) update(root << 1 | 1,l,r,v);
		push_up(root);
	}
	int query(int root,int l,int r){
		int ans = 0;
		if (l <= sum[root].l && sum[root].r <= r){
			return sum[root].data;
		}
		push_down(root);
		int mid = sum[root].mid();
		if (l <= mid) ans += query(root << 1,l,r);
		if (mid + 1 <= r) ans += query(root << 1 | 1,l,r);
		return ans;
	}
};

int main(void){
	vector<int> nums{1,2,3,4,5,6,7};
	SegmentTree tree(nums);
	tree.build(1,0,6);
	cout << "0-2: " << tree.query(1,0,2) << endl;
	cout << "3-4: " << tree.query(1,3,4) << endl;
	cout << "1-5: " << tree.query(1,1,5) << endl;
	cout << "----------------------------------" << endl;
	tree.update(1,3,5,-1);
	cout << "0-2: " << tree.query(1,0,2) << endl;
	cout << "3-4: " << tree.query(1,3,4) << endl;
	cout << "1-5: " << tree.query(1,1,5) << endl;
	cout << "----------------------------------" << endl;
	tree.update(1,2,4,10);
	cout << "0-2: " << tree.query(1,0,2) << endl;
	cout << "3-4: " << tree.query(1,3,4) << endl;
	cout << "1-5: " << tree.query(1,1,5) << endl;
	return 0;
}

LeetCode例题

307. 区域和检索 - 数组可修改

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值