[알고리즘] 코테 유형 분석 24. 세그먼트 트리

최민길(Gale)·2023년 9월 7일
1

알고리즘

목록 보기
126/172

안녕하세요 이번 시간에는 세그먼트 트리에 대해 알아보는 시간을 갖도록 하겠습니다.

세그먼트 트리란 주어진 데이터들의 구간합과 데이터 업데이트를 빠르게 수행하기 위해 고안해낸 자료구조입니다. 세그먼트 트리는 크게 3단계로 구현할 수 있습니다.

  1. 트리 크기 구하기 : 리프 노드의 개수가 데이터의 개수(N) 이상이 되도록 일차원 배열을 생성합니다.
    --> 트리의 크기 = 2^(k+1) (k = 2^k >= N 을 만족하는 k의 최솟값)

  2. 원본 데이터 채우기 : 시작 인덱스를 2^k로 설정한 후 순서대로 원본 데이터를 입력합니다.
    --> 시작 인덱스 : 트리의 크기/2 -1

  3. 나머지 데이터 채우기 : 가장 뒤의 인덱스부터 시작하여 질의에 맞게 나머지 데이터를 로직대로 추가합니다.
    --> 구간합을 구하는 경우 : tree[idx/2] = tree[idx/2] + tree[idx];
    --> 최소 최대를 구하는 경우 : tree[idx/2] = Math.min(또는 max)(tree[idx/2], tree[idx])

  4. 인덱스 변환 : 현재 트리를 기준으로 입력받은 인덱스를 세그먼트 트리 인덱스로 변환합니다.
    --> 세그먼트 트리 idx = 주어진 질의 idx + 시작 인덱스 (주어진 질의 idx + 2^k -1)

  5. 두 노드의 값 스위칭 : 만약 문제에서 두 값의 순서를 서로 바꾸는 경우 시작 인덱스를 2씩 나누면서 조건에 맞게 트리에 데이터를 추가합니다.

  6. 질의값 구하기 : 구하고자 하는 질의값을 리턴합니다.

    1. 시작 인덱스%2 == 1 일 경우 해당 노드를 선택 후 시작 인덱스 --
    2. 종료 인덱스%2 == 0 일 경우 해당 노드를 선택 후 종료 인덱스 --
    3. 시작 인덱스 /= 2
    4. 종료 인덱스 /= 2
  7. 데이터 업데이트하기 : 구한 질의값을 출력합니다.

세그먼트 트리 문제는 집합 내의 원소들끼리 순서를 변경할 때의 질의값을 구하는 문제에서 주로 사용됩니다.

백준 2042(구간 합 구하기) 문제의 경우 중간의 수의 변경이 발생했을 때 시작에서 끝까지의 구간 합을 구하는 문제입니다. 중간의 수의 변경이 빈번하게 발생하기 때문에 데이터의 변경에도 시간이 비교적 적게 걸리는 세그먼트 트리를 이용합니다. 1차원 배열을 이용해 트리값을 초기화한 후 위의 단계대로 구간합을 구합니다. 이 때 두 노드를 바꿀 때 값과 시작 인덱스가 가리키는 값과의 차이를 시작 인덱스를 /= 2 한 트리에 지속적으로 더해줍니다. 즉 부모 노드에 두 값의 차이만큼 지속적으로 더해 구간합을 구해주며 질의값을 구할 때 선택된 노드의 값을 누적하여 더한 값을 출력합니다.

백준 10868(최솟값) 문제의 경우 정수 집합이 주어졌을 때 a번째 정수부터 b번째 정수 중 제일 작은 정수를 각각 구하는 문제입니다. 구간이 여러 개가 존재하기 때문에 세그먼트 트리를 이용합니다. 이 경우 두 노드를 바꾸는 경우는 없으며 부모 노드를 탐색하면서 최솟값을 구해 이를 리턴합니다.

백준 11505(구간 곱 구하기) 문제의 경우 N개의 수 중 중간의 수의 변경이 빈번하게 일어날 때 부분의 곱을 구하는 문제입니다. 구간 합 구하기와 마찬가지로 중간의 수의 변경이 빈번하게 발생하기 때문에 세그먼트 트리를 이용합니다. 여기서 주의할 점은 곱 연산이기 때문에 1차원 배열을 1로 초기화해주어야 하며 곱하기 연산을 *= 로 처리할 경우 % 연산이 먼저 처리되어 실질적으로 % 연산이 수행되지 않기 때문에 val = val * (수행 연산) % 나머지 이런 식으로 진행해야 합니다.

백준 11505 문제의 코드 예시를 통해 세그먼트 트리의 구현 코드를 보여드리겠습니다.

import java.util.*;
import java.io.*;

class Main{
    static int N,M,K;
    static int MOD = 1000000007;
    static long[] tree;

    public static void main(String[] args) throws Exception{
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine());
        N = Integer.parseInt(st.nextToken());
        M = Integer.parseInt(st.nextToken());
        K = Integer.parseInt(st.nextToken());

		// 1. 트리의 크기 구하기
        int h = 0;
        int l = N;
        while(l != 0){
            l /= 2;
            h++;
        }
        int size = (int) Math.pow(2,h+1);
        
        // 2. 원본 데이터 채우기(시작 인덱스 구하기)
        int start = size/2 -1;
        tree = new long[size+1];
        Arrays.fill(tree,1);
        for(int i=1;i<=N;i++) tree[i+start] = Long.parseLong(br.readLine());
        
        // 3. 나머지 데이터 채우기
        int idx = size-1;
        while(idx != 1){
            tree[idx/2] = tree[idx/2] * tree[idx]%MOD;
            idx--;
        }

        StringBuilder sb = new StringBuilder();
        for(int i=0;i<M+K;i++){
            st = new StringTokenizer(br.readLine());
            long a = Integer.parseInt(st.nextToken());
            int b = Integer.parseInt(st.nextToken());
            long c = Long.parseLong(st.nextToken());

			// 4. 인덱스 변환 : start+b
            // 5. 두 노드의 값 스위칭
            if(a == 1) change(start+b, c);
            // 6. 질의값 구하기
            else if(a == 2){
                b += start;
                c += start;
                // 7. 데이터 업데이트하기
                sb.append(getVal(b,(int)c)).append("\n");
            }
            else return;
        }
        System.out.println(sb);
    }

	// 6. 질의값 구하기
    static long getVal(int s, int e){
        long val = 1;
        while(s <= e){
            if(s%2 == 1){
                val = val * tree[s] % MOD;
                s++;
            }
            if(e%2 == 0){
                val = val * tree[e]%MOD;
                e--;
            }

            s /= 2;
            e /= 2;
        }
        return val;
    }

	// 5. 두 노드의 값 스위칭
    static void change(int idx, long val){
        tree[idx] = val;
        while(idx > 1){
            idx /= 2;
            tree[idx] = tree[idx*2]%MOD * tree[idx*2+1]%MOD;
        }
    }
}
profile
저는 상황에 맞는 최적의 솔루션을 깊고 정확한 개념의 이해를 통한 다양한 방식으로 해결해오면서 지난 3년 동안 신규 서비스를 20만 회원 서비스로 성장시킨 Software Developer 최민길입니다.

0개의 댓글

관련 채용 정보