树状数组与线段树

引入

线段树和树状数组是两个十分相似的数据结构,它们能使对一个区间的数据修改以及查询的速度提升许多。两个结构本质相同,但是各有优缺点,下面我们实现它们的单点修改,单点查询,区间修改,区间查询。

线段树

线段树是一种二叉搜索树,与区间树相似,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点。

使用线段树可以快速的查找某一个节点在若干条线段中出现的次数,时间复杂度为O(logN)。而未优化的空间复杂度为2N,实际应用时一般还要开4N的数组以免越界,因此有时需要离散化让空间压缩。
在这里插入图片描述

树状数组

树状数组(Binary Indexed Tree(B.I.T), Fenwick Tree)是一个查询和修改复杂度都为log(n)的数据结构。主要用于查询任意两位之间的所有元素之和,但是每次只能修改一个元素的值;经过简单修改可以在log(n)的复杂度下进行范围修改,但是这时只能查询其中一个元素的值(如果加入多个辅助数组则可以实现区间修改与区间查询)。
在这里插入图片描述

线段树

  • 线段树的构造
    因为树状数组不需要构造过程,下面谈一谈线段树的构造。
    使用递归,代码如下:
void build(int left, int right, int index)
{
	tree[index].left = left;
	tree[index].right = right;
	if (left == right) return;
	int mid = (left + right) / 2;
	build(left, mid, index * 2);
	build(mid + 1, right, index * 2 + 1);
}
  • 线段树单点修改
    单点修改就是每遍历到一个节点,如果这个节点代表着的区间包括这个点,那么就修改它。
// 第dis个数加上k 
void add(int index, int dis, int k)
{
	if (tree[index].left == tree[index].right) 
	{
		tree[index].sum += k;
		return ;
	}
	if (dis <= tree[index].r) 
		add(index * 2, dis, k);
	else 
		add(index * 2 + 1, dis, k);
	tree[index].sum = tree[index * 2].sum + tree[index * 2 + 1].sum;
}
  • 线段树区间修改
    ① 如果当前区间全部在要加的区间范围之内,那么对区间,也就是该节点修改, 并return。
    ② 如果当前区间的right>=目标区间的left,说明这两个区间有交集,那么进入这个区间。
    ③ 如果当前区间的left<=目标区间的right,说明这两个区间有交集,那么进入这个区间。
// 区间[left, right]数据加上k 
void add(int index, int left, int right, int k)
{
	if (left <= tree[index].left && tree[index].right <= right)
	{
		tree[index].sum += k;
		return ;
	}
	if (tree[index * 2].right >= left) 
		add(index * 2, left, right, k);
	if (tree[index * 2 + 1].left <= right) 
		add(index * 2 + 1, left, right, k);
}
  • 线段树区间查询
    同修改
// 区间查询
void search(int index, int left, int right)
{
	if (left <= tree[index].left && tree[index].right <= right)
	{
		ans += tree[index].num;
		return ;
	}
	if (tree[index * 2].right >= left) 
		search(index * 2, left, right);
	if (tree[index * 2 + 1].left <= right) 
		search(index * 2 + 1, left, right);
}
  • 线段树单点查询
    从根节点,一直搜索到目标节点,然后一路上都加上就好了。
// 单点查询
void search(int index, int dis)
{
    ans += tree[index].num;
    if(tree[index].left == tree[index].right)
    	return ;
    if(dis <= tree[index * 2].right)
        search(index* 2, dis);
    if(dis >= tree[index * 2 + 1].left)
        search(index * 2 + 1, dis);
} 
  • lowbit
    lowbit这个东西很神奇,它可以将一个二进制数的所有高位一都去掉,只留下最低位的1,比如lowbit(5) =0001(即0101->0001)。
int lowbit(int k)
{
	return k & -k;
}
  • 树状数组单点修改
    如果我们要修改某个数 x 的值,只要加上lowbit(x),直到等于 n.。举个例子:总共有8个数第2个数要加上 k,具体过程如下:
    ① c[0010] += k;
    ② c[0010 + 0010] (c[0100]) += k;
    ③ c[0100+0100] (c[1000]) += k;
    这样我们能维护整个树状数组了。
// 单点修改 
void add(int x, int k) 
{
	while (x <= n)
	{
		tree[x] += k;
		x += lowbit(x);
	} 
} 
  • 树状数组区间修改
    如果我们要在 x 到 y 区间加上一个 k,可以分两步做。
    ① x 到 n 都加上一个 k。
    ② y +1 到 n 加上一个 -k。
// 区间修改 
add(x, k);
add(y + 1, k);
  • 树状数组区间查询
    其实就是求前缀和,比如我们要求 x 到 y 区间的和,那么就将从 1 到 y 的和减去从 1 到 x 的和。方法是这样的,比如求从 1 到 y 的和,先将 y 转为二进制,然后一直减去 lowbit(y),直到 y 等于0。下面用具体数据:
    如求1到7的和,应该是这样子的:
    ans += c[0111];//start
    ans += c[0111 - 0001];
    ans += c[0110 - 0010];
    ans += c[0100 - 0100];// end
// 区间查询
int sum(int x)
{
    int ans = 0;
    while(x != 0)
    {
        ans += tree[x];
        x -= lowbit(x);
    }
    return ans;
} 
res = sum(y) - sum(x - 1);// 区间[x, y]的和
  • 树状数组单点查询
    从 x 开始,不断进行 x -= lowbit(x),ans += tree[x],直到x 等于 0.
int search(int x)
{
    int ans = 0;
    while(x != 0)
    {
        ans += tree[x];
        x -= lowbit(x);
    }
    return ans;
}
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值