Skip to main content

Command Palette

Search for a command to run...

C/C++ 이진 트리(binary tree) 개요 및 구현(1)

Updated
4 min read

개요

트리는 노드들이 나무 가지처럼 연결된 비선형 계층적 자료구조이다. 하위 트리가 존재하고, 그 노드에 또 하위 트리가 존재하는 자료구조 이다. 트리의 맨 위에 있는 루트 노드가 존재한다. 우리가 알아볼 트리는 이진 트리이다. 이진 트리는 자식 노드(부모로부터 아래로 이어진 노드)가 2개 이하인 구조를 말한다. 트리의 사용 사례로는 다음과 같다

  • 계층 적 데이터 저장(파일,폴더)

  • 효율적인 검색 속도

  • 데이터 베이스의 인덱싱

트리에 관한 용어

  • degree - 각 노드가 가진 자식 노드(가지)의 수

  • root node(루트 노드) - 맨 위의 노드, 부모가 없다

  • leaf node(단말 노드) - 자식이 없는 노드

  • internal node(내부 노드) - leaf node가 아닌 노드

  • edge - 노드를 연결하는 선

  • depth - 루트에서 어떤 노드에 도달하기 위해 거쳐야 하는 edge의 수

  • level - 같은 깊이를 가진 노드의 집합, 맨 위부터 레벨 1, 2, 3 순으로 간다.

  • height - 루트 노드에서 가장 깊숙이 있는 노드의 깊이

이진 트리의 종류

  • 정 이진트리(Strict Binary Tree)

    오직 0 또는 2의 degree의 노드만 존재한다. 즉 자식이 무조건 두 개 또는 한 개이다.

  • 포화 이진트리(Perfect Binary Tree)

    모든 노드가 2개의 자식을 가지고 leaf 노드가 모두 같은 level이다.

  • 완전 이진트리(Complete Binary Tree)

    마지막 level을 제외하고 모든 노드가 채워져 있어야 한다. 마지막 레벨의 노드는 다 채워져 있을 수도 있고 아닐 수도 있다. 또한 노드는 왼쪽에서 오른 쪽으로 빈 공간 없이 채워져 있어야 한다.

이진 트리의 특징

  • height 당 최대/최소 노드

    • 최대 - n = 2^(h+1) -1

    • 최소 - n = h + 1

  • strict m-array(최대 dgree가 2인) tree 에서 height 당 최대/최소 노드

    • 최대 - (m^(h+1) - 1) / (m - 1)

    • 최소 - m*h + 1

    • 이진트리는 2-array tree다.

이진 트리의 표현

  • array

    하나의 배열에 노드를 순서대로 입력하는 것이다. 일반적으로 맨 위 level의 노드부터 해서 왼쪽에서 오른쪽 순서대로 입력한다.

    각 노드의 인덱스에 대한 규칙은 다음과 같다.

루트 노드1
노드 i의 부모i / 2
노드 i의 왼쪽 자식i * 2
노드 i의 오른쪽 자식i * 2 + 1
  • 연결 리스트 활용

    각 노드를 연결 리스트의 노드로 보는 것이다. 각 노드는 데이터와 왼쪽 노드를 향한 포인터, 오른쪽 노드를 향한 포인터로 이루어져 있다.

    훨씬 직관적이기에 연결 리스트 형태의 이진 트리 활용을 선호한다.

이진 트리 순회(traversing) 방식

  • preorder

    A->B->D->H->I->E->J->K->C->F->G

    부모 노드, 왼쪽 노드, 오른쪽 노드 순으로 순회함

  • postorder

    H->I->D->J->K->E->B->F->G->C->A

    왼쪽 노드, 오른쪽 노드, 부모 노드 순으로 순회함

  • Inorder

    H->D->I->B->J->E->K->A->F->C->G

    왼쪽 노드, 부모 노드, 오른쪽 노드 순으로 순회함

TreeCreate함수-트리 생성

트리 생성은 큐와 연결 리스트를 통해 구현한다. 원리는 다음과 같다. 노드를 생성하고, 큐에 넣는다. 큐의 선입선출 방식을 이용해 큐에서 노드를 뽑은 다음, 해당 노드의 왼쪽 자식 노드, 오른쪽 자식 노드를 생성해 큐에 집어 넣는다. 그 후, 다시 큐에서 노드를 뽑아 자식 노드를 만들어 큐에 넣는 방식을 반복한다. 코드를 통해 알아보자.

Node *root = NULL;
void Treecreate()
{
    Node *p, *t;
    Queue q;
    create(&q, 100);

    int x;
    printf("Enter root value");
    scanf("%d", &x);
    root = new Node;
    root->data = x;
    root->lchild = root->rchild = NULL;
    enqueue(&q, root);

    while(!isEmpty(q))
    {
        p = dequeue(&q);
        printf("Enter left child: %d", p->data);
        scanf("%d", &x);
        if (x != -1)
        {
            t = new Node;
            t->data = x;
            t->lchild = t->rchild = NULL;
            p->lchild = t;
            enqueue(&q, t);
        }

        printf("Enter right child: %d", p->data);
        scanf("%d", &x);
        if (x != -1)
        {
            t = new Node;
            t->data = x;
            t->lchild = t->rchild = NULL;
            p->rchild = t;
            enqueue(&q, t);
        }
    }
}

큐에 노드가 존재할 때, 노드를 큐에서 뽑아 자식 노드를 만들고, 자식 노드를 큐에 넣는 방식이다.

preorder함수-preorder 순회를 재귀 함수로

void preorder(Node *p)
{
    if (p)
    {
        printf("%d", p->data);
        preorder(p->lchild);
        preorder(p->rchild);
    }
}

부모 노드의 데이터를 출력하고, 왼쪽 자식 노드로 옮기고, 오른쪽 자식 노드로 옮기는 방식이다. 트리 자체가 하위 노드에 또 하위 노드가 있는 재귀 형태이기 때문에 재귀 함수로의 구현이 쉽다.

postorder함수-postorder 순회를 재귀 함수로

void postorder(Node *p)
{
    if (p)
    {
        postorder(p->lchild);
        postorder(p->rchild);
        printf("%d ", p->data);
    }
}

Inorder함수-Inorder 순회를 재귀 함수로

void inorder(Node *p)
{
    if (p)
    {
        inorder(p->lchild);
        printf("%d ", p->data);
        inorder(p->rchild);
    }
}

마무리

트리의 기초와 트리 생성, 순회 코드를 알아보았다. 비선형 구조인 만큼 직관적인 트리 생성을 이해하기 힘들다. 하지만, 연결 리스트와 재귀를 활용해서 쉽게 구현할 수 있다. 다음 글에서는 위의 순회 코드를 루프를 통해 구현해보자. 루프는 이보다 이해가 어려우니 주의를 기울이자.

More from this blog

[ZSH] tree 사용하기

들어가며 큰 규모의 프로젝트를 출시한 뒤, 후일을 위해서 더 늦기 전에 파일 정리 및 문서화를 진행해야했다. 문서화 작업을 하는 중에 기왕 정리하는 거 파일 구조를 이쁘게 트리 구조로 나열하여 코멘트를 달면 나중에 보더라도 이해하기 더 쉬울 것 같았다. 어떻게 해야 간지나는 트리 구조를 만들 수 있을까 방법을 찾다보니 역시나 파일 구조를 트리로 이쁘게 출력해주는 커맨드 툴이 존재했다. tree 커맨드에 대해서 알아보고 알짜배기 내용만 정리했다....

Feb 21, 20242 min read

[Next.js] parallel routes & intercepting routes

트위터 로그인 모달창을 만들어보며 넥스트의 parallel routes 와 intercepting routes 을 학습한 내용을 정리해보았습니다. 트위터 로그인 창을 확인해봅시다. 루트 디렉토리 화면을 배경으로 i/flow/login 페이지가 동시에 표시되고 있습니다. 저는 app router 를 학습하기 전까지는 createPortal 을 사용하여 포탈 영역에 로그인 컴포넌트를 띄우는 방식을 사용했었습니다. const NoLogin =()=...

Feb 1, 20244 min read

[React] Server component (RSC)

React.js 18 에 도입된 리액트 서버 컴포넌트는 서버에서 동작하는 리액트 컴포넌트를 의미합니다. Next가 권장하는 라우팅 방식인 app router의 기반이 되는 컴포넌트이기 때문에 app router 를 이해하기 위해서는 server component 에 대한 이해가 필요합니다. server component 리액트는 클라이언트단만을 컴포넌트화하는 대신, server component라는 개념을 통해 서버 영역을 컴포넌트화합니다. ...

Jan 29, 20243 min read

Flutter, JavaScript

42 posts