본문 바로가기
Algorithm/Data Structure

[자료구조] 세그먼트 트리(Segment Tree)

by r4bb1t 2020. 4. 14.
반응형

세그먼트 트리(Segment Tree)는 부분 합을 트리 구조에 저장해서, 값이 바뀔 수 있는 상황에서도 부분합을 빠르게 구할 수 있는 방법이다.

 

세그먼트 트리를 그려보면 이런 느낌이다.

위의 대괄호 안에 들어있는 숫자는 각 노드의 인덱스이고, 아래 큰 숫자는 부분합의 범위이다.

즉 루트 노드인 [0]에는 배열의 0~9번째 값들의 합이 저장되어 있고, [11] 노드에는 배열의 5~6번째 값들의 합이 저장되어 있는 식이다.


구현 |

 

1. init

배열의 초기 값을 세그먼트 트리에 처음 넣는 함수이다.

이때는 루트 노드 [0]부터 아래로 내려간다.

한 노드에 값을 채울 때는 자식 노드에 값을 먼저 채우고 그 뒤에 그 합을 쓰면 되는 것이다.

그런 식으로 말단 노드 (예를 들어, 6번째부터 6번째까지의 합 노드 = 그냥 6번째 값 노드) 까지 내려갔다 오면 된다.

루트 노드가 [0]일 때 [index]의 자식 노드는 [index * 2 + 1], [index * 2 + 2]다.

이를 이용해서 일단 이렇게 간단하게 짜면 된다.

int init(int S, int E, int index)
{
    if (S == E)
    {
        return segTree[index] = array[S];
    }
    return segTree[index] = init(S, (S + E) / 2, index * 2 + 1) + init((S + E) / 2 + 1, E, index * 2 + 2);
}

어떤 배열의 0번째부터 n번째까지의 값을 세그트리에 넣고 싶다면 init(0, n, 0); 을 쓰면 된다.

 

2. change

배열 내에서 값이 변하면 트리에도 적용시켜주는 함수가 필요하다.

이 때는 반대로 말단 노드에서 위로 계속 올라가야 하는데, 내가 바꾸려는 값이 들어있는 말단 노드가 어디인지 못 찾겠다.

그래서 init할 때 해당 배열의 값이 트리의 어느 인덱스에 있는지 저장하는 변수를 하나 만들어준다.

int init(int S, int E, int index)
{
    if (S == E)
    {
        treeIndex[S] = index;
        return segTree[index] = array[S];
    }
    return segTree[index] = init(S, (S + E) / 2, index * 2 + 1) + init((S + E) / 2 + 1, E, index * 2 + 2);
}

한 줄 추가해주면 된다.

그럼 위의 treeIndex를 갖고 말단 노드 값을 바꿔주고, 그 부모 노드도 바꿔주고, 다시 그 부모 노드도 바꿔줄 수 있다.

void change(int index, int newVar)
{
    array[index] = newVar;
    segTree[treeIndex[index]] = newVar;
    int i = (treeIndex[index] - 1) / 2;
    while (i >= 0)
    {
        segTree[i] = segTree[i * 2 + 1] + segTree[i * 2 + 2];
        if (i == 0)
            break;
        else
            i = (i - 1) / 2;
    }
}

index는 바꿔줄 배열의 값의 index고, newVar은 새로운 값이다.

우선 array와 말단 노드 값을 바꿔준 후 루트 노드까지 계속 올라가며 바꿔주면 된다.

 

3. sum

결론적으로 구간 합을 구해야 하는데, 예를 들어 2~7번째 배열의 부분합을 구해야 한다면 세그트리에서는 아래 표시해놓은 노드들의 값을 더하면 되는 접근이다.

 

int sum(int start, int end, int S, int E, int index)
{
    if (start > E || end < S)
    {
        return 0;
    }
    else if (start >= S && end <= E)
    {
        return segTree[index];
    }
    else
    {
        return sum(start, (start + end) / 2, S, E, index * 2 + 1) + sum((start + end) / 2 + 1, end, S, E, index * 2 + 2);
    }
}

start, end는 현재 함수에서 보고 있는 구간이고, S와 E는 합을 구하고 싶은 구간이다. 그래서 함수를 호출할 때도 S와 E는 변하지 않고 start와 end만 바꿔준다.

지금 보고 있는 구간이 S~E에 포함되어 있으면 그냥 그 구간의 합을 반환하고,

반쪽만 걸쳐(?) 있으면 지금 구간을 반으로 나눠서 다시 함수에 넣어준다.

 

응용하면 구간 합 뿐만 아니라 최소/최댓값 등 많은 부분에서 유용하게 쓸 수 있다. 세그트리 조아~


관련 문제 |

[BOJ] 구간 합 구하기 (https://www.acmicpc.net/problem/2042)

[BOJ] 최솟값 (https://www.acmicpc.net/problem/10868)

[BOJ] 최솟값과 최댓값 (https://www.acmicpc.net/problem/2357)

 

반응형

'Algorithm > Data Structure' 카테고리의 다른 글

[자료구조] 트라이(Trie)  (0) 2023.05.18
[자료구조] sparse table  (3) 2020.10.09
[자료구조] 큐(Queue)  (0) 2019.11.11
[자료구조] 스택(Stack)  (2) 2019.11.03

댓글