펜윅나무
Fenwick tree펜윅나무 이진 인덱스 트리 | ||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
![]() | ||||||||||||||||
유형 | 바이오노말목 | |||||||||||||||
발명된 | 1989 | |||||||||||||||
발명자 | 보리스 랴브코 | |||||||||||||||
큰 O 표기법의 시간 복잡도 | ||||||||||||||||
|
![]() | 이 글의 선두 부분은 그 내용을 충분히 요약하지 못할 수 있습니다.(2023년 7월) |
펜윅 트리(Fenwick tree) 또는 이진 인덱스 트리(BIT)는 값을 효율적으로 업데이트하고 값 배열에서 접두사 합을 계산할 수 있는 데이터 구조입니다.
이 구조는 1989년 보리스[1] 랴브코(Boris Ryabko)에 의해 제안되었으며 1992년 [2]추가 수정이 발표되었습니다.1994년 [3]그의 기사에서 이 구조를 묘사한 피터 펜윅의 이름을 따서 펜윅 나무라는 이름으로 알려지게 되었습니다.
Fenwick 트리는 평평한 값 배열과 비교할 때 값 업데이트와 접두사 합 계산의 두 가지 작업 간에 훨씬 더 나은 균형을 유지합니다.n개의 n 값으로 된 플랫 어레이는 값 또는 접두사 합을 저장할 수 있습니다.첫 번째 경우, 계산 프리픽스 합은 선형 시간을 필요로 하고, 두 번째 경우, 배열 값을 업데이트하는 것은 선형 시간을 필요로 합니다(두 경우 모두 다른 연산은 일정한 시간 내에 수행될 수 있음).Fenwick 트리를 사용하면O( n){\(\log n 시간 에 두 작업을 모두 수행할 수 있습니다.이는 값을 의+ 1개의 n개의 노드가 트리로 나타내므로 트리의 각 노드의 값은 상위(포함) 인덱스에서 노드(전용) 인덱스까지의 배열의 접두사 합입니다.트리 자체는 암시적이며 n개의{\ n 의 배열로 저장할 수 있으며, 배열에서 암시적 루트 노드가 생략됩니다.트리 구조를 사용하면 O ){\ O n 노드 만 사용하여 값 검색, 값 업데이트, 접두사 합 및 범위 합 작업을 수행할 수 있습니다.
동기
값 배열이 주어지면 일부 연관 이진 연산에 따라 각 인덱스까지의 값의 실행 합계를 계산하는 것이 좋습니다(정수의 덧셈이 단연코 가장 일반적임).Fenwick 트리는 기본 값 배열에 대한 변경 사항을 허용하고 모든 추가 쿼리가 이러한 변경 사항을 반영하도록 하는 것 외에도 어떤 인덱스에서도 실행 중인 총합을 쿼리할 수 있는 방법을 제공합니다.
펜윅 트리는 특히 산술 코딩 알고리즘을 구현하도록 설계되어 있는데, 산술 코딩 알고리즘은 생성된 각 심볼의 카운트를 유지하고 이를 주어진 심볼보다 작은 심볼의 누적 확률로 변환해야 합니다.지원하는 운영 개발은 주로 이 경우에 사용하는 것이 동기부여가 되었습니다.
묘사
펜윅 트리는 n개의{\ n개의 값을 하나의 기반 A{\ A을 (를) 고려함으로써 가장 쉽게 이해할 수 있습니다.해당 펜윅 트리에는 암시적 노드가 0인 의 n개의 노드가 .트리의 각 k k}에는 k k의 서로 다른 거듭제곱이 2인 합에 해당하는 인덱스가 있는 노드가 포함되어 있습니다( 합이 0인 k = {\ k= 포함).예를 들어 k = {\ k=에 1 = = = , 1==4= k {\ k에 3 + + + ... 36주어진 노드의 부모는 인덱스의 마지막 설정 비트(LSB)를 지움으로써 찾을 수 있으며, 이는 합에서 2의 최소 거듭제곱에 해당합니다.예를 들어 6 = 110의 부모는 4 = 100입니다.
다음 다이어그램은 15개 요소 배열 A에 해당하는 16개 노드 펜윅 트리의 구조를 보여줍니다.

A( ] = { [ ]} = + {\ A]=\{라고 . i{\ i의 노드 값은 A {\ A의 값 범위 합에 해당합니다. 즉, 현재 노드의 인덱스까지 부모 인덱스 다음으로 시작하는 A의 값입니다. A( ]{\ A는 현재 노드에 대한 "책임 범위"로 되며 (i )= (i (- {\)= \ 여기서 & 는 비트와이즈 AND를 나타냅니다) 값으로 됩니다.이 범위의 인덱스는 i{\ i의 하위 인덱스와 직접적으로 일치하지 않습니다. 예를 들어 노드 2의 책임 는 A = { [ {\ A2]=\{ 그러나 노드 1은 노드 2의 자식이 아닙니다.루트 노드 0에는 값이 0인 빈 A ] = { } {\ A]=\{\})의 합이 포함되어 있습니다.
값 배열을 통해 펜윅 트리를 구축하는 초기 프로세스는 O logon{\)} 시간 에 실행됩니다.
최대/분 범위 쿼리
인덱스 = 2 {\k는 홀수)(k는 홀수)인 다음 간격으로 min/max를 추적하면 [r]{\ 범위에서max/min을 찾을 수 있습니다.
이것은 다음을 의미합니다.
- 하위 트리의 첫 번째 노드에서 이 노드까지 모든 노드를 한 번 보고 왼쪽 자식을 따라 리프 노드를 칠 때까지 이동합니다.왼쪽 자식이 없으면 위치를 변경하지 않습니다.
- 이 하위 트리의 위치 오른쪽에 있는 모든 노드를 보면 루트 노드를 제외한 부분 트리의 일부입니다.
특별한 경우는 BIT1과 BIT2의 일부인 i {\ k2가 max/min인 경우입니다.
주요 아이디어:max가 BIT1과 BIT2에 모두 있는 경우 루트 노드여야 합니다.그렇지 않으면 왼쪽이나 오른쪽에 있습니다.어떤 시점에서, 사람들은 max가 리프 노드 또는 루트 노드라는 것을 마주치게 될 것입니다; 이러한 사고방식을 반복함으로써.
트리를 구축할 때, 각 값에 대해 BIT1, BIT2 또는 둘 다에 있는지 확인합니다.BIT1[i]에 있으면 = {\]=)A BIT2도 입니다.이후 인덱스를 val = val + val & (-val)로 업데이트합니다.배열의 SIZE가 적중될 때까지 반복합니다.
[ 범위 검색을수행할 때는 값이 합법적인 범위 내에 포함되지 않은 BIT1 및 BIT2로 들어가지 않아야 합니다.
일반[ ]{\ max/min 쿼리는 합/곱과 동일한 방식으로 구현할 수 있습니다.
총 범위 쿼리
[0,r]에서 범위 합에서 2^i = (-val) & val; 1을 빼서 모든 오른쪽 부모를 우회한 후 첫 번째 왼쪽 부모로 이동합니다.그 다음엔 아이에게, 아이가 어디서 왔는지와 가장 가까운 지수를 가지고 있습니다.왼쪽 아이들을 아래로 향하게 하고, 아래로 향하게 하고, 그렇지 않으면.한 사람은 두 번 더 추가하는 것을 피합니다. 한 사람은 항상 왼쪽으로 가고, 결코 오른쪽으로 가지 않기 때문입니다. 한 사람은 오른쪽에 추가만 됩니다(index, delta).
어떤 일이 일어나고 있는지는 하위 트리의 가장 가까운 루트로 이동하는 것입니다. 그 다음에 하위 트리의 가장 가까운 루트로 이동하는 것입니다. 그 다음에 하위 트리의 가장 많은 값을 포함하는 노드에서 하위 인덱스로 이동합니다. 같은 하위 트리에 하나가 있습니다.하위 트리를 덮기 위해 루트의 여러 자식을 통과할 수도 있습니다.이 하위 트리가 가려지면 한 사람은 더 큰 하위 트리로 이동합니다.만약 어떤 사람이 어린아이에게 내려가지 않았다면, 지수 1에 대해 오른쪽으로 가는 위험을 감수했을 것입니다. 이는 2^i = (-val) & valto - 를 빼면 같은 값이 두 번 더 추가된다는 것을 의미합니다.
맨 아래로 내려가지 않았다면, 먼저 왼쪽 아이에게 가야 할 때, 같은 가치를 여러 번 더할 위험을 감수했을 것입니다.
[l,r] 범위 합을 구하려면 합(0,r) - 합(0,l)만 구하면 됩니다.
합산포인트업데이트
i{\ i의 펜윅 트리의 값을 업데이트하려면 다음을 수행합니다.
- 델타 δ= [ ] A [ ] {\ = {new{old을(를) 계산합니다.
- 그러면 i i인 반면:
- LSB를 추가하여 인덱스 업데이트: ←+ (&( -i){\ i i
- i i의 값에δ {\을(를) 추가합니다.
값을 더할 때는 항상 오른쪽으로 가야 합니다. 왜냐하면 합을 항상 왼쪽으로 두면 같은 값을 두 번 합하는 위험을 피할 수 있기 때문입니다.
2^i = -val & val을 더할 때, 가장 가까운 부모로 간단히 이동합니다.
트리 만들기
다음과 같이 진행합니다.
- A{\ A을 (를) 트리 값이 포함된 T{\ T로 복사합니다.
- 각 인덱스 i에 대해 1부터 n까지:
- j =+ (&( -) j=라고 .이 노드는 책임 범위에 A {\ A이(가) 된i {\ i보다 큰 첫 번째 노드입니다.
- j j인 경우 j에서 노드의 업데이트 값: [ ] ← [] + [ ]{\ T T
각 업데이트는 인덱스당 최대 한 번 발생하므로 결과 알고리즘은 O{\ O 시간 입니다.이 작업은 원래 배열에서 제자리에서 수행할 수도 있으므로 T {\Tn]}에 복사 및 추가 저장소가 제거됩니다.
유사한 작업을 수행하여 n에서 1로 역반복하고 각 시간 단계에서i {\i}의 값에서 =i + (i &(-i) {\= + ())}의 값을 빼서 Fenwick 트리를 의 주파수 배열 A ] {\ An{\displaystyle A로 다시 변환할 수 있습니다.
다차원으로 확장
2D 펜윅 트리(2D BIT)는 트리의 각 노드가 1D 펜윅[5] 트리를 포함하고 2D 행렬 A[m,n]의 하위 행렬에 대한 범위 합을 저장하는 1D 펜윅 트리로 구성될 수 있습니다.트리 표현은 암시적으로 유지되며 데이터는 배열의 위치 (i, j)가 i번째 펜윅 트리의 j번째 노드에 해당하는 mxn 배열에 저장됩니다.
점 업데이트 및 범위 쿼리는 1D 경우와 유사하게 구현되지만 중첩 루프(내부 루프의 열 차원(하위 트리)을 따라 반복하고 외부 루프의 행 차원(메인 트리)을 따라 반복)를 사용합니다.
이 아이디어는 범위 쿼리 및 포인트 업데이트(텐서가 각 축을 따라 크기 n이라고 가정)를 위한 시간 O n ) On와 함께 임의의 수의 차원 d를 가진 텐서로 확장될 수 있습니다.
의사코드
펜윅 트리에서 두 가지 주요 연산인 쿼리와 업데이트의 간단한 의사코드 구현은 다음과 같습니다.
함수 쿼리(tree, index)는 합 := 0인 반면 인덱스 > 0 도합 += tree[index] 인덱스 -= lso(index) 반환 합 함수 업데이트(tree, index, value)는 인덱스 < size(tree) do tree[index] += value 인덱스 += lso(index)입니다.
함수 ( {\text은 n n의 최하위 1비트 또는 마지막 집합 비트를 계산하거나, n n의 약수이기도 한 2의 최대 거듭제곱을 계산합니다. 예를 들어 ( = ) =이진법 표현과 같이 ( 2)= 2 =4 {\1}}, 00_}) ={2}= 입니다.이 기능은 비트 와이즈 AND 연산을 통해 간단히 코드로 구현할 수 있습니다.lso(n) = n & (-n)
, 부호가 있는 정수 데이터 [3]유형을 가정합니다.
시공
펜윅 트리를 구성하기 위한 하나의 순진한 알고리즘은 널 값으로 트리를 초기화하고 각 인덱스를 개별적으로 업데이트하는 것으로 구성됩니다.이 솔루션은 O log ){\ O { 시간 에 작동하지만 O{\ O 이 가능합니다.
함수 구성(값)은 모든 인덱스에 대한 트리 := 값이며 트리의 상위 값입니다.인덱스 : = 인덱스 + ls(인덱스)(부모인 경우)인덱스 < 크기(트리) 다음 트리[부모]인덱스] += 값 반환 트리
적용들
![]() |
가장 긴 연속 증가
배열 {\ 5,1, 2, 4, 6] {\displaystyle [8, 5, 7, 3, 1, 2, 4, 6을(를) 고려합니다.
각 에 대해A {\]}:
] < ){\ ]= x [] + {\ A + s.t. array[] <= array[i].
array[x] <= array[i]>는 i = array[index in list]이기 때문에 보장됩니다.
실행 예제:
- A[array[0] = A[8] = 최대값(0 <= x < 8) A[x] + 1 = 최대값(0,0,0,0,...,0) + 1 = 1
- A[array[1] = A[5] = 최대값(0 <= x < 5) A[x] + 1 = 최대값(0,0,0,0,0,...,0) + 1 = 1
- A[array[2] = A[7] = 최대값(0 <= x < 7) A[x]+ 1 = 최대값(A[0],[1],A[2],A[3],A[4],A[5],A[6]) + 1 = A[5] + 1 = 1 + 1 = 2
- A[array[3] = A[3] = 최대값(0 <= x < 3) A[x] + 1 = 최대값(0,0,0...,0) + 1 = 1
- A[array[4] = A[1] = 최대값(0 <= x < 1) A[x] + 1 = 최대값(A[0]) + 1 = 1
- A[array[5]] = A[2] = 최대값(0 <= x < 2) A[x] + 1 = 최대값(0,1) + 1 = 2
- A[array[6] = A[9] = 최대값(0 < x < 9) A[x] + 1 = 최대값(0,1,2,1,0,1,0,2,1) + 1 = 2+1 = 3
- A[array[7] = A[4] = 최대값(0 <= x < 4) A[x] + 1 = 최대값(0,1,2,1) + 1 = 2+1 = 3
- A[array[8] = A[6] = 최대값(0 < x < 6) A[x] + 1 = 최대값(0,1,2,1,3,1) + 1 = 3+1 = 4
그것은 정답 1,2,4,6에 잘 해당합니다.
최대값이 너무 크면 정렬을 사용하여 숫자 압축을 사용할 수 있습니다.
i = 배열[정렬되지 않은 목록의 인덱스] 대신 i = indexInSortedList[정렬되지 않은 목록의 인덱스]입니다.
예를 들어 다음과 같습니다.
- 배열: 166,32,44,66,88
- 분류: 32,44,66,88,166
그러면:
A[indexInSortList[0]] = A[4] 이제 우리는 익숙한 문제로 돌아갑니다.
참고 항목
참고문헌
- ^ Boris Ryabko (1989). "A fast on-line code" (PDF). Soviet Math. Dokl. 39 (3): 533–537. Archived (PDF) from the original on 2019-07-17. Retrieved 2019-07-17.
- ^ Boris Ryabko (1992). "A fast on-line adaptive code" (PDF). IEEE Transactions on Information Theory. 28 (1): 1400–1404. Archived (PDF) from the original on 2019-07-14. Retrieved 2019-07-14.
- ^ a b Peter M. Fenwick (1994). "A new data structure for cumulative frequency tables". Software: Practice and Experience. 24 (3): 327–336. CiteSeerX 10.1.1.14.8917. doi:10.1002/spe.4380240306. S2CID 7519761.
- ^ Mircea Dima, Rodica Ceterchi (2015). "Efficient Range Minimum Queries using Binary Indexed Trees" (PDF). Archived (PDF) from the original on 2023-06-01. Retrieved 2023-07-10.
- ^ Pushkar Mishra (2013). "A New Algorithm for Updating and Querying Sub-arrays of Multidimensional Arrays" (PDF). Archived (PDF) from the original on 2023-04-07. Retrieved 2023-07-18.
- ^ Halim, Steven; Halim, Felix; Effendy, Suhendry. Competitive Programming. Vol. 1 (4th ed.). ISBN 9781716745522.