yoongrammer

펜윅 트리(Fenwick Tree, Binary Indexed Tree, BIT) 개념 및 구현 본문

자료구조 (Data structure)

펜윅 트리(Fenwick Tree, Binary Indexed Tree, BIT) 개념 및 구현

yoongrammer 2022. 11. 20. 17:49
728x90

목차

펜윅 트리(Fenwick Tree, Binary Indexed Tree, BIT)


펜윅 트리(Fenwick Tree)는 세그먼트 트리의 변형으로 수열의 구간 합을 빠르게 구할 수 있도록 고안된 자료 구조입니다.

펜윅 트리는 Binary Indexed Tree라고도 하며, 줄여서 BIT라고 합니다.

 

펜윅 트리는 세그먼트 트리처럼 O(logN) 시간으로 구간 합을 구할 수 있으며 세그먼트 트리에 비해 적은 공간이 필요하고 구현하기 쉽다는 장점이 있습니다.

펜윅 트리 구성


배열 A를 가지고 세그먼트 트리를 만들면 다음과 같습니다. 각 노드에는 구간합 정보가 들어 있습니다.

위 세그먼트 트리에서 각 층에에서 짝수 인덱스를 무시하면 아래와 트리를 얻을 수 있습니다.

이러한 구조를 펜윅 트리라고 합니다.

주어진 n개의 배열을 가지고 세그먼트 트리를 만들려면 2n-1만큼의 배열이 필요하지만 펜윅트리는 n개의 배열만으로 구현이 가능합니다.

 

펜윅 트리의 홀수 번째 인덱스는 리프 노드로써 배열 값을 저장하고 있고, 짝수번째는 내부 노드로써 블록 구간만큼의 구간 합 정보가 저장되어 있습니다.

 

펜윅 트리 구현


비트 연산을 알고 있다면 구현이 쉬운 편입니다.

편의를 위해 1 기반 인덱싱을 사용합니다.

 

0이 아닌 최하위 비트

펜윅 트리를 구현하기 전 주어진 값에 0이 아닌 최하위 비트를 알아야 합니다.

다음은 주어진 수에 최하위 비트를 나타냅니다. 최하위 비트는 빨강으로 표시함.

3 = (11)₂

5 = (101)₂

6 = (110)₂

8 = (1000)₂

 

i 값에 최하위 비트는 i & -i이 됩니다.

다음은 i = 6 (110)₂에서 최하위 비트를 가져오는 과정입니다.

i = 110

~i = 001
-i = ~i + 1
    = 001 + 1
    = 010

i & -i = 110 & 010
        = 010

이 최하위 비트를 가져오는 연산은 sum과 add연산에 사용됩니다.

 

Sum

인덱스 1부터 i까지 부분 합을 구하는 연산입니다.

sum(13) 과정

위 예에서는 1 ~ 13 까지 부분합을 구하기 위해 13 = (1101)₂, 12 = (1100)₂ 및 8= (1000)₂ 위치의 값을 합산하여 부분합을 구하게 됩니다.

  • sum(13) = BIT[13] + BIT[12] + BIT[8] = 6 + 8 + 28 = 42

이 위치는 최하위 비트를 빼면서 찾을 수 있습니다.

def sum(i):
	ret = 0
	while i > 0:
		ret += tree[i]
		i -= (i & -i)
	
	return ret

Add

i번째 배열에 값을 추가하는 연산입니다.

이때 i번째 인덱스를 포함하는 모든 노드들도 업데이트합니다.

A[3] += 2 과정

위 예는 A[3] += 2 연산을 하기 위해 BIT 배열에 3 = (11)₂, 4 = (100)₂, 8 = (1000)₂, 16 = (10000)₂ 위치에 값을 변경해 줍니다.

이 위치는 최하위 비트를 더하면서 찾을 수 있습니다.

def add(i, num):
	while i <= n:
		tree[i] += num
		i += (i&-i)

 

sum, add 연산을 가지고 아래 연산을 구현할 수 있습니다.

  1. Point Update & Range Query
  2. Range Update & Point Query
  3. Range Update & Range Query

여기선 1, 2를 알아보도록 하겠습니다.

728x90

 

Point update & Range query


Point update

point update는 i 번째 배열 값을 x만큼 증가시키는 작업을 합니다

def update(i, x):
	add(i, x)

 

Range query

range query는 구간합을 구하는 연산입니다.

 

펜윅 트리는 세그먼트 트리와 다르게 구간합 정보가 일부만 저장되어 있어 부분합 정보만 가져올 수 있습니다.

(여기서 i번째 인덱스에 대한 부분 합은 0~i 까지 합을 의미합니다.)

때문에 구간 합을 구하려면 추가 연산이 필요 합니다.

구간합 [l, r]을 구하기 위해 [0, r] 과 [0, l-1]의 부분합을 빼는 연산을 해야 합니다.

인덱스 L부터 R까지 구간 합을 구하는 연산입니다.

def range_query(l, r):
	return sum(r) - sum(l-1)

Range update & Point query


Range update

Range Update는 [l, r] 범위의 값을 x만큼 증가시키는 연산을 합니다.

 

[l, r] 범위의 값을 x만큼 증가시키는 범위 업데이트를 해주기 위해선 다음 두 가지 포인트 연산을 해주면 됩니다.

  • add(l, x), add(r+1, -x)
def range_update(l, r, x):
	add(l, x)
	add(r+1, -x)

다음 연산을 사용하여 point update도 가능합니다.

  • range_update(i, i, val)

다음은 0으로 초기화된 A배열을 가지고 [4, 6] 범위의 값을 5만큼 증가시키는 예입니다.

add(4, 5), add(7, -5)가 수행된 이후 모습

Point query

Point query는 Range update 이후 i번째 위치에 있는 값을 가져오는 연산을 합니다.

[l, r] 범위에 Range update 연산 이후 i 위치에 따라 다음 특성이 있습니다. (위 그림을 보면 이해하기 쉬움)

  • i < l
    • l 이전에는 증가 작업이 없었으므로 sum(i)의 결과는 0이 됩니다.
  • l ≤ i ≤ r
    • l 범위부터 x만큼 증가 작업이 있었으므로 sum(i)의 결과는 x가 됩니다.
  • r < i
    • r+1 부터 x만큼 감소 작업이 있었으므로 l범위부터 시작된 x만큼 증가되는 작업이 상쇄됩니다.

이런 특성이 있기 때문에 A[i] 값을 얻기 위해서는 다음 연산만 해주면 됩니다.

  • sum(i)
def point_query(i):
	return sum(i)

 

728x90
Comments