Computer >> 컴퓨터 >  >> 프로그래밍 >> 프로그래밍

데이터 구조 레벨 순서 트리 순회 완벽 정리 (C++ 구현 예제 포함)

이 글에서는 이진 탐색 트리(Binary Search Tree)를 레벨 순서(level-order)로 순회하는 방법을 살펴보겠습니다. 레벨 순서 순회는 루트 노드부터 시작해 같은 깊이(레벨)에 있는 노드들을 왼쪽에서 오른쪽으로 차례대로 방문한 뒤, 한 단계 아래 레벨로 내려가는 방식입니다. 너비 우선 탐색(BFS, Breadth-First Search)과 같은 개념으로, 큐(queue) 자료구조를 활용해 손쉽게 구현할 수 있습니다.

레벨 순서 순회의 동작 원리

예를 들어 다음과 같은 이진 탐색 트리가 있다고 가정해 보겠습니다.

데이터 구조 레벨 순서 트리 순회 완벽 정리 (C++ 구현 예제 포함)

루트 10에서 출발해 각 레벨을 순서대로 방문하면 순회 결과는 다음과 같습니다.

순회 순서: 10 → 5 → 16 → 8 → 15 → 20 → 23

  • 레벨 0: 10
  • 레벨 1: 5, 16
  • 레벨 2: 8, 15, 20, 23

알고리즘

레벨 순서 순회는 큐를 이용해 다음 절차로 수행됩니다.

levelOrderTraverse(root):
Begin
    define queue que to store nodes
    insert root into the que.
    while que is not empty, do
        item := item present at front position of queue
        print the value of item
        if left of the item is not null, then
            insert left of item into que
        end if
        if right of the item is not null, then
            insert right of item into que
        end if
        delete front element from que
    done
End

핵심 아이디어는 단순합니다. 방문한 노드의 자식들을 큐에 미리 넣어두면, 큐의 선입선출(FIFO) 특성 덕분에 자연스럽게 위쪽 레벨부터 아래쪽 레벨 순으로 노드를 처리할 수 있습니다. 시간 복잡도는 모든 노드를 한 번씩 방문하므로 O(n), 공간 복잡도도 최악의 경우 마지막 레벨의 노드 수만큼 저장해야 하므로 O(n)입니다.

C++ 구현 예제

#include<iostream>
#include<queue>
using namespace std;
class node{
    public:
        int h_left, h_right, bf, value;
        node *left, *right;
};
class tree{
    private:
        node *get_node(int key);
    public:
        node *root;
        tree(){
            root = NULL; // 처음에는 루트를 NULL로 설정
        }
        void levelorder_traversal(node *r);
        node *insert_node(node *root, int key);
};
node *tree::get_node(int key){
    node *new_node;
    new_node = new node; // 새 노드를 동적으로 생성
    new_node->h_left = 0; new_node->h_right = 0;
    new_node->bf = 0;
    new_node->value = key; // 주어진 키 값을 노드에 저장
    new_node->left = NULL; new_node->right = NULL;
    return new_node;
}
void tree::levelorder_traversal(node *root){
    queue <node*> que;
    node *item;
    que.push(root); // 가장 먼저 루트를 큐에 삽입
    while(!que.empty()){
        item = que.front(); // 큐의 앞쪽(front) 요소를 가져옴
        cout << item->value << " ";
        if(item->left != NULL) // 왼쪽 자식이 있으면 큐에 삽입
            que.push(item->left);
        if(item->right != NULL) // 오른쪽 자식이 있으면 큐에 삽입
            que.push(item->right);
        que.pop(); // 처리가 끝난 요소를 큐에서 제거
    }
}
node *tree::insert_node(node *root, int key){
    if(root == NULL){
        return (get_node(key)); // 트리가 비어 있으면 새 노드를 루트로 생성
    }
    if(key < root->value){ // 키가 루트 값보다 작으면 왼쪽 서브트리로 이동
        root->left = insert_node(root->left, key);
    } else if(key > root->value) { // 키가 루트 값보다 크면 오른쪽 서브트리로 이동
        root->right = insert_node(root->right, key);
    }
    return root; // 키가 이미 존재하면 중복 삽입하지 않음
}
main(){
    node *root;
    tree my_tree;
    // 트리에 여러 개의 키를 삽입
    my_tree.root = my_tree.insert_node(my_tree.root, 10);
    my_tree.root = my_tree.insert_node(my_tree.root, 5);
    my_tree.root = my_tree.insert_node(my_tree.root, 16);
    my_tree.root = my_tree.insert_node(my_tree.root, 20);
    my_tree.root = my_tree.insert_node(my_tree.root, 15);
    my_tree.root = my_tree.insert_node(my_tree.root, 8);
    my_tree.root = my_tree.insert_node(my_tree.root, 23);
    cout << "Level-Order Traversal: ";
    my_tree.levelorder_traversal(my_tree.root);
}

실행 결과

Level-Order Traversal: 10 5 16 8 15 20 23

출력 결과에서 확인할 수 있듯이, 노드들이 루트부터 시작해 레벨별로 왼쪽에서 오른쪽 순서로 정확히 방문된 것을 볼 수 있습니다. 이처럼 레벨 순서 순회는 트리의 구조를 위에서 아래로, 폭넓게 탐색해야 하는 상황(예: 트리의 최소 깊이 계산, 레벨별 데이터 출력 등)에서 매우 유용하게 활용됩니다.