(C++) 树状数组

目录

一、介绍

二、一维树状数组

        2.1 区间长度

        2.2 前驱和后继

        2.3 查询前缀和

        2.4 点更新

三、一维数组的实现

        3.1 区间长度函数

        3.2 前缀和

        3.3 插入/更新

        3.4 封装成类


一、介绍

        树状数组(Binary Indexed Tree,BIT),又称为 Fenwick 树,是一种高效的数据结构,主要用于动态维护数组的前缀和(或区间和),以及支持单点更新的操作。其核心思想是利用二进制的特性,通过巧妙地设计数据结构,对每一部分设置了一个“管理员”,管理员存储量其管理对象的所和,从而实现快速的区间查询和单点更新操作。因此当需要对前n和数据进行求和的时候就不需要遍历n次,只需要查询对应管理员中的数值即可。

        本篇仅仅介绍一维的树状数组,多维的原理和此是一样的。

二、一维树状数组

        这里需要了解几个关于树状数组的几个定义。

        首先我们定义一个管理数组c[i],也就是上面提到的“管理员”。管理员的个数和一维数组的元素个数相同。

        再定义所有存储的一维数组为a[i]

        2.1 区间长度

        这里的区间长度指的是每一个管理员所管理的具体对象的多少。对于c[i],若i的二进制末尾有k个连续的0,则c[i]存储的区间长度为2^{k},即从a[i]向前数2^{k}个元素的累加和。例如,6的二级制为110,末尾有1个0,区间长度为2,就存储了a[5],a[6],即c[6] = a[5] + a[6]

        得到二进制末尾连续0的个数的方法是,将该二进制数取反后加一,再与原值做与运算。例如:20的二进制为10100,是原始二进制数,取反后为01011,加1后为01100,此时最低位的1和原数最低位的1的位置是一样的,01100和10100做与运算就得到了00100

i = 20 = 10100

\widetilde{i} = 01011        取反

01011+1=01100

01100 & 10100=00100 = 4

得到的区间长度为4。这里定义lowbit(i)函数得到的是区间的长度。

        2.2 前驱和后继

        直接的前驱为c[i-lowbit(i)]

        直接的后继为c[i+lowbit(i)]

        前驱:c[i]的直接前驱,直接前驱的直接前驱...

        后继:c[i]的直接后继,直接后继的直接后继...

        2.3 查询前缀和

        前i个元素的前缀和sum[i]等于c[i]加上c[i]的前驱。例如sum(5)c[5]加上c[5]的前驱。而c[5]的前驱为c[4],且c[4]没有前驱,故sum(5) = c[5] + c[4]

        解释一下c[5]的前驱。i=5,其二进制为0101,末尾没有0,故区间长度为1(2的0次方),则其前驱为c[5-1]=c[4],同理可得c[4]没有前驱,故只有一个前驱。

        2.4 点更新

        若对a[i]进行了修改,加上了z,则只需要更新c[i]以及后继即可,让所有的都加上z。因为该更新只会改变其本身和后继的数值,对前驱是没有影响的。

        需要注意的是,树状数组的下标需要从1开始,否则lowbit(0)=0会出现死循环。

三、一维数组的实现

        3.1 区间长度函数

        在计算机中,使用二进制的补码进行表示刚好是 i 取反加1,因此-i = \widetilde{i} + 1

int lowbit(int i)
{
	return (-i) & i;	//计算区间区间的大小
}

        3.2 前缀和

int sum(int i)
{
	int s = 0;
	for (; i > 0; i -= lowbit(i))
	{
		s += c[i];
	}
	return s;
}

        3.3 插入/更新

        该函数可以用来更新数值,也可以用来创建树状数组。只要把c[i]初始化为0,z 遍历a[i]插入即可。

void add(int i, int z)
{
	for (; i <= 9; i += lowbit(i))
	{
		c[i] += z;
	}

        3.4 封装成类

class BITree
{
private:
	std::vector<int> c;			//存储树的节点和
	int size;					//数组的大小
	int lowbit(int i);			//计算区间的大小
public:
	BITree(int n);				//输入数据量
	void Update(int i, int z);	//修改数据
	int sum(int i);			//查询
};

BITree::BITree(int n)
{
	size = n;
	c.resize(n + 1, 0);
}

int BITree::lowbit(int i)
{
	return (-i) & i;
}

void BITree::Update(int i, int z)
{
	for (; i <= 9; i += lowbit(i))
	{
		c[i] += z;
	}
}

int BITree::sum(int i)
{
	int s = 0;
	for (; i > 0; i -= lowbit(i))
	{
		s += c[i];
	}
	return s;
}

测试:

int main()
{
    int a[9] = { 1,2,3,4,5,6,7,8,9 };
    for (int i = 0; i < 9; i++)
    {
    	add(i + 1, a[i]);
    }
    std::cout << sum(9) << std::endl;

    BITree tree(9);
   for (int i = 0; i < 9; i++)
    {
	    tree.Update(i + 1, a[i]);
    }
    std::cout << tree.sum(4) << std::endl;
    return 0;
}

相关推荐

  1. C语言-算法-树状数组

    2024-04-23 15:56:03       51 阅读
  2. 数据结构-树状数组

    2024-04-23 15:56:03       35 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-04-23 15:56:03       98 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-04-23 15:56:03       106 阅读
  3. 在Django里面运行非项目文件

    2024-04-23 15:56:03       87 阅读
  4. Python语言-面向对象

    2024-04-23 15:56:03       96 阅读

热门阅读

  1. 工作后的自我介绍

    2024-04-23 15:56:03       30 阅读
  2. ATFX:注册邀请码怎么弄?

    2024-04-23 15:56:03       33 阅读
  3. 大数据——Scala 模式匹配

    2024-04-23 15:56:03       28 阅读
  4. 第4章:GO的错误处理机制

    2024-04-23 15:56:03       31 阅读
  5. 在 C 中打印字符串 - 如何在 C 中打印字符串

    2024-04-23 15:56:03       37 阅读
  6. Postgresql数据库高级sql总结3

    2024-04-23 15:56:03       28 阅读
  7. oracle sql 示例

    2024-04-23 15:56:03       31 阅读