C++
 Computer >> コンピューター >  >> プログラミング >> C++

C++でTrie(トライ木)を実装する方法|挿入・検索・削除のコード例付き

本記事では、C++を使ってTrie(トライ木)を実装する方法を詳しく解説します。Trieは木構造ベースのデータ構造で、大量の文字列データセットの中から特定のキーを効率的に検索・取得するために利用されます。

Trieとはどのようなデータ構造か

Trieは「プレフィックス木(接頭辞木)」とも呼ばれ、文字列を1文字ずつ枝分かれさせながら格納する木構造です。根からあるノードまでの経路が、1つの文字列の接頭辞に対応します。本記事の実装では英大文字(A〜Z)のみを扱うため、各ノードは最大26個の子ポインタを持ちます。辞書検索やオートコンプリート機能など、文字列を高速に処理したい場面で広く活用されています。

主要な関数と擬似コード

insert():キーの挿入

開始
関数 insert():
  キーがまだ存在しない場合、トライにキーを挿入する。
  キーが既存ノードの接頭辞である場合は、葉ノードとしてマークするだけにする。
終了

deleteNode():キーの削除

開始
関数 deleteNode():
  木が空であれば null を返す。
  キーの最後の文字を処理している場合、
    削除後、そのノードはもはや文字列の終端ではなくなる。
    指定したキーが他の文字列の接頭辞でなければ、ノードを削除して root = NULL とする。
  キーが最後の文字でない場合、
    ASCII値をもとに求めた子ノードに対して再帰的に処理を行う。
  ルートに子が残っておらず、かつ別の単語の終端でもない場合、
    ノードを削除して root = NULL とする。
終了

C++による実装例

#include <bits/stdc++.h>
using namespace std;
const int ALPHA_SIZE = 26;

struct Trie {
    struct Trie *child[ALPHA_SIZE];
    bool endofstring; // ノードが単語の終端を表す場合にtrue
};
struct Trie *createNode(void) { // 新規ノードの作成
    struct Trie *tNode = new Trie;
    tNode->endofstring = false;
    for (int i = 0; i < ALPHA_SIZE; i++)
        tNode->child[i] = NULL;
    return tNode;
}
void insert(struct Trie *root, string key) {
    struct Trie *curr = root;
    for (int i = 0; i < key.length(); i++) {
        int index = key[i] - 'A';
        if (!curr->child[index])
            curr->child[index] = createNode();
        curr = curr->child[index];
    }
    curr->endofstring = true; // 最後のノードを葉としてマーク
}
bool search(struct Trie *root, string key) { // キーがトライ内に存在するか確認し、存在すればtrueを返す
    struct Trie *curr = root;
    for (int i = 0; i < key.length(); i++) {
        int index = key[i] - 'A';
        if (!curr->child[index])
            return false;
        curr = curr->child[index];
    }
    return (curr != NULL && curr->endofstring);
}
bool isEmpty(Trie* root) { // ルートに子ノードが存在するかどうかを確認
    for (int i = 0; i < ALPHA_SIZE; i++)
        if (root->child[i])
            return false;
    return true;
}
Trie* deletion(Trie* root, string key, int depth = 0) {
    // 木が空の場合はnullを返す
    if (!root)
        return NULL;
    if (depth == key.size()) { // キーの最後の文字を処理している場合、
        if (root->endofstring)
            root->endofstring = false; // 削除後、このノードは文字列の終端ではなくなる
        if (isEmpty(root)) { // 指定したキーが他の文字列の接頭辞でない場合、
            delete (root);
            root = NULL;
        }
        return root;
    }
    // キーが最後の文字でない場合、
    int index = key[depth] - 'A';
    root->child[index] =
    deletion(root->child[index], key, depth + 1); // ASCII値をもとに求めた子ノードに対して再帰的に処理
    if (isEmpty(root) && root->endofstring == false) { // 子が残っておらず、別の単語の終端でもない場合
        delete (root);
        root = NULL;
    }
    return root;
}
int main() {
    string inputs[] = {"HELLOWORLD","HI","BYE", "THE","THENA"}; // 入力キー(大文字のA〜Zのみ)
    int n = sizeof(inputs)/sizeof(inputs[0]);
    struct Trie *root = createNode();
    for (int i = 0; i < n; i++)
        insert(root, inputs[i]);
    search(root, "HELLOWORLD")? cout << "Key is Found\n" :
    cout << "Key is not Found\n";
    search(root, "HE")? cout << "Key is Found\n" :
    cout << "Key is not Found\n";
    deletion(root, "THEN")? cout << "Key is deleted\n" :
    cout << "Key is not Deleted\n";
    return 0;
}

実行結果

Key is Found
Key is not Found
Key is deleted

出力の解説

  • "HELLOWORLD":挿入済みの完全なキーなので「Found」と表示されます。
  • "HE":トライ上には存在しますが、これは他のキーの接頭辞にすぎず、endofstringフラグが立っていないため「not Found」になります。Trieの検索は完全一致で行われる点がポイントです。
  • "THEN":挿入済みのキーなので正常に削除され、「deleted」と表示されます。

まとめ

Trieを使うと、キーの長さをLとしたとき、挿入・検索・削除のいずれもO(L)の時間計算量で実行できます。ハッシュテーブルと比較すると、共通の接頭辞を持つキーを効率よく管理でき、辞書順の走査や前方一致検索にも適しています。大量の文字列を扱うアプリケーションでは、ぜひTrieの導入を検討してみてください。

  1. 【C++】STLのset_symmetric_differenceで集合の対称差を実装するプログラム

    本記事では、C++の標準テンプレートライブラリ(STL)に含まれる set_symmetric_difference 関数を使って、2つの集合の「対称差」を求めるプログラムを紹介します。 対称差とは、2つの集合のうち「どちらか一方にだけ存在し、両方には存在しない」要素から構成される集合のことです。 主な集合演算の種類 和集合(Union):どちらか一方に含まれるすべての要素 積集合(Intersection):両方に共通して含まれる要素 対称差(Symmetric Difference / 排他的論理和 XOR):片方にのみ含まれる要素 差集合(Difference / 減算):一方から他方

  2. C++でグラフの隣接行列を実装する方法【サンプルコード付き解説】

    隣接行列とは グラフの隣接行列(Adjacency Matrix)とは、V×Vのサイズを持つ正方行列のことです。ここでVはグラフGの頂点数を表します。行列の行と列にはそれぞれ頂点が対応付けられ、頂点iから頂点jへの辺が存在する場合は、i行目・j列目の要素に1が格納されます(重み付きグラフの場合は、辺の重みなどの非ゼロの値が入ります)。辺が存在しない場合は0が格納されます。 なお、無向グラフの場合、辺は双方向につながりを持つため、隣接行列は必ず対称行列になります。つまり、adj[i][j]とadj[j][i]は常に同じ値となります。 隣接行列表現の計算量 空間計算量: 隣接行列にはO(V²)