컴퓨터 구조 운영체제 네트워크 자료구조 알고리즘 데이터베이스 내 학습
ds · 04

트리

자료구조트리순회

트리(Tree)는 노드(Node)와 간선(Edge)으로 이루어진 계층적 자료구조입니다. 하나의 루트에서 시작해 각 노드가 0개 이상의 자식 노드를 가지며, 사이클이 존재하지 않습니다. 파일 시스템, HTML DOM, 조직도처럼 계층 관계를 표현하는 데 자연스럽게 사용됩니다.

핵심 개념

노드와 간선

노드(Node)는 데이터를 담는 단위이고, 간선(Edge)은 노드 사이의 연결입니다. 최상위 노드를 루트(Root)라 하며, 자식이 없는 노드를 리프(Leaf)라 합니다. 노드 N개로 이루어진 트리는 항상 N-1개의 간선을 가집니다.

핵심 개념

레벨과 높이

레벨(Level)은 루트로부터의 거리로, 루트는 레벨 0입니다. 높이(Height)는 이 설명에서 루트에서 가장 깊은 리프까지의 레벨입니다(교재에 따라 노드 수로 세기도 합니다). 루트에서 특정 노드로 가는 경로는 하나만 존재합니다.

핵심 개념

이진 트리 (Binary Tree)

각 노드가 최대 2개의 자식(왼쪽 자식, 오른쪽 자식)만 가질 수 있는 트리입니다. 모든 레벨이 노드로 가득 찬 트리를 포화 이진 트리(교재에 따라 용어가 다릅니다), 마지막 레벨을 제외하고 왼쪽부터 채워진 트리를 완전 이진 트리라 합니다.

핵심 개념

전위 순회 (Preorder)

루트 → 왼쪽 서브트리 → 오른쪽 서브트리 순서로 방문합니다. 루트를 가장 먼저 처리하므로 트리 구조를 복사하거나 디렉토리 목록을 출력할 때 사용합니다.

핵심 개념

중위 순회 (Inorder)

왼쪽 서브트리 → 루트 → 오른쪽 서브트리 순서로 방문합니다. 이진 탐색 트리(BST)에서 중위 순회를 하면 값이 오름차순으로 정렬되어 나오는 특성이 있습니다.

핵심 개념

후위 순회 (Postorder)

왼쪽 서브트리 → 오른쪽 서브트리 → 루트 순서로 방문합니다. 자식을 모두 처리한 뒤 부모를 처리하므로 트리 삭제, 디렉토리 용량 합산처럼 하위 결과가 필요한 작업에 사용합니다.

재귀와 트리 — 모든 서브트리는 그 자체로 또 하나의 트리입니다. 이 자기 유사성 때문에 트리 순회는 재귀로 구현하는 것이 자연스럽고, 함수가 자기 자신을 호출할 때마다 호출 스택에 현재 노드가 쌓입니다.
시간복잡도 — 전위·중위·후위 순회 모두 모든 노드를 정확히 한 번씩 방문하므로 O(n)입니다. 재귀 호출 스택 깊이는 트리 높이에 비례해 O(h), 최악의 경우(편향 트리) O(n)까지 늘어날 수 있습니다.
깊이 우선 vs 너비 우선 — 전위·중위·후위는 한 방향으로 끝까지 내려갔다 올라오는 깊이 우선 탐색(DFS)입니다. 반면 레벨 순서대로 방문하는 레벨 순회(Level-order)는 큐를 사용한 너비 우선 탐색(BFS)입니다.
활용 사례 — 일반 이진 트리는 수식 트리(연산자 우선순위 표현), 허프만 코딩 트리, 트리 기반 자료구조의 기본 골격으로 쓰입니다. 탐색에 특화된 이진 탐색 트리(BST)는 다음 토픽에서 다룹니다.

트리가 필요한 이유

배열이나 연결 리스트는 데이터를 한 줄로 늘어놓기 때문에 계층 관계를 표현하기 어렵습니다. 트리는 한 노드에서 여러 자식으로 갈라지는 구조라서 폴더와 파일, 조직도, 문서의 태그 구조처럼 '포함 관계'가 있는 데이터를 그대로 담습니다. 데이터 크기가 커져도 루트에서 목적지까지의 거리(높이)가 천천히 늘어난다는 점도 중요한 성질입니다.

숫자로 확인하기

노드 수와 높이 — 높이가 h인 이진 트리(루트만 있으면 h = 0)가 가질 수 있는 노드는 최대 2^(h+1) − 1개입니다. h = 2이면 7개, h = 19이면 1,048,575개입니다.
균형 트리의 높이 — 노드가 10억 개(10⁹)여도 완전 이진 트리처럼 균형이 잡혀 있으면 높이는 약 29입니다. 한쪽으로만 이어진 편향 트리는 같은 노드 수에서 높이가 n − 1이 됩니다.
간선 수 — 노드가 N개인 트리의 간선은 N − 1개입니다. 노드 1,000개면 간선 999개입니다.
순회의 호출 깊이 — 재귀로 순회하면 호출 스택이 트리 높이만큼 쌓입니다. 높이가 100,000인 편향 트리를 재귀로 돌면 호출 깊이가 100,000에 이릅니다.

자주 하는 오해

이진 트리는 값이 정렬되어 있다 — 이진 트리는 자식이 최대 2개라는 모양에 대한 정의일 뿐입니다. 왼쪽이 작고 오른쪽이 크다는 규칙이 있는 것은 이진 탐색 트리입니다.
트리와 그래프는 다른 자료구조다 — 트리는 사이클이 없고 연결된 그래프의 특수한 경우입니다. 그래서 그래프 탐색 알고리즘을 그대로 쓸 수 있습니다.
높이와 깊이와 레벨의 정의는 하나다 — 루트를 0으로 세는지 1로 세는지, 간선 수로 세는지 노드 수로 세는지는 교재마다 다릅니다. 문제를 풀 때는 정의를 먼저 확인해야 합니다.
순회를 바꾸면 트리 자체가 바뀐다 — 순회는 방문 순서일 뿐 트리를 바꾸지 않습니다. 같은 트리를 전위, 중위, 후위로 읽으면 서로 다른 나열이 나옵니다.

면접에서 이렇게 물어요

면접 질문

전위, 중위, 후위 순회는 언제 쓰나요?

트리를 복사하거나 구조를 출력할 때는 전위, 이진 탐색 트리에서 정렬된 순서로 읽을 때는 중위, 자식의 결과를 모아 부모를 처리할 때(삭제, 용량 합산)는 후위를 주로 씁니다.

면접 질문

전위 순회와 중위 순회 결과로 트리를 복원할 수 있나요?

노드 값이 모두 다르면 전위의 첫 값이 루트이고, 그 값을 중위 결과에서 찾으면 왼쪽과 오른쪽 서브트리가 나뉘어 하나로 복원됩니다.

면접 질문

완전 이진 트리와 포화 이진 트리의 차이는 무엇인가요?

많이 쓰는 정의로는 포화 이진 트리는 모든 레벨이 가득 찬 트리이고, 완전 이진 트리는 마지막 레벨만 비어 있을 수 있으며 왼쪽부터 채워진 트리입니다(교재에 따라 용어가 다릅니다). 완전 이진 트리는 배열로 빈틈 없이 표현할 수 있습니다.

면접 질문

레벨 순회는 어떻게 구현하나요?

큐에 루트를 넣고, 하나 꺼낼 때마다 그 자식을 큐에 넣는 BFS 방식입니다. 층 단위로 방문하려면 한 층의 크기만큼 반복해서 꺼냅니다.

실제 시스템에서는

HTML 문서 — WHATWG DOM 표준은 문서를 부모와 자식을 가진 노드로 이루어진 트리로 모델링합니다. 브라우저는 HTML을 이 트리로 만들어 다룹니다.
파일 시스템 — 디렉터리는 파일과 하위 디렉터리를 담는 트리 구조입니다. Java의 Files.walkFileTree는 이 트리를 깊이 우선으로 순회합니다. 자세한 구조는 파일 시스템 토픽에서 봅니다.
데이터베이스 인덱스 — 많은 데이터베이스가 B-Tree 계열 트리로 인덱스를 만듭니다. 인덱스 토픽에서 이어서 봅니다.

함께 보면 좋은 토픽

이진 탐색 트리 — 트리에 정렬 규칙을 더한 구조를 봅니다.
힙 — 완전 이진 트리를 배열로 표현하는 구조를 봅니다.
그래프 — 트리가 그래프의 특수한 경우라는 점을 봅니다.
BFS / DFS — 트리 순회가 DFS와 BFS와 같은 원리임을 봅니다.
트라이 — 문자열을 다루는 트리 구조를 봅니다.
스킵 리스트 — 정렬된 연결 리스트 위에 빠른 길을 여러 층 얹어 탐색을 줄이는 자료구조를 봅니다.

참고 자료

Java Files — Files — Java SE 21 API
교재 — Cormen, Leiserson, Rivest, Stein, "Introduction to Algorithms" 4판, "Elementary Data Structures" 장(트리), "Elementary Graph Algorithms" 장

Java — 이진 트리 노드 정의 및 삽입

/* ── 이진 트리 노드 ─────────────────────────────────────── */
public class TreeNode {
    int value;
    TreeNode left;
    TreeNode right;

    TreeNode(int value) {
        this.value = value;
    }
}

/* ── 레벨 순서로 채워 넣는 완전 이진 트리 구성 ─────────────── */
// 배열 인덱스 i의 자식은 2i+1(왼쪽), 2i+2(오른쪽)에 대응합니다.
public class BinaryTree {
    TreeNode root;

    public TreeNode buildFromArray(int[] values) {
        if (values.length == 0) return null;
        TreeNode[] nodes = new TreeNode[values.length];
        for (int i = 0; i < values.length; i++) {
            nodes[i] = new TreeNode(values[i]);
        }
        for (int i = 0; i < values.length; i++) {
            int leftIdx = 2 * i + 1;
            int rightIdx = 2 * i + 2;
            if (leftIdx < values.length)  nodes[i].left  = nodes[leftIdx];
            if (rightIdx < values.length) nodes[i].right = nodes[rightIdx];
        }
        root = nodes[0];
        return root;
    }
}

일반 이진 트리는 정렬 조건이 없으므로, 배열을 레벨 순서(Level-order)로 그대로 노드에 대응시켜 완전 이진 트리 형태로 구성할 수 있습니다.
BST(이진 탐색 트리)처럼 "왼쪽 자식 < 부모 < 오른쪽 자식" 조건이 없는 순수 구조라는 점이 다음 토픽인 BST와의 차이입니다.

Java — 전위 / 중위 / 후위 순회 (재귀)

/* ── 전위 순회: Root → Left → Right ─────────────────────── */
public void preorder(TreeNode node, List<Integer> result) {
    if (node == null) return;       // 재귀 종료 조건
    result.add(node.value);                 // 1. 루트 방문
    preorder(node.left, result);          // 2. 왼쪽 서브트리
    preorder(node.right, result);         // 3. 오른쪽 서브트리
}

/* ── 중위 순회: Left → Root → Right ─────────────────────── */
public void inorder(TreeNode node, List<Integer> result) {
    if (node == null) return;
    inorder(node.left, result);           // 1. 왼쪽 서브트리
    result.add(node.value);                 // 2. 루트 방문
    inorder(node.right, result);          // 3. 오른쪽 서브트리
}

/* ── 후위 순회: Left → Right → Root ─────────────────────── */
public void postorder(TreeNode node, List<Integer> result) {
    if (node == null) return;
    postorder(node.left, result);         // 1. 왼쪽 서브트리
    postorder(node.right, result);        // 2. 오른쪽 서브트리
    result.add(node.value);                 // 3. 루트 방문
}

/* ── 호출 예시 ──────────────────────────────────────────── */
List<Integer> result = new ArrayList<>();
preorder(root, result);
// result: [루트, 왼쪽 서브트리 전위, 오른쪽 서브트리 전위] 순서로 채워집니다.

세 순회 함수는 재귀 호출 순서만 다를 뿐 구조가 동일합니다.
result.add()를 어느 위치에 두느냐가 전위·중위·후위를 가릅니다.
각 재귀 호출은 호출 스택에 현재 노드를 쌓고, null을 만나면(리프의 자식) 스택을 거슬러 올라갑니다 — 시각화의 "스택 깊이"가 바로 이 호출 스택입니다.
시간복잡도는 모든 노드를 정확히 한 번 방문하므로 O(n), 공간복잡도는 재귀 호출 스택 깊이만큼인 O(h) (h = 트리 높이, 최악의 경우 편향 트리에서 O(n))입니다.

Java — 레벨 순회 (BFS, 참고)

/* ── 레벨 순회: 큐를 이용한 너비 우선 탐색 ──────────────── */
// 전위·중위·후위가 깊이 우선(DFS)인 것과 달리, 레벨 순회는 큐 기반 BFS입니다.
public void levelOrder(TreeNode root, List<Integer> result) {
    if (root == null) return;

    Queue<TreeNode> queue = new LinkedList<>();
    queue.offer(root);

    while (!queue.isEmpty()) {
        TreeNode node = queue.poll();
        result.add(node.value);
        if (node.left  != null) queue.offer(node.left);
        if (node.right != null) queue.offer(node.right);
    }
}

재귀 대신 큐(Queue)에 자식 노드를 순서대로 넣고 빼면서 레벨 단위로 방문합니다.
트리의 폭(너비)이 넓을수록 큐가 커지므로 공간복잡도는 O(w) (w = 최대 레벨 너비)입니다.
레벨 순회는 이번 시각화의 핵심 범위는 아니지만, DFS(전위·중위·후위)와 BFS(레벨 순회)의 차이를 이해하는 데 참고가 됩니다.

Q1

이진 트리에서 각 노드가 최대 몇 개의 자식을 가질 수 있나요?

'이진'이라는 이름에서 힌트를 얻으세요.
이진 트리(Binary Tree)는 각 노드가 최대 2개의 자식(왼쪽, 오른쪽)만 가질 수 있는 트리 구조입니다.

Q2

중위 순회(In-order Traversal)의 순서는?

'중간'이라는 이름처럼 루트를 중간에 방문합니다.
중위 순회는 왼쪽 서브트리→루트→오른쪽 서브트리 순서로 방문합니다. 이진 탐색 트리에서는 이 순서로 순회하면 정렬된 결과를 얻을 수 있습니다.

Q3

전위 순회(Pre-order)와 후위 순회(Post-order)의 차이는?

'전(pre)'과 '후(post)'는 루트 방문 시점을 가리킵니다.
전위 순회는 루트→왼쪽→오른쪽 순서로 루트를 가장 먼저 방문하고, 후위 순회는 왼쪽→오른쪽→루트 순서로 루트를 가장 마지막에 방문합니다.