[Algorithm] Data Structure - Fenwick Tree

자료구조 펜윅트리

Posted by lim.Chuck on September 17, 2024

Fenwick Tree?

Fenwick Tree(또는 Binary Indexed Tree)는 배열의 값을 효율적으로 업데이트하고, 구간 합(prefix sum)을 빠르게 구할 수 있는 자료구조입니다. 이 자료구조는 다음과 같은 작업을 자주 해야 할 때 유용합니다:

  1. 포인트 업데이트: 배열의 특정 원소 값을 업데이트하는 작업.
  2. 구간 합 쿼리: 배열의 특정 위치까지의 합을 구하는 작업.

Fenwick Tree는 이러한 작업을 O(log n) 시간 복잡도로 수행할 수 있는데, 일반적인 배열에서 이 작업을 수행하면 O(n) 시간이 걸릴 수 있습니다.

사용 예시

  • 누적 빈도 테이블: 구간 합을 계산하는 데 사용.
  • 구간 합 쿼리: 배열에 대한 업데이트와 쿼리를 효율적으로 처리.
  • 역전 횟수 세기: 배열에서 역전된 요소 쌍의 개수를 계산할 때.
  • 경쟁 프로그래밍: Fenwick Tree는 경쟁 프로그래밍에서 구간 쿼리 및 업데이트 문제를 해결하는 데 자주 사용됩니다.

Fenwick Tree 동작 방식

구조:

  • Fenwick Tree는 bit[]라는 배열에 저장되며, 여기서 bit[i]는 배열의 인덱스 i까지의 구간 합에 대한 정보를 포함합니다.
  • 핵심 개념은 Fenwick Tree의 각 원소가 배열의 특정 부분 집합에 대한 합계를 나타낸다는 것입니다.

작업:

  1. 원소 업데이트: 배열의 특정 원소 값을 업데이트할 때, 이 인덱스를 포함하는 모든 노드를 조정합니다.
  2. 구간 합 쿼리: 배열의 특정 위치까지의 합을 구하려면, 트리를 역방향으로 탐색하여 관련 노드들의 값을 더합니다.

JavaScript

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class FenwickTree {
  constructor(size) {
    this.bit = new Array(size + 1).fill(0);
    this.size = size;
  }

  // 인덱스 `i`에 `value` 값을 더하는 업데이트
  update(i, value) {
    while (i <= this.size) {
      this.bit[i] += value;
      i += i & -i; // 다음 관련 노드로 이동
    }
  }

  // 1번 인덱스부터 `i`번 인덱스까지의 구간 합
  query(i) {
    let sum = 0;
    while (i > 0) {
      sum += this.bit[i];
      i -= i & -i; // 부모 노드로 이동
    }
    return sum;
  }

  // `l`부터 `r`까지의 구간 합
  rangeQuery(l, r) {
    return this.query(r) - this.query(l - 1);
  }
}

// 사용 예시:
let fenwick = new FenwickTree(10);
fenwick.update(3, 5);
fenwick.update(5, 2);
console.log(fenwick.rangeQuery(1, 5)); // 결과: 7

Rust

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
struct FenwickTree {
    bit: Vec<i32>,
}

impl FenwickTree {
    // 주어진 크기의 Fenwick Tree 생성
    fn new(size: usize) -> FenwickTree {
        FenwickTree {
            bit: vec![0; size + 1],
        }
    }

    // 인덱스 `i`에 `value` 값을 더하는 업데이트
    fn update(&mut self, mut i: usize, value: i32) {
        while i < self.bit.len() {
            self.bit[i] += value;
            i += i & (!i + 1);  // 다음 관련 노드로 이동
        }
    }

    // 1번 인덱스부터 `i`번 인덱스까지의 구간 합
    fn query(&self, mut i: usize) -> i32 {
        let mut sum = 0;
        while i > 0 {
            sum += self.bit[i];
            i -= i & (!i + 1);  // 부모 노드로 이동
        }
        sum
    }

    // `l`부터 `r`까지의 구간 합
    fn range_query(&self, l: usize, r: usize) -> i32 {
        self.query(r) - self.query(l - 1)
    }
}

fn main() {
    let mut fenwick = FenwickTree::new(10);
    fenwick.update(3, 5);
    fenwick.update(5, 2);
    println!("{}", fenwick.range_query(1, 5));  // 결과: 7
}

핵심 요점:

  • Fenwick Tree는 업데이트와 쿼리 작업을 로그 시간에 수행하여, 데이터셋에 대한 빈번한 수정 및 쿼리가 필요한 문제에 적합합니다.
  • 같은 작업을 할 수 있는 세그먼트 트리 등 다른 자료구조에 비해 구현이 간단하지만, 더하기와 같은 역연산이 가능한 작업에만 사용 가능합니다.

Fenwick Tree?

A Fenwick Tree (also known as a Binary Indexed Tree) is a data structure that allows efficient updates and prefix sum queries on an array of numbers. It is particularly useful when we need to frequently perform the following operations:

  1. Point Update: Update a single element in the array.
  2. Prefix Sum Query: Compute the sum of elements from the start of the array to a given index.

The Fenwick Tree provides these operations in O(log n) time, which is faster than a simple array where these operations might take O(n) time.

Use Cases

  • Cumulative Frequency Tables: Calculating prefix sums.
  • Range Sum Queries: Efficient queries and updates on an array.
  • Inversion Counting: Counting the number of inversions in an array.
  • Competitive Programming: Fenwick trees are commonly used in contests for range queries and updates.

How the Fenwick Tree Works

Structure:

  • The tree is stored as an array bit[] where bit[i] contains information about the prefix sum of elements up to index i.
  • The key observation is that each element of the Fenwick Tree represents the sum of a subset of the array’s elements.

Operations:

  1. Update an Element: To update an element, adjust the values of the nodes that cover this index.
  2. Prefix Sum Query: To get the sum of elements up to index i, traverse the tree backwards and sum the values stored at relevant nodes.

Fenwick Tree in JavaScript

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class FenwickTree {
  constructor(size) {
    this.bit = new Array(size + 1).fill(0);
    this.size = size;
  }

  // Update the value at index `i` by `value`
  update(i, value) {
    while (i <= this.size) {
      this.bit[i] += value;
      i += i & -i; // Move to the next responsible node
    }
  }

  // Get the prefix sum from 1 to `i`
  query(i) {
    let sum = 0;
    while (i > 0) {
      sum += this.bit[i];
      i -= i & -i; // Move to the parent node
    }
    return sum;
  }

  // Range query sum from l to r
  rangeQuery(l, r) {
    return this.query(r) - this.query(l - 1);
  }
}

// Example usage:
let fenwick = new FenwickTree(10);
fenwick.update(3, 5);
fenwick.update(5, 2);
console.log(fenwick.rangeQuery(1, 5)); // Output: 7

Fenwick Tree in Rust

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
struct FenwickTree {
    bit: Vec<i32>,
}

impl FenwickTree {
    // Create a new Fenwick Tree with a given size
    fn new(size: usize) -> FenwickTree {
        FenwickTree {
            bit: vec![0; size + 1],
        }
    }

    // Update the value at index `i` by `value`
    fn update(&mut self, mut i: usize, value: i32) {
        while i < self.bit.len() {
            self.bit[i] += value;
            i += i & (!i + 1);  // Move to the next responsible node
        }
    }

    // Get the prefix sum from 1 to `i`
    fn query(&self, mut i: usize) -> i32 {
        let mut sum = 0;
        while i > 0 {
            sum += self.bit[i];
            i -= i & (!i + 1);  // Move to the parent node
        }
        sum
    }

    // Range query sum from l to r
    fn range_query(&self, l: usize, r: usize) -> i32 {
        self.query(r) - self.query(l - 1)
    }
}

fn main() {
    let mut fenwick = FenwickTree::new(10);
    fenwick.update(3, 5);
    fenwick.update(5, 2);
    println!("{}", fenwick.range_query(1, 5));  // Output: 7
}

Key Points:

  • The Fenwick Tree enables efficient update and query operations in logarithmic time, making it suitable for problems that involve frequent modifications and queries on a dataset.
  • It’s simpler to implement than other data structures that achieve the same goals, like segment trees, but with some limitations (it only works with operations that have inverses, like sum).