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

C++에서 재귀와 스택 없이 이진 트리 후위 순회 구현하기


문제 소개

이번 문제에서는 하나의 이진 트리가 주어지며, 재귀(recursion)와 스택(stack)을 사용하지 않고 이진 트리의 후위 순회 결과를 출력하는 것이 목표입니다.

이진 트리(Binary Tree)는 각 노드가 최대 2개의 자식 노드를 가질 수 있는 특수한 형태의 트리 자료구조입니다.

C++에서 재귀와 스택 없이 이진 트리 후위 순회 구현하기

후위 순회(Postorder Traversal)란?

후위 순회는 대표적인 트리 순회 기법 중 하나로, 왼쪽 서브트리 → 오른쪽 서브트리 → 루트 순서로 노드를 방문합니다. 즉, 자식 노드들을 모두 먼저 처리한 뒤에 부모(루트) 노드를 가장 마지막에 방문하는 방식입니다.

위 트리의 후위 순회 결과는 다음과 같습니다.

8 → 4 → 2 → 7 → 9 → 6

접근 방법

재귀 호출이나 스택 없이 트리를 순회하려면 깊이 우선 탐색(DFS)에 기반한 반복(iterative) 기법을 활용하고, 방문한 노드의 정보는 해시 테이블(hash table)에 저장하는 방식으로 문제를 해결할 수 있습니다.

예제 코드 1 — unordered_set 활용

다음은 위 접근 방식을 구현한 C++ 프로그램입니다.

#include <bits/stdc++.h>
using namespace std;
struct Node {
    int data;
    struct Node *left, *right;
};

void postOrderTraversal(struct Node* head) {
    struct Node* temp = head;
    unordered_set<Node*> visited;
    while (temp && visited.find(temp) == visited.end()) {
        if (temp->left && visited.find(temp->left) == visited.end())
            temp = temp->left;
        else if (temp->right && visited.find(temp->right) == visited.end())
            temp = temp->right;
        else {
            cout<<temp->data<<"\t";
            visited.insert(temp);
            temp = head;
        }
    }
}

struct Node* insertNode(int data) {
    struct Node* node = new Node;
    node->data = data;
    node->left = NULL;
    node->right = NULL;
    return (node);
}

int main() {
    struct Node* root = insertNode(6);
    root->left = insertNode(2);
    root->right = insertNode(9);
    root->left->left = insertNode(8);
    root->left->right = insertNode(4);
    root->right->left = insertNode(7);
    root->right->left->left = insertNode(13);
    cout<<"Post Order Traversal of the binary tree :\n";
    postOrderTraversal(root);
    return 0;
}

출력 결과

Post Order Traversal of the binary tree :
8    4    2    13    7    9    6

개선된 접근 방법 — unordered_map 활용

첫 번째 방법은 노드 하나를 출력할 때마다 항상 루트(head)부터 다시 탐색을 시작해야 하므로 비효율적입니다. 이를 개선하려면 unordered_map을 사용해 각 노드의 부모 노드를 함께 저장하면 됩니다. 그러면 자식 노드를 모두 방문한 노드는 맵에 기록된 부모 포인터를 참조하여 곧바로 상위 노드로 되돌아갈 수 있으므로, 루트까지 반복해서 거슬러 올라가는 오버헤드를 크게 줄일 수 있습니다.

또한 각 노드 구조체에 visited 플래그를 추가해 방문 여부를 노드 자체에 저장하면, 별도의 해시 셋 없이도 방문 관리를 할 수 있어 시스템 부담을 더욱 덜 수 있습니다.

예제 코드 2 — unordered_map으로 부모 포인터 저장

다음은 개선된 방식을 구현한 프로그램입니다.

#include <bits/stdc++.h>
using namespace std;
struct Node {
    int data;
    struct Node *left, *right;
    bool visited;
};

void postOrderTraversal(Node* root) {
    Node* n = root;
    unordered_map<Node*, Node*> postorder;
    postorder.insert(pair<Node*, Node*>(root, nullptr));
    while (n) {
        if (n->left && postorder.find(n->left) == postorder.end()) {
            postorder.insert(pair<Node*, Node*>(n->left, n));
            n = n->left;
        }
        else if (n->right && postorder.find(n->right) == postorder.end()) {
            postorder.insert(pair<Node*, Node*>(n->right, n));
            n = n->right;
        }
        else {
            cout<<n->data<<"\t";
            n = (postorder.find(n))->second;
        }
    }
}

struct Node* insertNode(int data) {
    struct Node* node = new Node;
    node->data = data;
    node->left = NULL;
    node->right = NULL;
    node->visited = false;
    return (node);
}

int main() {
    struct Node* root = insertNode(6);
    root->left = insertNode(2);
    root->right = insertNode(9);
    root->left->left = insertNode(8);
    root->left->right = insertNode(4);
    root->right->left = insertNode(7);
    root->right->left->left = insertNode(13);
    cout<<"Post Order Traversal of the binary tree :\n";
    postOrderTraversal(root);
    return 0;
}

출력 결과

Post Order Traversal of the binary tree :
8    4    2    13    7    9    6

마무리

두 방법 모두 시간 복잡도는 O(n)이며, 방문 정보를 저장하는 자료구조 때문에 공간 복잡도 역시 O(n)입니다. 재귀와 스택 없이도 후위 순회를 정확하게 수행할 수 있다는 점이 이 기법의 핵심이며, 특히 unordered_map에 부모 포인터를 저장하는 두 번째 방법이 불필요한 재탐색을 줄여 더 효율적입니다.