2 minute read

다원 탐색 트리 란?


다원 탐색 트리(Multiway Search Tree) 또는 m-원 탐색 트리(m-way search tree)는 다음과 같은 특징을 갖는 트리입니다.

  • 하나의 노드는 최대 m-1개의 키(key)와 m개의 자식(child)을 가질 수 있습니다. 여기서 m은 트리의 차수(order)라고 합니다.
  • 각 노드 안의 키는 오름차순으로 정렬되어 있습니다.
  • 노드의 키 Ki와 Ki+1 사이의 서브 트리에 있는 모든 값은 Ki보다 크고 Ki+1보다 작습니다
  • 모든 리프 노드는 같은 깊이를 가집니다. (균형 트리)

이진 탐색 트리는 m=2인 다원 탐색 트리의 특수한 경우입니다.

MultiwaySearchTree
(이미지출처 geeksforgeeks)

특징

  • 높이 감소: 하나의 노드가 여러 개의 키를 가질 수 있기 때문에, 같은 수의 데이터를 저장하는 이진 탐색 트리에 비해 트리의 높이가 낮아집니다. 이는 디스크 접근 횟수를 줄여 성능 향상에 도움이 됩니다.
  • 균형 유지: 모든 리프 노드가 같은 깊이를 가지므로, 트리의 균형이 유지됩니다. 이를 통해 탐색, 삽입, 삭제 연산의 시간 복잡도가 보장됩니다.
  • 외부 저장 장치에 적합: 하나의 노드를 디스크의 하나의 블록에 저장하여 디스크 I/O 횟수를 최소화할 수 있습니다.



다원 탐색 트리의 종류


  • B-트리 (B-tree): 가장 널리 사용되는 다원 탐색 트리 중 하나입니다. 각 노드의 키 개수에 최소 개수와 최대 개수에 대한 제약이 있습니다.
  • B+ 트리 (B+ tree): B-트리의 변형으로, 모든 데이터를 리프 노드에 저장하고, 리프 노드들을 연결 리스트로 연결하여 순차 접근을 효율적으로 지원합니다. 데이터베이스 인덱싱에 주로 사용됩니다.
  • B* 트리 (B* tree): B-트리의 또 다른 변형으로, 형제 노드 간의 키 공유를 통해 공간 활용률을 높입니다.



장단점


장점

  • 효율적인 대용량 데이터 관리: 디스크 I/O 횟수를 최소화하여 대용량 데이터 처리에 적합합니다.
  • 빠른 탐색, 삽입, 삭제: 균형 트리이므로 O(log n)의 시간 복잡도를 보장합니다.

단점

  • 구현 복잡: 삽입, 삭제, 분할 등의 연산 구현이 비교적 복잡합니다.
  • 메모리 오버헤드: 각 노드에 여러 개의 키와 자식 포인터를 저장해야 하므로, 메모리 사용량이 증가할 수 있습니다.



예제


#include <iostream>
#include <vector>
#include <algorithm>

const int M = 3; // 트리의 차수 (M-원 트리)

struct Node {
    std::vector<int> keys;
    std::vector<Node*> children;
    bool isLeaf;

    Node() : isLeaf(true) {}
};

void insertNonFull(Node* node, int key) {
    int i = node->keys.size() - 1;

    if (node->isLeaf) {
        // 리프 노드인 경우 키 삽입
        while (i >= 0 && key < node->keys[i]) {
            node->keys[i + 1] = node->keys[i];
            i--;
        }
        node->keys[i + 1] = key;
    } else {
        // 리프 노드가 아닌 경우 적절한 자식 노드 선택
        while (i >= 0 && key < node->keys[i]) {
            i--;
        }
        i++;

        // 선택된 자식 노드가 가득 차 있는 경우 분할
        if (node->children[i]->keys.size() == M - 1) {
            // splitChild(node, i); // 분할 함수 (구현 생략)
            // 분할 후 다시 적절한 위치 찾기
            if (key > node->keys[i]) {
                i++;
            }
        }
        insertNonFull(node->children[i], key);
    }
}

int main() {
    Node* root = new Node();
    insertNonFull(root, 10);
    insertNonFull(root, 20);
    insertNonFull(root, 30);
    insertNonFull(root, 40);
    insertNonFull(root, 50);

    // ...

    return 0;
}


Top