Binary Indexed Tree(二元索引数)(树状数组)

1 Binary Indexed Tree(二元索引数)(树状数组)

  • 1 用途:以O(log n)时间复杂度得到任意区间和。同时支持在O(log n)时间内支持动态单点值的修改。空间复杂度O(n)。
  • 2 出处:Peter M. Fenwick. A new data structure for cumulative frequency tables. Software: Practice and Experience. 1994, 24 (3): 327–336. doi:10.1002/spe.4380240306.
  • 3 原理:按照Peter M. Fenwick的说法,正如所有的整数都可以表示成2的幂和,我们也可以把一串序列表示成一系列子序列的和。采用这个想法,我们可将一个前缀和划分成多个子序列的和,而划分的方法与数的2的幂和具有极其相似的方式。一方面,子序列的个数是其二进制表示中1的个数,另一方面,子序列代表的f[i]的个数也是2的幂
    如下说明也很贴切:
How does Binary Indexed Tree work? 
The idea is based on the fact that all positive integers can be represented as the sum of powers of 2. For example 19 can be 
represented as 16 + 2 + 1. Every node of the BITree stores the sum of n elements where n is a power of 2. For example, in the first 
diagram above (the diagram for getSum()), the sum of the first 12 elements can be obtained by the sum of the last 4 elements (from 
9 to 12) plus the sum of 8 elements (from 1 to 8). The number of set bits in the binary representation of a number n is O(Logn). 
Therefore, we traverse at-most O(Logn) nodes in both getSum() and update() operations. The time complexity of the construction is 
O(nLogn) as it calls update() for all n elements. 

2 直观解释

  • 1 C[i]表示f[1]…f[i]的和,而用tree[idx]表示某些子序列的和
  • 2 实际上tree[idx]是那些indexes from (idx - 2^r + 1) to idx的f[index]的和,其中r是idx最右边的那个非零位到右边末尾的0的个数,比如:
    • eg 2.0 当idx=8 decimal = 1000,有r=3,则,tree[8] = f[1] + … + f[8],
    • eg 2.1 当idx=11 decimal = 1011,有r=0,则,tree[11] = f[11],
    • eg 2.2 当idx=12 decimal = 1100,有r=2,则,tree[12] = f[9] + f[10] + f[11] + f[12],
    • eg 2.3 当idx=14 decimal = 1110,有r=1,则,tree[14] = f[13] + f[14]
  • 3 有了上面tree这个数组(也就是bit本体),我们可以得到: C[13] = tree[13] + tree[12] + tree[8]
    • 从上述例子可以得出一个重要结论:求前idx个和,也就是求C[idx]的时候,idx中1的个数即为构成C[idx]的子序列的个数,也就是有多少个tree中的元素加起来

C1 = f1

C2 = f1 + f2

C3 = f3

C4 = f1 + f2 + f3 + f4

C5 = f5

C6 = f5 + f6

C7 = f7

C8 = f1 + f2 + f3 + f4 + f5 + f6 + f7 + f8



C16 = f1 + f2 + f3 + f4 + f5 + f6 + f7 + f8 + f9 + f10 + f11 + f12 + f13 + f14 + f15 + f16

  • 4 有了3作为基础,求前缀和过程如下:4,5参考
    https://cdn.jsdelivr.net/gh/xychen5/blogImgs@main/imgs/BITSum.3uh1v2dddp40.png
    • 4.1 tree[y] 是 tree[x] 的父节点,当且仅当可以通过从 x 的二进制表示中去除最后一个设置位(即数位为1的位)来获得 y,即 y = x – (x & (-x))。
      • lowbit函数 就是 取最后一个设置位的函数,lowbit = [](int x) int {return (x & (-x));}
      • eg: tree[8]是tree[10]的父节点,因为 10 - (10&(-10)) == 8 为true
    • 4.2 节点 tree[y] 的子节点 tree[x] 存储了 y(inclusive) 和 x(exclusive) 之间元素的总和:arr[y,…,x)。
    • 4.3 实际例子:求C[11] = C[1011]:
      • 4.3.1 看下图即可,很显然我们需要沿着路径一直加到dummy node(tree[0]其值为0,方便运算而已)为止,从这里能够再一次看出,为何其前缀和的算法时间复杂度为O(log n),因为路径上的node个数就是C[1011]中的1的个数
      • C[1011] = C[11] = tree[11] + tree[10] + tree[8]
      • 4.3.2具体代码:
int getSum(int BITree[], int index)
{
    int sum = 0; // Initialize result
    // index in BITree[] is 1 more than the index in arr[]
    index = index + 1;
    // Traverse ancestors of BITree[index]
    while (index>0)
    {
        // Add current element of BITree to sum
        sum += BITree[index];
        // Move index to parent node in getSum View
        index -= index & (-index);
    }
    return sum;
}
  • 5 更新BITree
    • 5.1 类似4中的求和,更改一个节点需要更改所有被当前节点所影响的子节点
    • 5.2 子节点的获取: parent of idx = idx + (idx & (-idx));
    • 5.3 举个例子,如4中的图:对于idx = 2这个点,需要更新节点4,8
void updateBIT(int BITree[], int n, int index, int val)
{
    // index in BITree[] is 1 more than the index in arr[]
    index = index + 1;
    // Traverse all ancestors and add 'val'
    while (index <= n)
    {
        // Add 'val' to current node of BI Tree
        BITree[index] += val;
        // Update index to that of parent in update View
        index += index & (-index);
    }
}

3 整体实现

// C++ code to demonstrate operations of Binary Index Tree
#include <iostream>

using namespace std;

/*		 n --> No. of elements present in input array.
	BITree[0..n] --> Array that represents Binary Indexed Tree.
	arr[0..n-1] --> Input array for which prefix sum is evaluated. */

// Returns sum of arr[0..index]. This function assumes
// that the array is preprocessed and partial sums of
// array elements are stored in BITree[].
int getSum(int BITree[], int index)
{
	int sum = 0; // Initialize result

	// index in BITree[] is 1 more than the index in arr[]
	index = index + 1;

	// Traverse ancestors of BITree[index]
	while (index>0)
	{
		// Add current element of BITree to sum
		sum += BITree[index];

		// Move index to parent node in getSum View
		index -= index & (-index);
	}
	return sum;
}

// Updates a node in Binary Index Tree (BITree) at given index
// in BITree. The given value 'val' is added to BITree[i] and
// all of its ancestors in tree.
void updateBIT(int BITree[], int n, int index, int val)
{
	// index in BITree[] is 1 more than the index in arr[]
	index = index + 1;

	// Traverse all ancestors and add 'val'
	while (index <= n)
	{
	// Add 'val' to current node of BI Tree
	BITree[index] += val;

	// Update index to that of parent in update View
	index += index & (-index);
	}
}

// Constructs and returns a Binary Indexed Tree for given
// array of size n.
int *constructBITree(int arr[], int n)
{
	// Create and initialize BITree[] as 0
	int *BITree = new int[n+1];
	for (int i=1; i<=n; i++)
		BITree[i] = 0;

	// Store the actual values in BITree[] using update()
	for (int i=0; i<n; i++)
		updateBIT(BITree, n, i, arr[i]);

	// Uncomment below lines to see contents of BITree[]
	//for (int i=1; i<=n; i++)
	//	 cout << BITree[i] << " ";

	return BITree;
}


// Driver program to test above functions
int main()
{
	int freq[] = {2, 1, 1, 3, 2, 3, 4, 5, 6, 7, 8, 9};
	int n = sizeof(freq)/sizeof(freq[0]);
	int *BITree = constructBITree(freq, n);
	cout << "Sum of elements in arr[0..5] is "
		<< getSum(BITree, 5);

	// Let use test the update operation
	freq[3] += 6;
	updateBIT(BITree, n, 3, 6); //Update BIT for above change in arr[]

	cout << "\nSum of elements in arr[0..5] after update is "
		<< getSum(BITree, 5);

	return 0;
}

4 实际例题 - 剑指 Offer 51. 数组中的逆序对

1 题目

https://leetcode-cn.com/problems/shu-zu-zhong-de-ni-xu-dui-lcof/

2 解题思路

  • 1 思路:
    • 1.1 首先注意到:对于数组{5,5,2,3,6}而言,得到每个value的个数的统计:
      • index -> 1 2 3 4 5 6 7 8 9
      • value -> 0 1 1 0 2 1 0 0 0
    • 1.2 那么上述过程中,比如对于5,其贡献的逆序数对为5之前所有数字出现次数的和,也就是value数组中2之前的前缀和!
  • 2 那么如何快速获得前缀和呢?考虑使用BST来获取,参考:
    • 2.1 整体思路如下:
      • a 使用数字在数组中的排名来代替数字(这不会对逆序数对的个数产生影响)
      • b 对数组nums中的元素nums[i]从右到左构建BITree(i 从 n-1 到 0),注意,BITree所对应的前缀和是数组里数字出现次数的和
        • 比如进行到nums[i],那么nums[i]右边的数字都已经统计了他们的出现次数,而后获取nums[i] - 1的前缀和,即可获取所有 < nums[i]的数字在nums[i:n]中的出现次数之和,也就是nums[i]贡献的逆序数对的个数
        • 之所以是逆序遍历构建BITree,是因为对于nums[i],它能够贡献的逆序数对的个数仅仅出现在它的右侧,所以需要在右侧进行
    • 2.2 额外说一下数组离散化,也就是不关系数字大小本身,只关心他们之间的相对排位
        sort(sortNoNums.begin(), sortNoNums.end(), std::less<int>());
        
        for(auto& num : nums) {
            num = lower_bound(sortNoNums.begin(), sortNoNums.end(), num) - sortNoNums.begin() + 1;
        }

相似题目:
https://leetcode-cn.com/problems/count-of-smaller-numbers-after-self/

class Solution {
public:
    class BIT {
    public: 
        vector<int> tree;
        int n;
        BIT(int _n): n(_n) , tree(_n + 1){cout << "init Done!" << endl;};

        // find prefixSum with index x
        int query(int x) {
            int sum = 0;
            while(x > 0) {
                sum += tree[x];
                x -= (x&(-x));
            }
            return sum;
        }

        // update x with delta val v
        void update(int x, int v) {
            cout << "d1"<< endl;
            while(x <= n) {
                tree[x] += v;
                x += (x&(-x));
            }
            cout << "d2"<< endl;
        }
    };

    int reversePairs(vector<int>& nums) {
        // desrialization, using sort no to denote the value
        vector<int> sortNoNums = nums;
        sort(sortNoNums.begin(), sortNoNums.end(), std::less<int>());
        
        for(auto& num : nums) {
            num = lower_bound(sortNoNums.begin(), sortNoNums.end(), num) - sortNoNums.begin() + 1;
        }

        // using binary indexed tree to statistic the reversePair num
        // start from the end of nums, so that the prefix in BIT means the reversePiar num
        int ans = 0;
        BIT bit(nums.size());
        for(int i = nums.size() - 1; i >= 0; --i) {
            ans += bit.query(nums[i] - 1); // cause only elements right than current num[i] will contribute to the ans
            cout << ans << endl;
            // statistic frequence of nums[i]
            bit.update(nums[i], 1);
        }

        return ans;
    }
};
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值