ABOUT

성능과 운영 안정성을 함께 끌어올리는 개발자입니다.

92% Positional Error Reduction
79% p95 Latency Improvement
90%+ Long Tasks Reduction

2022.02 · 한국장학재단

우수 멘티

한국장학재단 사회 리더 대학생 멘토링 IT

2022.10 · 동작구청

우수 인재상

동작구청 우수 SW 인재

2025.05 · (주) 그랩

프로그래밍 우수상

(주) 그랩 우수 프로그램 개발

2025.05 · AWSKRUG

AWS한국사용자모임 발표

AI agent 스크립트 튜닝 관련 발표

ComputerScience

Development

Engineering

Trouble Shooting

GUESTBOOK

첫 마음부터
함께 나누는 온기

방명록 작성하러 가기

SUBSCRIBE

최신소식을
편하게 만나보세요.

트리 (Tree)

도입

트리 구조는 데이터를 일렬로 나열하는 방식이 아니라, 루트와 부모-자식 관계를 기준으로 계층적으로 조직하는 자료구조다.

배열이나 연결 리스트가 순서 중심 구조라면, 트리 구조는 관계 중심 구조에 가깝습니다. 어떤 데이터가 다른 데이터의 하위에 속하는지, 어떤 범위가 어떤 하위 범위들로 나뉘는지처럼 계층적 관계를 표현하는 데 강합니다.

그래서 트리는 단순히 노드를 많이 연결한 그림이 아니라, 탐색 순서와 부모-자식 관계, 서브트리 단위의 분해가 중요한 구조입니다. 검색, 우선순위 관리, 문자열 접두어 탐색, 외부 메모리 인덱싱처럼 성격이 다른 문제들이 모두 트리 계열 자료구조로 풀리는 이유도 여기에 있습니다.

즉 트리 구조를 이해한다는 것은 단순히 루트와 리프라는 용어를 아는 일이 아니라, 데이터를 계층형으로 모델링하고 그 위에서 탐색과 분할을 수행하는 관점을 이해하는 일에 가깝습니다.

필요성

데이터가 계층을 이루거나 탐색 범위를 반복적으로 반으로 나누거나, 부분 구조를 재귀적으로 다뤄야 할 때 트리 구조가 가장 자연스러운 해법이 된다

트리가 중요한 이유는 단순히 교과서에서 많이 나오기 때문이 아닙니다. 실제 문제를 보면 데이터가 평평한 표 형태보다 계층 구조를 띠는 경우가 많고, 특정 범위만 빠르게 찾아야 하거나, 하위 구조 단위로 같은 연산을 반복해야 하는 경우도 자주 생깁니다.

이때 트리 구조는 서브트리라는 단위를 통해 큰 문제를 작은 문제로 나누고, 루트에서 시작해 필요한 가지로만 내려가는 방식으로 탐색 범위를 줄여 줍니다. 검색 트리, 힙, 트라이, B-트리가 서로 다른 문제를 다루면서도 모두 트리라는 공통 형식을 갖는 이유가 바로 이것입니다.

즉 트리는 하나의 자료구조 이름이라기보다, 계층과 분기와 재귀를 이용해 문제를 다루는 방식 전체에 더 가깝습니다.

트리 구조가 특히 강한 지점
  • 데이터가 부모-자식 형태의 계층을 이룰 때
  • 검색 범위를 단계적으로 줄이며 탐색해야 할 때
  • 우선순위나 접두어처럼 특정 규칙을 구조 자체에 반영하고 싶을 때
  • 큰 문제를 서브트리 단위로 재귀적으로 처리해야 할 때

정의

자료구조 관점의 트리는 루트에서 시작해 부모-자식 관계로 접근하는 구조이며, 보통 특별한 언급이 없으면 rooted 이고 ordered 하게 다뤄진다

NIST 기준으로 트리는 루트에서 시작해 접근하는 자료구조로 설명됩니다. 각 노드는 leaf이거나 internal node이고, internal node는 하나 이상의 child를 가지며 그 child들의 parent가 됩니다. 같은 parent를 공유하는 노드들은 siblings입니다.

흥미로운 점은 자료구조 sense의 tree와 그래프 이론의 tree가 완전히 같은 개념은 아니라는 것입니다. 자료구조 쪽 트리는 보통 rooted이고 ordered이기 때문에, 같은 노드 수라도 왼쪽 자식이냐 오른쪽 자식이냐 같은 차이가 구조적으로 의미를 가질 수 있습니다.

즉 실무에서 말하는 트리 구조는 단순한 무순서 무사이클 그래프보다 더 강한 의미를 가진 경우가 많습니다. 루트, 자식 순서, 서브트리 경계가 실제 연산 의미에 직접 영향을 주기 때문입니다.

핵심 메시지

"트리 구조의 본질은 노드 수가 아니라

루트에서 시작해 서브트리 단위로 문제를 나누고 탐색하는 방식에 있습니다."

핵심 원리

트리 구조의 핵심은 전체를 여러 서브트리로 나눌 수 있다는 점이며, 대부분의 연산은 루트에서 시작해 필요한 서브트리만 따라 내려가는 방식으로 이뤄진다

트리는 재귀적 구조입니다. 루트 아래에는 여러 서브트리가 있고, 각 서브트리 역시 다시 같은 성격의 트리로 볼 수 있습니다. 그래서 크기 계산, 높이 계산, 순회, 검색처럼 많은 연산이 재귀적으로 정의됩니다.

또 트리의 성능을 이해할 때는 전체 노드 수뿐 아니라 높이(height)가 중요합니다. 특히 루트에서 리프까지의 경로가 길어질수록 탐색, 삽입, 삭제, 재귀 순회에서 실제 비용이 커지기 쉽습니다.

그래서 같은 트리 계열이라도 어떤 구조는 균형을 유지해 높이를 억제하고, 어떤 구조는 완전성(complete-ness)을 이용해 배열 표현을 쓰고, 어떤 구조는 문자열 접두어 규칙을 트리 모양에 직접 녹여 넣습니다.

즉 트리의 본질은 계층형 저장 그 자체보다, 서브트리와 높이를 이용해 연산 의미를 구조에 반영하는 데 있습니다.

Tree Thinking
1) 루트에서 시작한다
2) 현재 노드와 자식 관계를 본다
3) 필요한 서브트리로만 내려간다
4) 서브트리도 다시 같은 문제로 본다
5) 높이와 순회 순서가 실제 성능과 의미를 좌우한다

핵심 용어

루트, 리프, 깊이, 높이, 조상, 자손, 서브트리 같은 용어를 구분해야 트리 연산과 변형 구조를 제대로 읽을 수 있다
용어 의미 실무 해석
Root 트리의 시작 노드 대부분의 탐색과 연산이 여기서 출발
Node 트리를 이루는 기본 단위 값과 자식 포인터 또는 참조를 가진다
Parent / Child 부모-자식 관계 계층 구조와 이동 방향을 정의
Sibling 같은 부모를 가진 노드들 같은 레벨의 형제 노드
Leaf 자식이 없는 노드 말단 데이터 또는 종료 지점 역할
Depth 루트에서 해당 노드까지의 경로 길이 현재 노드가 몇 단계 아래에 있는지
Height 해당 노드에서 가장 먼 자손까지의 경로 길이 탐색/재귀 비용과 균형 정도를 읽는 기준
Ancestor / Descendant 조상 / 자손 관계 경로 상 포함 관계를 나타냄
Subtree 특정 노드를 루트로 하는 부분 트리 재귀와 분할 정복의 기본 단위

특히 depth와 height는 자주 헷갈립니다. depth는 위에서 아래로 내려온 거리이고, height는 현재 노드 아래에 얼마나 긴 경로가 남아 있는지를 말합니다. 트리 전체의 height는 루트의 height입니다.

순회

트리 순회는 어떤 노드를 어떤 순서로 방문할지 정하는 규칙이며, 전위·중위·후위·레벨 순회는 각각 목적이 다르다
순회 방식 방문 순서 실무 해석
Preorder 루트 → 서브트리들 현재 노드를 먼저 처리해야 할 때 적합
Inorder 왼쪽 서브트리 → 루트 → 오른쪽 서브트리 이진 검색 트리에서 정렬 순 출력과 잘 맞음
Postorder 서브트리들 → 루트 자식 처리가 끝난 뒤 부모를 처리할 때 적합
Level-order 깊이 순서대로, 루트부터 레벨별 방문 너비 우선 탐색과 같고 큐 기반 구현이 자연스러움

전위·중위·후위는 깊이 우선 성격을 띠고, 레벨 순회는 루트에서부터 깊이별로 내려가는 너비 우선 성격을 가집니다. 특히 중위 순회는 왼쪽/루트/오른쪽이 분명한 이진 트리 맥락에서 가장 자연스럽습니다.

Traversal Overview
Preorder   : root -> subtree
Inorder    : left -> root -> right
Postorder  : subtree -> root
Level-order: level by level from root

기본 구조

트리 구현은 크게 포인터 기반 노드 표현과, 완전 이진 트리에서 자주 쓰는 배열 기반 표현으로 나뉜다

가장 기본적인 방법은 각 노드가 부모나 자식에 대한 참조를 직접 갖는 포인터 기반 표현입니다. 일반 트리나 이진 트리는 이 방식이 가장 직관적이고, 서브트리 단위 연산과 재귀 구현이 쉽습니다.

반면 완전 이진 트리 계열에서는 배열 표현이 매우 강력합니다. Open Data Structures가 설명하듯, 노드를 breadth-first 순서로 배열에 두면 루트는 0번, 왼쪽 자식은 2i+1, 오른쪽 자식은 2i+2, 부모는 (i-1)/2 로 계산할 수 있습니다.

이 방식은 heap처럼 완전성 제약이 있는 구조에서는 포인터를 따로 저장하지 않아도 되기 때문에 매우 효율적입니다. 하지만 모양이 자유로운 일반 트리에는 잘 맞지 않습니다.

binary-tree/
├── pointer based
│   ├── value
│   ├── parent
│   ├── left
│   └── right
└── array based (complete binary tree)
    ├── root index = 0
    ├── left(i) = 2i + 1
    ├── right(i) = 2i + 2
    └── parent(i) = (i - 1) / 2

기본 구현

트리를 이해하는 가장 빠른 방법은 이진 트리 노드 하나와 재귀 순회 함수를 같이 보는 것이다
class Node<T> {
    T value;
    Node<T> left;
    Node<T> right;
    Node<T> parent;

    Node(T value) {
        this.value = value;
    }
}

public void preorder(Node<Integer> node) {
    if (node == null) return;

    System.out.println(node.value); // root
    preorder(node.left);            // left subtree
    preorder(node.right);           // right subtree
}
실전 포인트
트리 연산은 재귀와 잘 맞지만, 트리 높이가 너무 크면 재귀 깊이도 커집니다. Open Data Structures도 높이가 큰 트리에서는 재귀가 더 많은 스택 공간을 사용해 문제가 될 수 있다고 설명합니다. 그래서 편향된 트리에서는 반복문과 스택, 큐 구현도 함께 염두에 두는 편이 좋습니다.

패턴 1. 이진 트리와 균형 트리

이진 트리는 자식이 최대 둘인 가장 기본적인 트리이고, 균형 트리는 그 높이가 과하게 치우치지 않도록 제약을 추가한 구조다

이진 트리는 각 노드가 최대 두 자식을 가지는 트리입니다. 왼쪽과 오른쪽이 구분되기 때문에 중위 순회, BST, heap 같은 중요한 구조의 기반이 됩니다.

문제는 이진 트리라고 해서 항상 효율적인 것은 아니라는 점입니다. 한쪽으로 길게 치우친 트리는 사실상 연결 리스트처럼 동작할 수 있습니다. 그래서 균형 트리는 리프들이 루트에서 너무 멀리 차이나지 않도록 제약을 둡니다.

대표적으로 AVL 트리는 각 노드의 두 서브트리 높이 차이가 최대 1이 되도록 유지하는 balanced binary search tree이고, NIST 기준으로 조회·삽입·삭제가 O(log n) 입니다.

Binary Tree
- 각 노드의 자식 수가 최대 2개

Balanced Binary Tree
- 리프들이 루트에서 지나치게 멀어지지 않도록 제약
- 필요하면 rotation 으로 재균형

패턴 2. 이진 검색 트리(BST)

정렬 순서를 트리 구조 자체에 반영한 대표적인 예가 BST 이며, 왼쪽은 더 작고 오른쪽은 더 크다는 불변식이 핵심이다

이진 검색 트리는 각 노드의 왼쪽 서브트리 키가 현재 노드보다 작고, 오른쪽 서브트리 키가 더 크다는 규칙을 유지합니다. 이 규칙 덕분에 루트에서 비교를 반복하며 필요한 방향으로만 내려가 검색 범위를 줄일 수 있습니다.

또 BST에서는 중위 순회가 자연스럽게 정렬 순 출력을 만들어 냅니다. 그래서 정렬된 순회와 검색을 동시에 지원해야 하는 문제에서 매우 자주 등장합니다.

다만 BST의 실제 성능은 높이에 크게 좌우됩니다. 불변식만 지켜도 지나치게 치우친 트리가 될 수 있기 때문에, 실무에서는 AVL이나 red-black tree 같은 balanced BST가 더 자주 쓰입니다.

패턴 3. 힙, 트라이, B-트리

트리는 하나의 모양이 아니라, 어떤 불변식을 구조에 넣느냐에 따라 우선순위용 힙, 접두어용 트라이, 디스크 인덱스용 B-트리처럼 전혀 다른 역할을 하게 된다
구조 핵심 규칙 실무 해석
Heap complete tree + 부모가 더 extreme 한 key priority queue, heap sort 의 기반
Trie 공통 prefix 마다 노드를 둠 문자열/접두어 검색에 적합
B-tree 여러 자식을 갖는 balanced search tree 디스크/외부 메모리 인덱스에 적합

힙은 complete tree 위에 heap property를 얹은 구조입니다. 부모 키가 자식보다 더 크거나 더 작은 방향으로 정렬되어 있기 때문에, 루트에서 최소값 또는 최대값을 빠르게 얻는 priority queue와 잘 맞습니다.

트라이는 문자열 저장을 위해 공통 접두어마다 노드를 두는 트리입니다. 그래서 일반 비교 기반 검색과 달리, 문자열 길이와 접두어 공유 구조를 직접 활용할 수 있습니다.

B-트리는 한 노드가 여러 자식을 갖는 balanced search tree이며, NIST는 느린 메모리(예: 디스크)에서 높이와 접근 횟수를 작게 유지하는 데 좋은 구조라고 설명합니다. 그래서 데이터베이스 인덱스나 파일 시스템 인덱스 문맥에서 자주 등장합니다.

핵심 포인트
트리 구조를 배울 때 가장 중요한 것은 “트리 = BST”처럼 하나로 축소해서 보지 않는 것입니다. 같은 트리 계열이라도 정렬 불변식, 완전성, prefix 규칙, 외부 메모리 효율 같은 제약이 달라지면 성격 자체가 완전히 달라집니다.

한계와 주의점

트리 구조는 강력하지만, 높이가 커지거나 불변식이 무너지면 장점이 빠르게 사라지고 구현 복잡도도 급격히 올라간다

첫째, 트리는 높이에 민감합니다. 같은 노드 수라도 편향된 트리는 탐색 경로가 길어지고, 재귀 깊이도 함께 커집니다. 따라서 균형 유지가 필요한지, 편향이 허용되는 문제인지 먼저 판단해야 합니다.

둘째, 트리 종류마다 불변식이 다르기 때문에 서로 바꿔 생각하면 안 됩니다. BST는 정렬 검색에 강하지만 최소/최대 우선순위 추출만을 위해 존재하는 구조가 아니고, heap은 priority queue에 강하지만 전체 정렬 순 검색과는 잘 맞지 않습니다.

셋째, 포인터 기반 트리는 구현과 디버깅 난이도가 생각보다 높습니다. 부모/자식 연결이 잘못되면 서브트리 전체가 깨질 수 있고, 회전이나 삭제 연산은 특히 실수가 많습니다.

주의해야 할 지점
  • 높이가 커지면 탐색과 재귀 비용이 함께 커질 수 있음
  • BST, heap, trie, B-tree 는 모두 트리지만 불변식과 목적이 다름
  • 삭제, 회전, 병합 같은 연산은 포인터 연결 실수가 잦음
  • 완전 이진 트리가 아니라면 배열 표현이 항상 좋은 선택은 아님

자주 하는 실수

트리 구조를 어렵게 만드는 가장 흔한 원인은 용어보다도, 서로 다른 트리의 불변식과 목적을 한꺼번에 섞어 생각하는 데 있다
  • 트리와 그래프 이론의 tree 를 완전히 같은 의미로 생각함
  • BST 와 heap 을 비슷한 검색 트리로 착각함
  • 중위 순회가 모든 트리에서 자연스럽게 정의된다고 생각함
  • 트리 성능을 노드 수만 보고 판단하고 height 를 놓침
  • 완전 이진 트리가 아닌데도 배열 표현을 무리하게 적용함
  • 삭제 연산 후 부모/자식 링크 갱신을 빠뜨려 구조를 망가뜨림

실무 루틴

트리 구조를 적용할 때는 먼저 데이터의 관계와 불변식을 정하고, 그다음 높이·순회·표현 방식이 문제에 맞는지 확인하는 순서가 가장 안전하다
  1. 먼저 데이터가 정말 계층형 인지, 아니면 단순 순차 구조면 충분한지 판단한다.
  2. 트리에 넣을 불변식 이 무엇인지 정한다. 예를 들어 정렬, 우선순위, 접두어, 완전성 같은 규칙이다.
  3. 검색/삽입/삭제 성능이 height 에 얼마나 민감한지 본다.
  4. 포인터 기반 표현이 맞는지, complete binary tree 라면 배열 표현 이 더 나은지 검토한다.
  5. 순회 방식은 문제 목적에 맞춰 정한다. 정렬 출력이면 중위, 부모 우선이면 전위, 자식 후처리면 후위, 레벨별 처리면 BFS가 자연스럽다.
  6. 불균형이 걱정되면 BST 대신 balanced tree 계열을 먼저 고려한다.

디버깅

트리 디버깅은 값 하나를 보는 것보다, 구조 불변식과 높이와 순회 결과가 기대와 맞는지 먼저 확인하는 과정이다
1
먼저 현재 트리가 일반 트리인지, BST인지, heap인지, trie인지부터 구분하고 그 구조의 불변식을 다시 확인한다.
2
삽입이나 삭제 뒤에는 부모/자식 링크가 끊어지지 않았는지, 루트 참조가 바뀌어야 하는 상황을 놓치지 않았는지 본다.
3
BST라면 중위 순회 결과가 정렬 순서인지, heap이라면 부모가 자식보다 더 extreme 한 값을 유지하는지 확인한다.
4
성능이 이상하면 노드 수보다도 먼저 트리 높이와 편향 여부를 측정한다.
5
배열 기반 complete binary tree라면 인덱스 공식 2i+1, 2i+2, (i-1)/2 가 깨지지 않는지 확인한다.
점검 체크리스트
- 이 트리의 불변식은 무엇인가
- 루트와 부모/자식 링크가 올바른가
- 순회 결과가 기대한 의미와 맞는가
- height 가 비정상적으로 커지지 않았는가
- 배열 기반이라면 parent/child index 계산이 맞는가

요약

트리 구조의 핵심은 데이터를 루트와 서브트리 중심의 계층으로 나누고, 높이와 순회 규칙, 불변식을 이용해 검색과 우선순위와 접두어 탐색과 인덱싱 같은 문제를 구조적으로 해결하는 데 있다
  • ✅ 트리 구조는 루트와 부모-자식 관계를 기준으로 데이터를 계층적으로 다루는 자료구조다.
  • ✅ 자료구조 관점의 tree는 보통 rooted 이고 ordered 하게 다뤄진다.
  • ✅ depth, height, subtree, ancestor, descendant 같은 용어를 구분해야 구조를 정확히 읽을 수 있다.
  • ✅ 전위·중위·후위·레벨 순회는 각각 방문 의미가 다르다.
  • ✅ 이진 트리는 가장 기본적인 형태이고, balanced tree는 높이 편향을 줄이기 위한 제약을 가진다.
  • ✅ BST는 정렬 불변식, heap은 우선순위 불변식, trie는 prefix 불변식, B-tree는 외부 메모리 친화성을 구조에 반영한다.
  • ✅ 트리 성능은 노드 수뿐 아니라 height 에 크게 좌우된다.
  • ✅ 좋은 트리 설계는 자료형 이름보다도 불변식과 표현 방식이 문제에 맞는지부터 확인한다.

 

 

728x90