좋아요! 백준 2263번 "트리의 순회" 문제는 중위 순회(Inorder) 와 후위 순회(Postorder) 가 주어졌을 때, 전위 순회(Preorder) 결과를 구하는 문제입니다.
입력:
ninorder[]postorder[]출력: 전위 순회 결과 preorder[]
입력:
7
4 2 5 1 6 3 7
4 5 2 6 7 3 1
출력:
1 2 4 5 3 6 7
📌 이 트리는 이렇게 생겼습니다:
1
/ \
2 3
/ \ / \
4 5 6 7
import java.io.*;
import java.util.*;
public class Main {
static int[] inorder, postorder;
static int[] indexInInorder;
static StringBuilder sb = new StringBuilder();
public static void main(String[] args) throws IOException {
// 입력을 빠르게 받기 위해 BufferedReader 사용
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
int n = Integer.parseInt(br.readLine()); // 노드 수
inorder = new int[n];
postorder = new int[n];
indexInInorder = new int[100001]; // 값의 위치를 저장
// 중위 순회 입력 받기
StringTokenizer st = new StringTokenizer(br.readLine());
for (int i = 0; i < n; i++) {
inorder[i] = Integer.parseInt(st.nextToken());
indexInInorder[inorder[i]] = i;
}
// 후위 순회 입력 받기
st = new StringTokenizer(br.readLine());
for (int i = 0; i < n; i++) {
postorder[i] = Integer.parseInt(st.nextToken());
}
// 재귀 호출 시작
buildPreOrder(0, n - 1, 0, n - 1);
System.out.println(sb.toString());
}
static void buildPreOrder(int inStart, int inEnd, int postStart, int postEnd) {
if (inStart > inEnd || postStart > postEnd) return;
// 후위 순회의 마지막 원소는 현재 서브트리의 루트이다.
int root = postorder[postEnd];
sb.append(root).append(" ");
// 루트의 위치를 중위 순회에서 찾는다.
int rootIndex = indexInInorder[root];
// 왼쪽 서브트리의 노드 수 계산
int leftSize = rootIndex - inStart;
// 왼쪽 서브트리 재귀 호출
buildPreOrder(inStart, rootIndex - 1, postStart, postStart + leftSize - 1);
// 오른쪽 서브트리 재귀 호출
buildPreOrder(rootIndex + 1, inEnd, postStart + leftSize, postEnd - 1);
}
}
예제 입력:
n = 7
inorder = 4 2 5 1 6 3 7
postorder = 4 5 2 6 7 3 1
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
int n = Integer.parseInt(br.readLine());
n = 7inorder = new int[n];
postorder = new int[n];
indexInInorder = new int[100001];
indexInInorder는 값 → 인덱스를 빠르게 찾기 위한 용도// 중위 순회 배열 입력받기
StringTokenizer st = new StringTokenizer(br.readLine());
for (int i = 0; i < n; i++) {
inorder[i] = Integer.parseInt(st.nextToken());
indexInInorder[inorder[i]] = i;
}
📌 입력:
4 2 5 1 6 3 7
inorder = [4,2,5,1,6,3,7]
indexInInorder[4] = 0
indexInInorder[2] = 1
indexInInorder[5] = 2
...
// 후위 순회 배열 입력받기
st = new StringTokenizer(br.readLine());
for (int i = 0; i < n; i++) {
postorder[i] = Integer.parseInt(st.nextToken());
}
📌 입력:
4 5 2 6 7 3 1
postorder = [4,5,2,6,7,3,1]
buildPreOrder(0, n - 1, 0, n - 1);
buildPreOrder(0,6,0,6)4 2 5 1 6 3 74 5 2 6 7 3 11 → rootindexInInorder[1] = 3 → 중위에서 root 인덱스leftSize = 3 → 왼쪽 노드 수전위 출력:
1
buildPreOrder(0,2,0,2)2전위 출력:
1 2
buildPreOrder(0,0,0,0) → root=4전위:
1 2 4
buildPreOrder(2,2,1,1) → root=5전위:
1 2 4 5
buildPreOrder(4,6,3,5)전위:
1 2 4 5 3
buildPreOrder(4,4,3,3) → root=6전위:
1 2 4 5 3 6
buildPreOrder(6,6,4,4) → root=7전위:
1 2 4 5 3 6 7
1 2 4 5 3 6 7
좋아요! 지금부터 buildPreOrder() 메서드를 예제를 따라가며 완전히 디버깅하듯 한 줄씩, 어떻게 동작하는지를 해부하겠습니다.
static void buildPreOrder(int inStart, int inEnd, int postStart, int postEnd)
inStart, inEnd: 현재 중위 순회(inorder)에서 탐색할 구간postStart, postEnd: 현재 후위 순회(postorder)에서 탐색할 구간inorder = [4, 2, 5, 1, 6, 3, 7]
postorder = [4, 5, 2, 6, 7, 3, 1]
📌 우리가 만들고자 하는 전위 순회 결과:
1 2 4 5 3 6 7
buildPreOrder(0, 6, 0, 6)if (inStart > inEnd || postStart > postEnd) return;
0 > 6 또는 0 > 6 아니므로 → ❌조건 불만족 → 넘어감int root = postorder[postEnd];
postorder[6] = 1 → 현재 서브트리의 루트는 1번 노드sb.append(root).append(" ");
sb = "1 "int rootIndex = indexInInorder[root];
indexInInorder[1] = 3 → 중위 순회에서 루트 1의 위치int leftSize = rootIndex - inStart; // 3 - 0 = 3
buildPreOrder(0, 2, 0, 2)buildPreOrder(0, 2, 0, 2)postorder[2] = 2 → root`
sb = "1 2 "`
indexInInorder[2] = 1
leftSize = 1
→ 왼쪽 재귀: `buildPreOrder(0, 0, 0, 0)`
→ 오른쪽 재귀: `buildPreOrder(2, 2, 1, 1)`
---
### 🔁 3차 호출: `buildPreOrder(0, 0, 0, 0)` → 왼쪽 자식 4
- `postorder[0] = 4 → root`
- `sb = "1 2 4 "`
- `indexInInorder[4] = 0`
- `leftSize = 0`
→ 왼쪽: `buildPreOrder(0, -1, 0, -1)` → 종료
→ 오른쪽: `buildPreOrder(1, 0, 0, -1)` → 종료
⛔ 재귀 종료, 돌아감
---
### 🔁 4차 호출: `buildPreOrder(2, 2, 1, 1)` → 오른쪽 자식 5
- `postorder[1] = 5 → root`
- `sb = "1 2 4 5 "`
- `indexInInorder[5] = 2`
- `leftSize = 0`
→ 왼쪽: `buildPreOrder(2, 1, 1, 0)` → 종료
→ 오른쪽: `buildPreOrder(3, 2, 1, 0)` → 종료
⛔ 재귀 종료, 돌아감
---
### 🔁 5차 호출: `buildPreOrder(4, 6, 3, 5)` → 오른쪽 트리로 진입
- `postorder[5] = 3 → root`
- `sb = "1 2 4 5 3 "`
- `indexInInorder[3] = 5`
- `leftSize = 1`
→ 왼쪽: `buildPreOrder(4, 4, 3, 3)`
→ 오른쪽: `buildPreOrder(6, 6, 4, 4)`
---
### 🔁 6차 호출: `buildPreOrder(4, 4, 3, 3)` → 왼쪽 자식 6
- `postorder[3] = 6 → root`
- `sb = "1 2 4 5 3 6 "`
- `indexInInorder[6] = 4`
- `leftSize = 0`
→ 왼쪽/오른쪽 재귀 종료
---
### 🔁 7차 호출: `buildPreOrder(6, 6, 4, 4)` → 오른쪽 자식 7
- `postorder[4] = 7 → root`
- `sb = "1 2 4 5 3 6 7 "`
- `indexInInorder[7] = 6`
- `leftSize = 0`
→ 왼쪽/오른쪽 재귀 종료
---
## ✅ 최종 결과
출력값:
1 2 4 5 3 6 7
---
## 📌 전체 재귀 호출 구조 시각화
buildPreOrder(0,6,0,6) → root = 1
├─ buildPreOrder(0,2,0,2) → root = 2
│ ├─ buildPreOrder(0,0,0,0) → root = 4
│ └─ buildPreOrder(2,2,1,1) → root = 5
└─ buildPreOrder(4,6,3,5) → root = 3
├─ buildPreOrder(4,4,3,3) → root = 6
└─ buildPreOrder(6,6,4,4) → root = 7
---
아주 좋은 질문이에요!
`postStart` 값을 **재귀 함수 호출 시 어떻게 계산하는지** 이해하는 것은 이 문제에서 가장 어려운 부분 중 하나예요. 지금부터 **매우 천천히, 디버깅 방식으로**, **한 줄씩 흐름을 따라가며 설명**드릴게요.
---
## 🔁 메서드 시그니처 다시 확인:
```java
buildPreOrder(int inStart, int inEnd, int postStart, int postEnd)
inorder[inStart ~ inEnd], postorder[postStart ~ postEnd] 구간을 가지고 재귀 호출해요.postStart는 "현재 탐색하는 구간의 후위 순회 시작 위치"예요.inorder = [4, 2, 5, 1, 6, 3, 7]
postorder = [4, 5, 2, 6, 7, 3, 1]
buildPreOrder(0, 6, 0, 6)
postorder[6] = 1root = 1
rootIndex = indexInInorder[1] = 3
→ 루트의 왼쪽에는 3개의 노드가 있으므로
leftSize = 3
buildPreOrder(
inStart = 0,
inEnd = rootIndex - 1 = 2,
postStart = 0,
postEnd = postStart + leftSize - 1 = 0 + 3 - 1 = 2
)
✔ 왼쪽 서브트리의 postStart는 지금 postStart 그대로 (0)
왜냐면 postorder는 "왼쪽 → 오른쪽 → 루트" 순서이기 때문에,
현재 postStart ~ postStart+leftSize-1 구간이 왼쪽 서브트리를 담당해요.
buildPreOrder(
inStart = rootIndex + 1 = 4,
inEnd = 6,
postStart = postStart + leftSize = 0 + 3 = 3,
postEnd = postEnd - 1 = 6 - 1 = 5
)
✔ 오른쪽 서브트리의 postStart는 현재 postStart + 왼쪽 서브트리 크기!
왜냐하면 왼쪽 서브트리가 postorder에서 먼저 나오고,
오른쪽 서브트리가 그 다음에 나오기 때문이에요.
| 어떤 서브트리 | postStart 계산법 | postEnd 계산법 |
|---|---|---|
| 왼쪽 서브트리 | postStart 그대로 | postStart + leftSize - 1 |
| 오른쪽 서브트리 | postStart + leftSize | postEnd - 1 (루트 제외) |
inorder: [4, 2, 5, 1, 6, 3, 7]
index: 0 1 2 3 4 5 6
postorder: [4, 5, 2, 6, 7, 3, 1]
index: 0 1 2 3 4 5 6
^ ^
왼쪽 서브트리 루트 (1)
→ 왼쪽 트리의 postStart = 0, postEnd = 2
→ 오른쪽 트리의 postStart = 3, postEnd = 5
postStart는 후위 순회에서 어떤 서브트리를 처리할지 결정하는 기준입니다.leftSizepostStart + leftSize왼쪽 서브트리: buildPreOrder(0, 2, 0, 2)
postorder[2] = 2indexInInorder[2] = 1, → leftSize = 1buildPreOrder(0, 0, 0, 0) // postStart 그대로
buildPreOrder(2, 2, 1, 1) // postStart = postStart + leftSize = 0 + 1 = 1
모두 공식에 맞게 잘 들어맞죠!
postStart는 현재 처리 중인 후위 순회 구간의 시작점| 파라미터 | 왼쪽 서브트리 | 오른쪽 서브트리 |
|---|---|---|
| postStart | postStart | postStart + leftSize |
| postEnd | postStart + leftSize - 1 | postEnd - 1 |