일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- try-catch-finally
- 메모리계층구조
- 이분탐색
- 멀티 스레드
- 최장증가부분수열
- 프로그래머스
- MVCC
- MESI
- OS
- Parametric Search
- 실행과정
- 멀티 프로세스
- Runtime data area
- 방문길이
- 트랜잭션
- multi-thread
- lis
- 표현 가능한 이진트리
- java
- try-with-resource
- 틱택토
- 함께 자라기
- 세그멘테이션
- 자바
- lv3
- 캐시라인
- synchronized
- 이펙티브자바
- 멀티 코어
- 아키텍처 개선
- Today
- Total
siino's 개발톡
세그먼트 트리 뽀개기 본문
세그먼트 트리란? (Segment Tree)
여러개의 데이터가 연속적으로 존재할 때 특정한 범위에 대한 질의를 O(logN)의 시간복잡도로 해결할 수 있습니다.
여기서 말하는 질의(query)란 특정 index에 대한 update, 특정 구간에 대한 SUM, MIN, MAX등이 해당됩니다.
기본적인 개념은 데이터의 범위를 반씩 분할하며 그 구간의 query를 수행합니다.
구간합을 구하는 Segment Tree
위 그림에서와 같이 [1, 3, 5, 7, 9, 11]이라는 배열이 주어질 때, 구간의 대표값을 Tree에 저장합니다.
Root노드는 0 ~ 5까지의 index를 대표하는 값,
left child는 0 ~ 2 까지의 index를 대표하는 값,
right child는 3~5 까지의 index를 대표하는 값으로 저장합니다.
세그먼트 트리는 재귀로 구현하면 간단하게 구현할 수 있습니다.
세그먼트 트리에서 query를 조회할 때에는 해당 query의 범위가 해당 노드가 대표하고 있는 범위를 포함하는 지,
포함하지 않는 지의 여부가 중요합니다.
해당 노드가 대표하고 있는 범위가 query의 내부에 있다면 그 값을 선택해서 비교하고, 만약 노드가 대표하고 있는 범위가 query의 범위 밖에 있다면 해당 노드는 선택하지 않습니다.
주의할 점으로는
1. 처음에 min, max 등의 배열에 값을 할당하는 초기화 코드가 필요한데, 이 또한 재귀로 구현할 수 있습니다.
2. tree의 size는 주어진 데이터의 개수 n보다 큰 가장 가까운 2의 제곱수의 2배가 필요합니다.
따라서 보통은 넉넉하게 n * 4의 size로 할당합니다.
재귀에 대한 코드가 반복적이므로 한번 구현하면 편하게 사용할 수 있습니다.
public class SegmentTree {
static int n;
static int[] a, maxTree, minTree;
// 세그먼트 트리 초기화
//node: 해당노드
//nodeLeft, nodeRight: 실제 배열에서 몇번째 인덱스부터 몇번째 인덱스까지를 대표하느냐?
public static void init(int node, int nodeLeft, int nodeRight) {
if (nodeLeft == nodeRight) {
maxTree[node] = a[nodeLeft];
minTree[node] = a[nodeLeft];
return;
}
int mid = (nodeLeft + nodeRight) / 2;
init(
node * 2,
nodeLeft,
mid
);
init(
node * 2 + 1,
mid + 1,
nodeRight
);
maxTree[node] = Math.max(maxTree[node * 2], maxTree[node * 2 + 1]);
minTree[node] = Math.min(minTree[node * 2], minTree[node * 2 + 1]);
}
// 세그먼트 트리 갱신
public static void update(int node, int nodeLeft, int nodeRight, int queryIndex, int value) {
if (queryIndex < nodeLeft || nodeRight < queryIndex) {
return;
}
if (nodeLeft == nodeRight) {
maxTree[node] = value;
minTree[node] = value;
return;
}
int mid = (nodeLeft + nodeRight) / 2;
update(
node * 2,
nodeLeft,
mid,
queryIndex,
value
);
update(
node * 2 + 1,
mid + 1,
nodeRight,
queryIndex,
value
);
maxTree[node] = Math.max(maxTree[node * 2], maxTree[node * 2 + 1]);
minTree[node] = Math.min(minTree[node * 2], minTree[node * 2 + 1]);
}
// 최대값 쿼리
public static int queryMax(int node, int nodeLeft, int nodeRight, int queryLeft, int queryRight) {
if (queryRight < nodeLeft || nodeRight < queryLeft) {
return 0;
}
if (queryLeft <= nodeLeft && nodeRight <= queryRight) {
return maxTree[node];
}
int mid = (nodeLeft + nodeRight) / 2;
int leftMax = queryMax(
node * 2,
nodeLeft,
mid,
queryLeft,
queryRight
);
int rightMax = queryMax(
node * 2 + 1,
mid + 1,
nodeRight,
queryLeft,
queryRight
);
return Math.max(leftMax, rightMax);
}
// 최소값 쿼리
static int queryMin(int node, int nodeLeft, int nodeRight, int queryLeft, int queryRight) {
if (queryLeft > nodeRight || queryRight < nodeLeft) {
return Integer.MAX_VALUE;
}
if (queryLeft <= nodeLeft && nodeRight <= queryRight) {
return minTree[node];
}
int mid = (nodeLeft + nodeRight) / 2;
int leftMin = queryMin(
node * 2,
nodeLeft,
mid,
queryLeft,
queryRight
);
int rightMin = queryMin(
node * 2 + 1,
mid + 1,
nodeRight,
queryLeft,
queryRight
);
return Math.min(leftMin, rightMin);
}
}
'알고리즘' 카테고리의 다른 글
최장 증가 부분 수열(LIS, Longest Increasing Subsequence) (1) | 2024.01.08 |
---|---|
라빈-카프(Rabin-Karp) (1) | 2024.01.04 |