모든 강의를 무료로 볼 수 있어요. 회원가입 없이도 학습 가능합니다.

19강 / 전체 19강

트리 기초

10분 읽기 조회 0

트리 용어와 이진 트리 구조, 전위·중위·후위·레벨 네 가지 순회 구현

🎯 학습 목표

이 강을 마치면 트리의 기본 용어(루트·리프·부모·자식·깊이·높이)를 정확히 쓰고, 이진 트리를 노드 클래스로 구현할 수 있습니다. 그리고 전위·중위·후위·레벨 순회 네 가지를 재귀와 반복 양쪽으로 다룰 수 있게 됩니다.

트리는 계층 구조를 표현하는 자료구조입니다. 파일 시스템, 조직도, 문서 구조가 전부 트리입니다. 그리고 20강 이진 탐색 트리, 21강 힙, 23강 DFS가 모두 이 강 위에 올라갑니다.

💡 트리 용어 정리

트리는 사이클이 없고 하나의 루트에서 뻗어 나가는 구조입니다. 18강의 연결 리스트가 다음 노드를 하나만 가졌다면, 트리는 여러 개의 자식을 가집니다.

트리 구조와 용어

                    1          ← 루트(root) : 부모가 없는 노드
                  /   \
                 2     3       ← 1의 자식(child), 서로 형제(sibling)
                / \     \
               4   5     6     ← 4,5,6 은 리프(leaf) : 자식이 없음
              /
             7

  깊이(depth)  루트에서 그 노드까지의 간선 수
               1은 0, 2·3은 1, 4·5·6은 2, 7은 3

  높이(height) 그 노드에서 가장 먼 리프까지의 간선 수
               트리 전체의 높이 = 루트의 높이 = 3

  레벨(level)  같은 깊이의 노드 묶음
               레벨 0: [1]  레벨 1: [2,3]  레벨 2: [4,5,6]

  서브트리     어떤 노드와 그 아래 전체
               2의 서브트리 = {2, 4, 5, 7}

  ▲ 트리의 성질
   · 노드가 n개면 간선은 반드시 n-1개
   · 임의의 두 노드를 잇는 경로는 유일하다
   · 사이클이 없다

  이진 트리 — 자식이 최대 2개

                 1
               /   \
              2     3
             / \
            4   5

   포화 이진 트리 : 모든 레벨이 꽉 참
   완전 이진 트리 : 마지막 레벨만 왼쪽부터 채워짐 (21강 힙)
   편향 트리      : 한쪽으로만 뻗음 → 사실상 연결 리스트

  ▲ 높이가 왜 중요한가
    균형 잡힌 이진 트리의 높이는 약 log n 이다.
    → 루트에서 리프까지 O(log n)
    편향되면 높이가 n이 된다 → O(n)  ★
    20강 이진 탐색 트리의 성능이 여기에 달려 있다.

📖 네 가지 순회

순회(traversal)는 트리의 모든 노드를 빠짐없이 한 번씩 방문하는 것입니다. 언제 자기 자신을 처리하느냐에 따라 세 가지로 나뉩니다.

순회 순서 비교 (아래 트리 기준)

                 1
               /   \
              2     3
             / \
            4   5

  전위(preorder)   자기 → 왼쪽 → 오른쪽
                   1, 2, 4, 5, 3
                   ▲ 트리를 '복사'하거나 구조를 출력할 때

  중위(inorder)    왼쪽 → 자기 → 오른쪽
                   4, 2, 5, 1, 3
                   ▲ 이진 탐색 트리에서 '정렬된 순서'가 나온다 (20강)

  후위(postorder)  왼쪽 → 오른쪽 → 자기
                   4, 5, 2, 3, 1
                   ▲ 자식을 다 처리한 뒤 부모를 처리
                     (폴더 용량 계산, 트리 삭제 등)

  레벨(BFS)        위에서 아래로, 왼쪽에서 오른쪽으로
                   1, 2, 3, 4, 5
                   ▲ 큐를 쓴다 (6강) — 재귀가 아니다  ★

  ▲ 앞의 세 개는 모두 DFS 계열이다
    깊이 먼저 내려갔다가 되돌아온다 → 재귀·스택

  ▲ 레벨 순회만 BFS 계열이다
    가까운 것부터 훑는다 → 큐

  복잡도
   네 가지 모두 각 노드를 한 번씩 방문 → 시간 O(n)
   공간: DFS 는 O(높이), BFS 는 O(레벨의 최대 너비)

세 가지 DFS 순회의 차이는 "자기 자신을 처리하는 코드 줄이 어디 있는가" 하나뿐입니다. 재귀 호출 앞·사이·뒤 중 어디에 두느냐로 결정됩니다.

💻 실전 예제 — 순회와 트리 계산

from collections import deque


class TreeNode:
    def __init__(self, value):
        self.value = value
        self.left = None
        self.right = None


def preorder(node, result):
    """자기 → 왼쪽 → 오른쪽"""
    if not node:
        return
    result.append(node.value)      # 여기서 처리
    preorder(node.left, result)
    preorder(node.right, result)


def inorder(node, result):
    """왼쪽 → 자기 → 오른쪽"""
    if not node:
        return
    inorder(node.left, result)
    result.append(node.value)      # 위치만 바뀐다
    inorder(node.right, result)


def level_order(root):
    """레벨 순회 → 큐 사용, 레벨별로 묶어서 반환"""
    if not root:
        return []
    result = []
    q = deque([root])
    while q:
        size = len(q)              # 이번 레벨의 노드 수  ★
        level = []
        for _ in range(size):
            node = q.popleft()
            level.append(node.value)
            if node.left:
                q.append(node.left)
            if node.right:
                q.append(node.right)
        result.append(level)
    return result


def height(node):
    """트리 높이 (간선 기준) → 후위 순회 방식"""
    if not node:
        return -1                  # 빈 트리는 -1, 리프는 0
    return 1 + max(height(node.left), height(node.right))


root = TreeNode(1)
root.left = TreeNode(2)
root.right = TreeNode(3)
root.left.left = TreeNode(4)
root.left.right = TreeNode(5)

pre, ino = [], []
preorder(root, pre)
inorder(root, ino)
print(pre)                  # [1, 2, 4, 5, 3]
print(ino)                  # [4, 2, 5, 1, 3]
print(level_order(root))    # [[1], [2, 3], [4, 5]]
print(height(root))         # 2

코드를 줄 단위로 보겠습니다.

  • if not node: return — 기저 조건입니다(13강). 빈 노드에 도달하면 그냥 돌아옵니다. 이 줄이 없으면 None.left에서 오류가 납니다.
  • result.append(node.value)의 위치 — 전위와 중위 함수는 이 한 줄의 위치만 다릅니다. 후위는 재귀 호출 두 개 뒤에 두면 됩니다.
  • size = len(q) — 레벨 순회의 핵심입니다. 반복을 시작하기 전에 현재 큐의 크기를 고정해야 이번 레벨만 처리됩니다. 이 줄이 없으면 레벨 구분이 무너집니다.
  • q.popleft() — 덱을 씁니다(6강). list.pop(0)을 쓰면 O(n)이라 전체가 O(n²)가 됩니다.
  • if node.left: q.append(node.left) — 존재할 때만 큐에 넣습니다. None을 넣으면 다음 반복에서 오류가 납니다.
  • return -1 — 간선 기준 높이의 관례입니다. 빈 트리를 -1로 두면 리프가 자연스럽게 0이 됩니다. 노드 기준으로 세는 정의도 있으므로 문제가 어느 기준인지 반드시 확인하세요.
  • 1 + max(height(left), height(right)) — 후위 순회 구조입니다. 자식의 값을 모두 받은 뒤 자기 값을 계산합니다. 트리에서 값을 아래에서 위로 모으는 모든 문제가 이 형태입니다.

마지막 함수가 특히 중요합니다. "서브트리의 답을 받아 내 답을 만든다"는 패턴은 트리 문제의 가장 흔한 형태입니다. 노드 개수 세기, 합 구하기, 최댓값 찾기가 전부 같은 구조입니다.

⚠️ 자주 하는 실수

첫째, None 검사를 빠뜨립니다. 트리 문제 오류의 대부분이 여기서 납니다. 재귀 함수의 첫 줄을 if not node로 시작하는 습관을 들이세요.

둘째, 레벨 순회에서 size를 고정하지 않습니다. for _ in range(len(q))를 반복 중에 쓰면 큐가 늘어나면서 조건도 바뀝니다. 미리 변수에 담아야 합니다.

셋째, 높이와 깊이를 혼동합니다. 깊이는 위에서 아래로, 높이는 아래에서 위로 셉니다. 그리고 간선 기준인지 노드 기준인지에 따라 답이 1 차이 납니다. 문제 정의를 확인하세요.

넷째, 재귀 깊이를 고려하지 않습니다. 편향 트리는 높이가 n입니다. 노드가 10만 개인 편향 트리를 재귀로 순회하면 RecursionError가 납니다(13강). 스택을 쓴 반복 구현이나 한도 상향이 필요합니다.

다섯째, 레벨 순회를 재귀로 짜려 합니다. 레벨 순회는 큐가 본질입니다. 재귀는 깊이 우선이라 구조가 맞지 않습니다.

📝 핵심 요약 + 다음 강 예고

이번 강의 내용을 정리합니다.

#기억할 것
1노드 n개면 간선 n-1개, 사이클 없음
2균형 트리 높이는 약 log n, 편향되면 n
3전위·중위·후위는 자기 처리 위치만 다르다
4레벨 순회는 큐 — DFS가 아니다
5레벨 구분은 size 고정
6재귀 첫 줄은 if not node
7자식의 답을 받아 내 답을 — 트리 문제의 기본형

지금 해 볼 것은 후위 순회 함수를 직접 써 보는 것입니다. preorder를 복사해 append 줄을 맨 아래로 옮기기만 하면 됩니다. 결과가 [4, 5, 2, 3, 1]이 나오면 성공입니다.

다음 20강 「이진 탐색 트리」에서는 트리에 정렬 규칙을 넣습니다. 왼쪽은 작고 오른쪽은 큰 구조를 만들면 탐색·삽입·삭제를 평균 O(log n)에 할 수 있습니다. 그리고 중위 순회가 왜 정렬된 결과를 주는지도 이 강에서 밝혀집니다.

📚 참고 자료

관련 주제

  • 트리
  • 이진 트리
  • 루트
  • 리프
  • 높이
  • 전위 순회
  • 중위 순회
  • 레벨 순회
  • 개발·프로그래밍
  • 개발·프로그래밍 강의
  • 코딩 테스트 합격 알고리즘 30강
  • 무료강의
  • 무료 온라인 강의
  • NUGUNA
  • 누구나

📚 시리즈 전체 공유

코딩 테스트 합격 알고리즘 30강

이 강의가 속한 시리즈는 총 19강, 모두 무료입니다. 처음부터 배우려는 동료에게 시리즈 전체를 알려 주세요.

댓글

0/1000

불러오는 중...