개발지식/자료구조

[자료구조] 트리(Tree)

thinktank911 2025. 11. 10. 18:45

트리(Tree)란?

트리 자료구조를 이해하는 것은 자료구조 전반과 알고리즘(특히 탐색, 재귀) 공부에서 중요하다. 트리의 정의부터 살펴보자면 계층적 구조(hierarchical structure)를 표현하는 비선형 자료구조다. 선형 구조는 데이터가 순차적으로 나열되어 1:1로 연결되는 구조를 말하고 비선형 구조는 데이터가 일렬로 나열되지 않고 다대다로 연결되는 구조를 뜻한다.

예를 들어 회사 조직도, 폴더 구조, 가계도 등이 트리 구조의 형태를 띤다.

cf) 선형 자료구조 : 스택, 큐

기본 용어

용어 설명
노드(Node) 데이터가 저장되는 하나의 단위 (예: 폴더, 사람, 값 등)
루트(Root) 트리의 가장 위에 있는 노드 (시작점)
간선(Edge) 노드와 노드를 연결하는 선
부모(Parent) 아래에 자식 노드를 가지는 노드
자식(Child) 부모 노드 아래에 연결된 노드
형제(Sibling) 같은 부모를 공유하는 노드
리프(Leaf) 자식이 없는 노드
서브트리(Subtree) 어떤 노드를 루트로 하는 하위 트리
레벨(Level) 루트로부터의 거리 (간선 수)
차수(Degree) 노드가 가지고 있는 자식 노드의 갯수

트리 구조 예시

출처 : https://yoongrammer.tistory.com/68

트리의 특징

1. 사이클이 없음 ➡️순환구조 불가

2. N개의 노드가 있으면 항상 N-1개의 간선이 있음

3. 루트에서 리프까지 단 하나의 경로만 존재 

트리의 종류

종류 설명
이진 트리(Binary Tree) 각 노드가 최대 2개의 자식을 가짐 (왼쪽, 오른쪽)
완전 이진 트리 마지막 레벨을 제외한 모든 레벨이 꽉 차 있음
포화 이진 트리 모든 노드가 0개 또는 2개의 자식을 가짐
이진 탐색 트리 (BST) 왼쪽 < 부모 < 오른쪽 규칙을 따름
균형 트리 (AVL, Red-Black Tree 등) 한쪽으로 치우치지 않도록 높이 균형 유지
힙 (Heap) 부모 ≥ 자식(최대 힙) or 부모 ≤ 자식(최소 힙)
트라이 (Trie) 문자열 저장용 트리 (예: 자동완성)

 

이진 트리(Binary Tree)

1) 개념

각 노드가 최대 2개의 자식(left, right) 갖는 트리를 의미한다. 이진 트리의 서브 트리는 공집합일 수 있다. 즉, 0~2개의 자식 노드가 존재할 수 있고, 모든 노드의 차수는 2 이하이다. 이진 트리에는 서브 트리간의 순서가 존재해 왼쪽 서브 트리와 오른쪽 서브 트리는 구별된다.

 

2) 일반 트리와 비교

  이진 트리 일반 트리
자식 노드의 개수 2개 이하 무관
노드의 개수 하나도 갖지 않을 수 있음 1개 이상
서브 트리 간 순서 존재 존재 X

 

3) 구현 예시

class Node {
  constructor(value) {
    this.value = value;
    this.left = null;
    this.right = null;
  }
}

class BinaryTree {
  constructor() {
    this.root = null;
  }

  insert(value) {
    const newNode = new Node(value);
    if (!this.root) {
      this.root = newNode;
      return;
    }

    // BFS로 비어 있는 자리 찾기 (단순 삽입)
    const queue = [this.root];
    while (queue.length) {
      const node = queue.shift();
      if (!node.left) {
        node.left = newNode;
        return;
      } else if (!node.right) {
        node.right = newNode;
        return;
      } else {
        queue.push(node.left, node.right);
      }
    }
  }

  preorder(node = this.root) {
    if (!node) return;
    console.log(node.value);
    this.preorder(node.left);
    this.preorder(node.right);
  }
}

// 사용 예시
const tree = new BinaryTree();
tree.insert(1);
tree.insert(2);
tree.insert(3);
tree.insert(4);
tree.preorder(); // 1 2 4 3

완전 이진 트리(Complete Binary Tree)

1) 개념

마지막 레벨을 제외하고 모두 채워져 있는 트리 구조. 

 

2) 특징

  • 마지막 레벨을 제외하고 모든 레벨이 완전히 채워져 있다.
  • 이때, 마지막 레벨은 완전히 차 있지 않아도 되지만, 노드가 '왼쪽에서 -> 오른쪽으로' 먼저 채워져야 한다.
  • 힙(Heap)의 기반 구조
  • 완전 이진 트리는, 배열을 사용해 표현 가능하다.

3) 구현 예시

class CompleteBinaryTree {
  constructor() {
    this.tree = [];
  }

  insert(value) {
    this.tree.push(value);
  }

  getParent(index) {
    return this.tree[Math.floor((index - 1) / 2)];
  }

  print() {
    console.log(this.tree);
  }
}

const cbt = new CompleteBinaryTree();
cbt.insert(1);
cbt.insert(2);
cbt.insert(3);
cbt.insert(4);
cbt.insert(5);
cbt.print(); // [1,2,3,4,5]

포화 이진 트리(Full Binary Tree)

1) 개념

각 레벨에 노드가 꽉 차 있는 이진 트리로, 각 노드에 레벨 단위로 왼쪽에서 오른쪽으로 번호를 붙일 수 있다. 부여된 번호는 항상 일정하다.

 

2) 특징

  • 포화 이진 트리는 모든 레벨이 노드로 가득 차 있는 트리이다.
  • 모든 말단 노드가 '동일한 깊이 or 레벨'을 갖는다.
  • 트리의 노드 개수 = [2^k-1 개] 이며, 이때 k 는 트리의 높이이다. 

이진 트리 간 포함관계

이진 탐색 트리 (Binary Search Tree, BST)

1) 개념

왼쪽 < 부모 < 오른쪽 규칙을 따름

 

2) 특징

  • 왼쪽 < 루트 < 오른쪽
  • 탐색, 삽입, 삭제가 효율적 (평균 O(log n))

3) 구현예시

class BSTNode {
  constructor(value) {
    this.value = value;
    this.left = null;
    this.right = null;
  }
}

class BST {
  constructor() {
    this.root = null;
  }

  insert(value) {
    const newNode = new BSTNode(value);
    if (!this.root) return (this.root = newNode);

    let current = this.root;
    while (true) {
      if (value < current.value) {
        // 현재 값이 부모와 비교해서 작은 값이면 왼쪽에 넣기 
        if (!current.left) return (current.left = newNode);
        current = current.left;
      } else {
        // 현재 값이 부모와 비교해서 큰 값이면 오른쪽에 넣기
        if (!current.right) return (current.right = newNode);
        current = current.right;
      }
    }
  }

  search(value) {
    let current = this.root;
    while (current) {
      if (value === current.value) return true;
      current = value < current.value ? current.left : current.right;
    }
    return false;
  }
}

// 사용 예시
const bst = new BST();
bst.insert(10);
bst.insert(5);
bst.insert(15);
    //     10
    //   /    \
    // 5       10


console.log(bst.search(5));  // true
console.log(bst.search(7));  // false

 

트라이 (Trie, Prefix Tree)

1) 개념

문자열 저장용 트리 (예: 자동완성)

 

2) 특징

  • 문자열 검색/자동완성에 특화된 트리
  • 각 노드가 문자 하나씩을 가짐

3) 구현예시

class TrieNode {
  constructor() {
    this.children = {};
    this.isEndOfWord = false;
  }
}

class Trie {
  constructor() {
    this.root = new TrieNode();
  }

  // 단어를 사전에 넣기 
  insert(word) {
    let node = this.root;
    for (let ch of word) {
      if (!node.children[ch]) node.children[ch] = new TrieNode();
      node = node.children[ch]; // isEndOfWord 찍어주기 위해
    }
    node.isEndOfWord = true;
  }

  // 사전에 단어 있는지 검색
  search(word) {
    let node = this.root;
    for (let ch of word) {
      if (!node.children[ch]) return false;
      node = node.children[ch];
    }
    return node.isEndOfWord;
  }

  // 사전에 해당 단어로 시작하는 말 있는지 검색
  startsWith(prefix) {
    let node = this.root;
    for (let ch of prefix) {
      if (!node.children[ch]) return false;
      node = node.children[ch];
    }
    return true;
  }
}

// 사용 예시
const trie = new Trie();
trie.insert("cat");
trie.insert("car");
console.log(trie.search("car"));     // true  → "car"가 완전히 있음
console.log(trie.startsWith("ca"));  // true  → "ca"로 시작하는 단어 있음
console.log(trie.search("cap"));     // false → "cap"은 없음

 

균형 이진 트리 (Balanced Binary Tree)

1) 개념

트리의 왼쪽과 오른쪽 서브트리의 높이 차이가 일정 수준 이하로 유지되는 이진 탐색 트리

즉, 한쪽으로 쏠리지 않고 “균형 잡힌” 형태를 유지하는 트리

 

2) 특징

  • 기본 구조 : 이진 탐색 트리 (BST) 기반
  • 왼쪽, 오른쪽 서브트리 높이 차 ≤ 1 (혹은 일정 범위)
  • 삽입/삭제 시 회전(Rotation)을 통해 균형 복원
  • 삽입 / 삭제 / 탐색 모두 O(log n)
  • 대표 종류 : AVL 트리, Red-Black 트리, B-Tree, B+Tree 등

 


트리 순회

트리는 비선형 구조라서 순회(Traversal) 방식이 중요하다.

순회는 많이 알려진 방법인 깊이우선탐색(DFS)너비우선탐색(BFS) 으로 나눌 수 있다.

깊이우선탐색의 탐색방법에는 3가지가 존재하는데 전위 순회, 중위 순회, 후위 순회가 있다.

너비우선탐색은 레벨 순으로 차례대로 탐색하는 레벨 순회 방식이다.

 

1. 전위 순회(Preorder)

  • 루트 ➡️왼쪽 ➡️ 오른쪽
  • 방문순서 : 12 → 2 → 6 → 3 → 8 → 16 → 10 ....

  • 구현 코드
// 노드 구조 정의
class Node {
  constructor(value) {
    this.value = value;
    this.left = null;
    this.right = null;
  }
}

// 전위 순회 함수 (루트 → 왼쪽 → 오른쪽)
function preorder(node) {
  if (!node) return;
  console.log(node.value); // ① 루트 방문
  preorder(node.left);     // ② 왼쪽 자식 탐색
  preorder(node.right);    // ③ 오른쪽 자식 탐색
}

// 트리 구성 예시
//         A
//        / \
//       B   C
//      / \   \
//     D   E   F

const root = new Node('A');
root.left = new Node('B');
root.right = new Node('C');
root.left.left = new Node('D');
root.left.right = new Node('E');
root.right.right = new Node('F');

// 전위 순회 실행
preorder(root);			// A B D E C F

 

2. 중위 순회(Inorder)

  • 왼쪽 ➡️ 루트 ➡️ 오른쪽
  • 방문순서 : 3 → 6 → 8 → 2 → 16 → 12 → 4 → 10 → 7 ...

  • 구현 코드
// 노드 구조 정의
class Node {
  constructor(value) {
    this.value = value;
    this.left = null;
    this.right = null;
  }
}

// 중위 순회 함수 (왼쪽 → 루트 → 오른쪽)
function inorder(node) {
  if (!node) return;
  inorder(node.left);       // ① 왼쪽 자식 탐색
  console.log(node.value);  // ② 루트 방문
  inorder(node.right);      // ③ 오른쪽 자식 탐색
}

// 트리 구성 예시
//         A
//        / \
//       B   C
//      / \   \
//     D   E   F

const root = new Node('A');
root.left = new Node('B');
root.right = new Node('C');
root.left.left = new Node('D');
root.left.right = new Node('E');
root.right.right = new Node('F');

// 중위 순회 실행
inorder(root);		// D B E A C F

 

3. 후위 순회(Postorder)

  • 왼쪽 ➡️ 오른쪽 ➡️ 루트 
  • 방문순서 : 3 → 8 → 6 → 16 → 2 → 4 → 9 → 1 → 7 → 10 → 12

  • 구현 코드
// 노드 구조 정의
class Node {
  constructor(value) {
    this.value = value;
    this.left = null;
    this.right = null;
  }
}

// 후위 순회 함수 (왼쪽 → 오른쪽 → 루트)
function postorder(node) {
  if (!node) return;
  postorder(node.left);     // ① 왼쪽 자식 탐색
  postorder(node.right);    // ② 오른쪽 자식 탐색
  console.log(node.value);  // ③ 루트 방문
}

// 트리 구성 예시
//         A
//        / \
//       B   C
//      / \   \
//     D   E   F

const root = new Node('A');
root.left = new Node('B');
root.right = new Node('C');
root.left.left = new Node('D');
root.left.right = new Node('E');
root.right.right = new Node('F');

// 후위 순회 실행
postorder(root);		// D E B F C A

 

4. 레벨 순회(Level Order)

  • 위에서 아래로, 왼쪽에서 오른쪽으로 (BFS)
  • 큐(Queue)를 이용해서 한 레벨씩 순회
  • 방문순서 : 0 → 1 → 2 → 3 → 4 → 5 → 6

  • 구현 코드
// 노드 구조 정의
class Node {
  constructor(value) {
    this.value = value;
    this.left = null;
    this.right = null;
  }
}

// 레벨 순회 함수 (BFS)
function levelOrder(root) {
  if (!root) return;

  const queue = [root]; // 방문할 노드를 저장하는 큐

  while (queue.length > 0) {
    const node = queue.shift(); // 큐 맨 앞의 노드 꺼내기
    console.log(node.value);    // 방문

    // 왼쪽, 오른쪽 자식이 있다면 큐에 추가
    if (node.left) queue.push(node.left);
    if (node.right) queue.push(node.right);
  }
}

// 트리 구성 예시
//         A
//        / \
//       B   C
//      / \   \
//     D   E   F

const root = new Node('A');
root.left = new Node('B');
root.right = new Node('C');
root.left.left = new Node('D');
root.left.right = new Node('E');
root.right.right = new Node('F');

// 레벨 순회 실행
levelOrder(root);		// A B C D E F

참고

- https://simmigyeong.tistory.com/23

- https://medium.com/quantum-ant/%ED%8A%B8%EB%A6%AC-tree-cec69cfddb14