트리
트리(Tree)는 노드(Node)와 간선(Edge)으로 이루어진 계층적 자료구조입니다. 하나의 루트에서 시작해 각 노드가 0개 이상의 자식 노드를 가지며, 사이클이 존재하지 않습니다. 파일 시스템, HTML DOM, 조직도처럼 계층 관계를 표현하는 데 자연스럽게 사용됩니다.
핵심 개념
노드와 간선
노드(Node)는 데이터를 담는 단위이고, 간선(Edge)은 노드 사이의 연결입니다. 최상위 노드를 루트(Root)라 하며, 자식이 없는 노드를 리프(Leaf)라 합니다. 노드 N개로 이루어진 트리는 항상 N-1개의 간선을 가집니다.
핵심 개념
레벨과 높이
레벨(Level)은 루트로부터의 거리로, 루트는 레벨 0입니다. 높이(Height)는 이 설명에서 루트에서 가장 깊은 리프까지의 레벨입니다(교재에 따라 노드 수로 세기도 합니다). 루트에서 특정 노드로 가는 경로는 하나만 존재합니다.
핵심 개념
이진 트리 (Binary Tree)
각 노드가 최대 2개의 자식(왼쪽 자식, 오른쪽 자식)만 가질 수 있는 트리입니다. 모든 레벨이 노드로 가득 찬 트리를 포화 이진 트리(교재에 따라 용어가 다릅니다), 마지막 레벨을 제외하고 왼쪽부터 채워진 트리를 완전 이진 트리라 합니다.
핵심 개념
전위 순회 (Preorder)
루트 → 왼쪽 서브트리 → 오른쪽 서브트리 순서로 방문합니다. 루트를 가장 먼저 처리하므로 트리 구조를 복사하거나 디렉토리 목록을 출력할 때 사용합니다.
핵심 개념
중위 순회 (Inorder)
왼쪽 서브트리 → 루트 → 오른쪽 서브트리 순서로 방문합니다. 이진 탐색 트리(BST)에서 중위 순회를 하면 값이 오름차순으로 정렬되어 나오는 특성이 있습니다.
핵심 개념
후위 순회 (Postorder)
왼쪽 서브트리 → 오른쪽 서브트리 → 루트 순서로 방문합니다. 자식을 모두 처리한 뒤 부모를 처리하므로 트리 삭제, 디렉토리 용량 합산처럼 하위 결과가 필요한 작업에 사용합니다.
트리가 필요한 이유
배열이나 연결 리스트는 데이터를 한 줄로 늘어놓기 때문에 계층 관계를 표현하기 어렵습니다. 트리는 한 노드에서 여러 자식으로 갈라지는 구조라서 폴더와 파일, 조직도, 문서의 태그 구조처럼 '포함 관계'가 있는 데이터를 그대로 담습니다. 데이터 크기가 커져도 루트에서 목적지까지의 거리(높이)가 천천히 늘어난다는 점도 중요한 성질입니다.
숫자로 확인하기
자주 하는 오해
면접에서 이렇게 물어요
면접 질문
전위, 중위, 후위 순회는 언제 쓰나요?
트리를 복사하거나 구조를 출력할 때는 전위, 이진 탐색 트리에서 정렬된 순서로 읽을 때는 중위, 자식의 결과를 모아 부모를 처리할 때(삭제, 용량 합산)는 후위를 주로 씁니다.
면접 질문
전위 순회와 중위 순회 결과로 트리를 복원할 수 있나요?
노드 값이 모두 다르면 전위의 첫 값이 루트이고, 그 값을 중위 결과에서 찾으면 왼쪽과 오른쪽 서브트리가 나뉘어 하나로 복원됩니다.
면접 질문
완전 이진 트리와 포화 이진 트리의 차이는 무엇인가요?
많이 쓰는 정의로는 포화 이진 트리는 모든 레벨이 가득 찬 트리이고, 완전 이진 트리는 마지막 레벨만 비어 있을 수 있으며 왼쪽부터 채워진 트리입니다(교재에 따라 용어가 다릅니다). 완전 이진 트리는 배열로 빈틈 없이 표현할 수 있습니다.
면접 질문
레벨 순회는 어떻게 구현하나요?
큐에 루트를 넣고, 하나 꺼낼 때마다 그 자식을 큐에 넣는 BFS 방식입니다. 층 단위로 방문하려면 한 층의 크기만큼 반복해서 꺼냅니다.
실제 시스템에서는
Files.walkFileTree는 이 트리를 깊이 우선으로 순회합니다. 자세한 구조는 파일 시스템 토픽에서 봅니다.
함께 보면 좋은 토픽
참고 자료
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
이진 트리에서 각 노드가 최대 몇 개의 자식을 가질 수 있나요?
Q2
중위 순회(In-order Traversal)의 순서는?
Q3
전위 순회(Pre-order)와 후위 순회(Post-order)의 차이는?