Open In App

Implementation of compressed 2D Binary Indexed tree

Last Updated : 17 Oct, 2023
Improve
Improve
Like Article
Like
Save
Share
Report

A compressed 2D Binary Indexed Tree (BIT) is a data structure that is used to efficiently store and query two-dimensional arrays, particularly when the values in the array are frequently updated.

The compressed 2D Binary Indexed Tree (BIT) is a data structure used for fast querying and updating values in a two-dimensional array. It is an extension of the Binary Indexed Tree that is used for one-dimensional arrays. In the compressed 2D BIT, each node in the binary tree represents the sum of a rectangular subregion of the two-dimensional array. This allows for efficient range queries and updates on the two-dimensional array.

To save memory, the compressed 2D BIT uses a compression technique that removes empty nodes from the binary tree. This results in a smaller tree and a smaller memory footprint, which makes it useful for applications where memory is limited. The compressed 2D BIT can be used in a variety of applications, including image processing, spatial data analysis, and computer graphics.

The implementation of 2D BIT consists of the following components:

  • Compress the array: The compressed 2D BIT stores the prefix sums of the compressed array, which is obtained by compressing the original array by mapping each unique element in the array to an index in the range [1, n], where n is the number of unique elements in the array. This compression reduces the memory usage of the BIT.
  • Build the BIT: The compressed 2D BIT can be built using a modified version of the 1D BIT algorithm. The BIT is built row by row, where each row is compressed before being processed. The prefix sum of each compressed row is computed using the BIT, and the resulting prefix sums are stored in the BIT.
  • Query the BIT: To query the compressed 2D BIT, the query range is compressed and then processed row by row using the BIT. The prefix sums of the compressed rows are summed to obtain the prefix sum of the query range.
  • Update the BIT: Updating the compressed 2D BIT involves compressing the updated element and then updating the prefix sums of the compressed rows that contain the updated element using the 1D BIT update algorithm.

To implement a compressed 2D Binary Indexed Tree, we can follow the following steps:

  • Create an n x m two-dimensional array and initialize it with zeros. This array will be used to store the values that you want to query or update.
  • Create an n x m two-dimensional array called the compressed BIT. Initialize this array with zeros.
  • To update a value in the two-dimensional array, add the value to the corresponding cell in the compressed BIT and all its ancestors in the tree. This can be done using the standard BIT update algorithm for one-dimensional arrays.
  • To query the sum of values in a rectangular subregion of the two-dimensional array, calculate the sum of the values in the compressed BIT for the four corners of the rectangle and subtract the sum of the values in the compressed BIT for the cells outside the rectangle. This can also be done using the standard BIT query algorithm for one-dimensional arrays.
  • To compress the 2D BIT, iterate through each row of the 2D BIT and remove any nodes that have a value of zero and whose two children are also zero. This reduces the memory footprint of the data structure.

Below is the Python code to implement the above steps:

C++




#include <bits/stdc++.h>
using namespace std;
 
// Compressed2DBIT class
class Compressed2DBIT {
    int n, m;
    vector<vector<int> > bit, tree;
 
public:
    // Constructor
    Compressed2DBIT(int n, int m)
    {
        this->n = n;
        this->m = m;
 
        // Initialize bit and tree vectors
        bit.assign(n + 1, vector<int>(m + 1, 0));
        tree.assign(n, vector<int>(m, 0));
    }
 
    // Update function for bit vector
    void update(int x, int y, int val)
    {
        while (x <= n) {
            int y1 = y;
            while (y1 <= m) {
                bit[x][y1] += val;
                y1 += y1 & -y1;
            }
            x += x & -x;
        }
    }
 
    // Query function for bit vector
    int query(int x, int y)
    {
        int s = 0;
        while (x > 0) {
            int y1 = y;
            while (y1 > 0) {
                s += bit[x][y1];
                y1 -= y1 & -y1;
            }
            x -= x & -x;
        }
        return s;
    }
 
    // Compresses the bit vector into a 2D tree
    void compress()
    {
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= m; j++) {
                // Compute the sum of elements in the
                // corresponding rectangle
                tree[i - 1][j - 1] = query(i, j)
                                     - query(i - 1, j)
                                     - query(i, j - 1)
                                     + query(i - 1, j - 1);
            }
        }
        bit.clear(); // Clear bit vector after compression
    }
 
    // Getter function for compressed tree
    vector<vector<int> > get_compressed_tree()
    {
        return tree;
    }
};
 
// Main function
int main()
{
    // Initialize input matrix
    vector<vector<int> > arr = { { 1, 2, 3, 4, 5 },
                                 { 6, 7, 8, 9, 10 },
                                 { 11, 12, 13, 14, 15 },
                                 { 16, 17, 18, 19, 20 } };
 
    // Create Compressed2DBIT object and update with input
    // matrix
    Compressed2DBIT bit(4, 5);
    for (int i = 0; i < 4; i++) {
        for (int j = 0; j < 5; j++) {
            bit.update(i + 1, j + 1, arr[i][j]);
        }
    }
 
    // Test query function
    cout << bit.query(2, 3) << endl; // expected output: 27
    cout << bit.query(4, 5) << endl; // expected output: 210
 
    // Compress the bit vector and test query function again
    bit.compress();
    cout << bit.query(2, 3) << endl; // expected output: 27
    cout << bit.query(4, 5) << endl; // expected output: 210
 
    // Get compressed tree and print it
    vector<vector<int> > compressed_tree
        = bit.get_compressed_tree();
    for (auto row : compressed_tree) {
        for (auto x : row) {
            cout << x << " ";
        }
        cout << endl;
    }
 
    return 0;
}


Java




import java.util.*;
 
// Compressed2DBIT class
class Compressed2DBIT {
    int n, m;
    List<List<Integer>> bit, tree;
 
    // Constructor
    public Compressed2DBIT(int n, int m) {
        this.n = n;
        this.m = m;
 
        // Initialize bit and tree lists
        bit = new ArrayList<>(n + 1);
        for (int i = 0; i <= n; i++) {
            bit.add(new ArrayList<>(m + 1));
            for (int j = 0; j <= m; j++) {
                bit.get(i).add(0);
            }
        }
 
        tree = new ArrayList<>(n);
        for (int i = 0; i < n; i++) {
            tree.add(new ArrayList<>(m));
            for (int j = 0; j < m; j++) {
                tree.get(i).add(0);
            }
        }
    }
 
    // Update function for bit list
    public void update(int x, int y, int val) {
        while (x <= n) {
            int y1 = y;
            while (y1 <= m) {
                bit.get(x).set(y1, bit.get(x).get(y1) + val);
                y1 += y1 & -y1;
            }
            x += x & -x;
        }
    }
 
    // Query function for bit list
    public int query(int x, int y) {
        int s = 0;
        while (x > 0) {
            int y1 = y;
            while (y1 > 0) {
                s += bit.get(x).get(y1);
                y1 -= y1 & -y1;
            }
            x -= x & -x;
        }
        return s;
    }
 
    // Compresses the bit list into a 2D tree
    public void compress() {
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= m; j++) {
                // Compute the sum of elements in the
                // corresponding rectangle
                tree.get(i - 1).set(j - 1, query(i, j)
                                     - query(i - 1, j)
                                     - query(i, j - 1)
                                     + query(i - 1, j - 1));
            }
        }
        bit.clear(); // Clear bit list after compression
    }
 
    // Getter function for compressed tree
    public List<List<Integer>> getCompressedTree() {
        return tree;
    }
}
 
// Main function
public class Main {
    public static void main(String[] args) {
        // Initialize input matrix
        List<List<Integer>> arr = new ArrayList<>(Arrays.asList(
            Arrays.asList(1, 2, 3, 4, 5),
            Arrays.asList(6, 7, 8, 9, 10),
            Arrays.asList(11, 12, 13, 14, 15),
            Arrays.asList(16, 17, 18, 19, 20)
        ));
 
        // Create Compressed2DBIT object and update with input
        // matrix
        Compressed2DBIT bit = new Compressed2DBIT(4, 5);
        for (int i = 0; i < 4; i++) {
            for (int j = 0; j < 5; j++) {
                bit.update(i + 1, j + 1, arr.get(i).get(j));
            }
        }
      // Compress the bit list into a 2D tree
bit.compress();
          // Get the compressed tree and print it
    List<List<Integer>> compressedTree = bit.getCompressedTree();
    for (int i = 0; i < compressedTree.size(); i++) {
        for (int j = 0; j < compressedTree.get(i).size(); j++) {
            System.out.print(compressedTree.get(i).get(j) + " ");
        }
        System.out.println();
    }
}
}
       


Python3




# Define a class named Compressed2DBIT
class Compressed2DBIT:
 
  # Define the constructor
  # with arguments n and m
    def __init__(self, n, m):
        self.n = n
        self.m = m
 
        # Create a 2D BIT (Binary Indexed
        # Tree) with zeros, with dimensions
        # n + 1 and m + 1 initialize 2D
        # BIT with zeros
        self.bit = [[0] * (m + 1) for _ in range(n + 1)]
        self.tree = None
 
    def update(self, x, y, val):
        """
        Update the value at (x, y) in the 2D array by adding `val` to it.
        """
 
        # Update the values in the BIT
        # based on the provided x, y,
        # and val
        while x <= self.n:
            y1 = y
            while y1 <= self.m:
                self.bit[x][y1] += val
 
                # Compute the next y value
                # to update based on the
                # current y value
                y1 += y1 & -y1
 
                # Compute the next x value
                # to update based on the
                # current x value
            x += x & -x
 
    def query(self, x, y):
        """
        Query the sum of the elements in
        the subarray from (1, 1) to (x, y)
        """
        s = 0
        while x > 0:
            y1 = y
            while y1 > 0:
                s += self.bit[x][y1]
                y1 -= y1 & -y1
            x -= x & -x
        return s
 
    def compress(self):
        """
        Compress the 2D array using the
        Fenwick tree (Binary Indexed Tree)
        technique.
        """
 
        # initialize compressed 2D array
        # with zeros
        self.tree = [
            [0] * self.m for _ in range(self.n)]
        for i in range(1, self.n + 1):
            for j in range(1, self.m + 1):
 
                # Calculate the sum of the
                # elements in the subarray
                # from (1, 1) to (i, j)
                # using the formula:
                # sum(x1, y1, x2, y2) =
                # sum(x2, y2) - sum(x1-1, y2)
                # - sum(x2, y1-1) +
                # sum(x1-1, y1-1)
                self.tree[i-1][j-1] = self.query(i, j) - self.query(
                    i-1, j) - self.query(i, j-1) + self.query(i-1, j-1)
 
                # set the 2D BIT to None
                # to save memory
        self.bit_compressed = None
 
 
# Example usage
arr = [[1, 2, 3, 4, 5],
       [6, 7, 8, 9, 10],
       [11, 12, 13, 14, 15],
       [16, 17, 18, 19, 20]]
 
bit = Compressed2DBIT(4, 5)
for i in range(4):
    for j in range(5):
        bit.update(i + 1, j + 1, arr[i][j])
 
print(bit.query(2, 3))  # expected output: 27
print(bit.query(4, 5))  # expected output: 210
 
bit.compress()
print(bit.query(2, 3))  # expected output: 27
print(bit.query(4, 5))  # expected output: 210
# expected output:
# [[1, 2, 3, 4, 5], [6, 7, 8, 9, 10],
# [11, 12, 13, 14, 15],
# [16, 17, 18, 19, 20]]
print(bit.tree)


C#




using System;
using System.Collections.Generic;
 
class Program {
  // Main  function
  static void Main(string[] args)
  {
    // Initialize input matrix
    List<List<int> > arr = new List<List<int> >() {
      new List<int>() { 1, 2, 3, 4, 5 },
      new List<int>() { 6, 7, 8, 9, 10 },
      new List<int>() { 11, 12, 13, 14, 15 },
      new List<int>() { 16, 17, 18, 19, 20 }
    };
 
    // Create Compressed2DBIT object and update with
    // input matrix
    Compressed2DBIT bit = new Compressed2DBIT(4, 5);
    for (int i = 0; i < 4; i++) {
      for (int j = 0; j < 5; j++) {
        bit.update(i + 1, j + 1, arr[i][j]);
      }
    }
 
    // Compress the bit list into a 2D tree
    bit.compress();
 
    // Get the compressed tree and print it
    List<List<int> > compressedTree
      = bit.getCompressedTree();
    for (int i = 0; i < compressedTree.Count; i++) {
      for (int j = 0; j < compressedTree[i].Count;
           j++) {
        Console.Write(compressedTree[i][j] + " ");
      }
      Console.WriteLine();
    }
  }
}
 
// Compressed2DBIT class
class Compressed2DBIT {
  int n, m;
  List<List<int> > bit, tree;
 
  // Constructor
  public Compressed2DBIT(int n, int m)
  {
    this.n = n;
    this.m = m;
 
    // Initialize bit and tree lists
    bit = new List<List<int> >();
    for (int i = 0; i <= n; i++) {
      bit.Add(new List<int>());
      for (int j = 0; j <= m; j++) {
        bit[i].Add(0);
      }
    }
 
    tree = new List<List<int> >();
    for (int i = 0; i < n; i++) {
      tree.Add(new List<int>());
      for (int j = 0; j < m; j++) {
        tree[i].Add(0);
      }
    }
  }
 
  // Update function for bit list
  public void update(int x, int y, int val)
  {
    while (x <= n) {
      int y1 = y;
      while (y1 <= m) {
        bit[x][y1] += val;
        y1 += y1 & -y1;
      }
      x += x & -x;
    }
  }
 
  // Query function for bit list
  public int query(int x, int y)
  {
    int s = 0;
    while (x > 0) {
      int y1 = y;
      while (y1 > 0) {
        s += bit[x][y1];
        y1 -= y1 & -y1;
      }
      x -= x & -x;
    }
    return s;
  }
 
  // Compresses the bit list into a 2D tree
  public void compress()
  {
    for (int i = 1; i <= n; i++) {
      for (int j = 1; j <= m; j++) {
        // Compute the sum of elements in the
        // corresponding rectangle
        tree[i - 1][j - 1] = query(i, j)
          - query(i - 1, j)
          - query(i, j - 1)
          + query(i - 1, j - 1);
      }
    }
    bit.Clear(); // Clear bit list after compression
  }
 
  // Getter function for compressed tree
  public List<List<int> > getCompressedTree()
  {
    return tree;
  }
}
 
// This code is contributed by Tapesh(tapeshdua420)


Javascript




class Compressed2DBIT {
    constructor(n, m) {
        this.n = n;
        this.m = m;
 
        // Initialize bit and tree arrays
        this.bit = new Array(n + 1);
        this.tree = new Array(n);
 
        for (let i = 0; i <= n; i++) {
            this.bit[i] = new Array(m + 1).fill(0);
        }
 
        for (let i = 0; i < n; i++) {
            this.tree[i] = new Array(m).fill(0);
        }
    }
 
    // Update function for bit array
    update(x, y, val) {
        while (x <= this.n) {
            let y1 = y;
            while (y1 <= this.m) {
                this.bit[x][y1] += val;
                y1 += y1 & -y1;
            }
            x += x & -x;
        }
    }
 
    // Query function for bit array
    query(x, y) {
        let s = 0;
        while (x > 0) {
            let y1 = y;
            while (y1 > 0) {
                s += this.bit[x][y1];
                y1 -= y1 & -y1;
            }
            x -= x & -x;
        }
        return s;
    }
 
    // Compresses the bit array into a 2D tree
    compress() {
        for (let i = 1; i <= this.n; i++) {
            for (let j = 1; j <= this.m; j++) {
                // Compute the sum of elements in the corresponding rectangle
                this.tree[i - 1][j - 1] = this.query(i, j) - this.query(i - 1, j) - this.query(i, j - 1) + this.query(i - 1, j - 1);
            }
        }
    }
 
    // Getter function for compressed tree
    getCompressedTree() {
        return this.tree;
    }
}
 
// Initialize input matrix
const arr = [
    [1, 2, 3, 4, 5],
    [6, 7, 8, 9, 10],
    [11, 12, 13, 14, 15],
    [16, 17, 18, 19, 20]
];
 
// Create Compressed2DBIT object and update with input matrix
const bit = new Compressed2DBIT(4, 5);
for (let i = 0; i < 4; i++) {
    for (let j = 0; j < 5; j++) {
        bit.update(i + 1, j + 1, arr[i][j]);
    }
}
 
// Test query function
console.log(bit.query(2, 3)); // expected output: 27
console.log(bit.query(4, 5)); // expected output: 210
 
// Compress the bit array and test query function again
bit.compress();
console.log(bit.query(2, 3)); // expected output: 27
console.log(bit.query(4, 5)); // expected output: 210
 
// Get compressed tree and print it
const compressedTree = bit.getCompressedTree();
for (const row of compressedTree) {
    console.log(row.join(" "));
}


Output

27
210
27
210
[[1, 2, 3, 4, 5], [6, 7, 8, 9, 10], [11, 12, 13, 14, 15], [16, 17, 18, 19, 20]]

Time Complexity: 

  • The time complexity of the update method is O(log*n * log*m) because it performs two nested while loops, each of which executes log*n (or log*m) times due to the bitwise operations inside the loop conditions.
  • The time complexity of the query method is also O(log n * log m) because it performs two nested while loops, each of which executes log n (or log m) times due to the bitwise operations inside the loop conditions.
  • The time complexity of the compress method is O(n * m * log*n * log*m) because it performs two nested for loops, each of which executes n (or m) times, and each iteration of the inner loop calls the query method four times, resulting in a time complexity of O(log n * log m) per iteration.

Auxiliary Space:

  • The space complexity of the Compressed2DBIT class is O(n * m) because it stores the original 2D array in a 2D binary indexed tree (self.bit) and stores the compressed 2D array in a separate 2D array (self.tree), both of which have dimensions n x m. 
  • Additionally, it stores the values of n and m, as well as a reference to the compressed 2D array (self.tree) and the compressed 2D binary indexed tree (self.bit_compressed). 
  • Therefore, the total space complexity is O(n * m) + O(1) = O(n * m).

Advantages of compressed 2D Binary Indexed tree:

  • Space efficiency: The compressed 2D BIT uses less memory than the standard 2D BIT. The standard 2D BIT stores a value for each cell in the 2D array, while the compressed 2D BIT stores a value for each row and column. This results in a significant reduction in memory usage for large arrays.
  • Faster construction time: The compressed 2D BIT can be constructed faster than the standard 2D BIT. The construction time of the compressed 2D BIT is O(n * m * log n * log m), where n and m are the dimensions of the array. This is faster than the construction time of the standard 2D BIT, which is O(n^2 * m^2 * log n * log m).
  • Faster range queries: The compressed 2D BIT can perform range queries faster than the standard 2D BIT. Range queries are typically faster because the compressed 2D BIT requires fewer memory accesses and fewer operations to calculate the query result.
  • Simplicity: The compressed 2D BIT is simpler to implement and use than the standard 2D BIT. The compressed 2D BIT requires only one-dimensional BIT operations, which are simpler and easier to understand than the two-dimensional operations required by the standard 2D BIT.

Disadvantages of compressed 2D Binary Indexed tree:

  • Limited functionality: The compressed 2D BIT can only support range sum queries and point updates. It cannot support other types of queries such as range minimum queries or range maximum queries.
  • Slower point updates: Point updates on the compressed 2D BIT are slower than on the standard 2D BIT. This is because updating a single cell in the compressed 2D BIT requires recalculating the sums for all cells in the row and column containing the cell being updated.
  • Loss of information: The compressed 2D BIT stores only the row and column sums of the 2D array, and not the individual cell values. This means that information about the individual cells is lost, which may be important in some applications.
  • More complex implementation: While the compressed 2D BIT is simpler to understand than the standard 2D BIT, its implementation can be more complex. The compressed 2D BIT requires additional bookkeeping to keep track of the row and column sums, and the compression process can be tricky to get right.


Like Article
Suggest improvement
Previous
Next
Share your thoughts in the comments

Similar Reads