세그먼트 트리 (구간합, 구간곱, 최소값, 최대값)
세그먼트 트리 (구간합, 구간곱, 최소값, 최대값)
세그먼트트리가 처음에 봤을 때는 어려웠었는데
1년이 지나서 다시 보니까 별거 아니다.
특정 구간의 최소값을 구하는데, 질문이 1개면 그냥 일일이 세면 된다.
질문이 10만개면 달라진다.
메모이제이션을 해야 하는데, 세그멘테이션 트리를 이용한다.
N = 8, value = [2,1,5,3,2,4,10,5] 에서 구간 최소값 을 구하는 Segment tree
leaf Node가 N개 구성된다. ( tree[8] ~ tree[15] )
사실, 2^(HEIGHT-1) 이다. 따라서, N이 7일 때도 leaf는 8개
트리 어떻게 만드나?
초기화
1
2
3
4
5
final int HEIGHT = (int) Math.ceil(Math.log(N) / Math.log(2)) + 1; // 4
final int S = Math.pow(2,HEIGHT-1) // 8
long[] tree = new long[S * 2];
Arrays.fill(tree,Integer.MAX_VALUE); // 최소값을 넣어줄 것이기 때문에 높은 값으로 초기화.
leaf 노드에 값을 넣어준다. (tree[8] ~ tree[15])
1
2
3
4
5
6
values = {2,1,5,3,2,4,10,5}
for(int i = 0; i < N; i++) {
index = Math.pow(2,HEIGHT-1) + i;
tree[index] = values[i];
}
parents 구하기 (tree[1] ~ tree[7])
1
2
3
4
5
6
for(int i = S-1; i >= 1;i--) { // 7 6 5 4 3 2 1
int leftChild = i * 2;
int rightChild = i * 2 + 1;
tree[i] = Math.min(leftChild, rightChild);
}
값 초기화에는 여러 방법들이 있다. Top-down 방식보다 위 방법이 더 효율적임.
구간 최소값 구하기.
구간의 최소값 트리를 만들었다.
1 ~ 4
최소값 또는 5 ~ 6
최소값을 직접 세지 않아도 메모이제이션 되어 있다.
2 ~ 5
최소값은 어떻게 구하는가.
min(2, 3~4, 5) 로 해결하면 된다.
하늘색 부분은 return 잘 시켜주면 됨.
1
2
3
4
5
6
7
8
9
10
11
private long get(int idx, int l, int r, int from, int to) {
if(index >= tree.length-1) return Integer.MAX_VALUE; // tree idx 범위 밖
if(r < FROM || l > TO) return Integer.MAX_VALUE; // l ~ r 이 2 ~ 5와 겹치지 않음.
else if(FROM <= l && TO >= r) return tree[idx]; // l ~ r이 2 ~ 5 에 완전포함
// l ~ r 이 2 ~ 5 와 부분적으로 겹칠 경우 아래 코드 수행
int mid = (l + r) / 2;
return Math.min(get(idx*2, l, mid, int from, int to), get(idx*2 + 1, mid+1, r, from, to));
}
위 코드의 4번째 줄에서 return하는 경우는 3가지다.
- l = 2, r = 2
- l = 3, r = 4
- l = 5, r = 5
( 최대값의 경우 max 이고, 구간합은 덧셈, 구간곱은 곱셈을 해주면 된다. )
update ?
기존의 노드를 바꿔야 하는 경우가 있다.
초기화와 똑같다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// ex) 2번째 값을 10으로 바꾼다. [2,1,5,3,2,4,10,5] -> [2,10,5,3,2,4,10,5]
int idx = 2;
int value = 10;
update(2,10);
//...
private void update(int idx, int value) {
int idx += S - 1; // 2번째 값은 tree의 index상으로 9임.
tree[idx] = value;
idx /= 2;
while(idx >= 1) {
int leftChild = idx * 2;
int rightChild = idx * 2 + 1;
tree[idx] = Math.min(tree[leftIdx], tree[rightIdx]);
idx /= 2;
}
}
value 범위가 int 범위를 초과하는지 항상 확인 할것.
문제 잘 읽자…
세그먼트 문제를 풀면서 런타임 에러도 뜨고, 많이 틀렸다.
역시나 문제를 잘못 읽었다…
습관이 참 무섭다. 집이라고 대충대충 보면 안되겠다.
오픽 시험이 이틀 남았는데, 영어 공부하기가 너무 귀찮아서 세그먼트를 풀었다..
Sorry, Eva….
이 기사는 저작권자의
CC BY 4.0
라이센스를 따릅니다.