ポインタベースの Trie ライブラリ. ソース
ポイント
- ポインタと new で実装.Trie という構造体がノードを表す.
- 実装上,ノードは増やすだけで,削除はしない.
- (template parameter で指定する) 3つのモードがある.これらのモードは,メンバ関数 insert / erase / search の振舞に影響する
- TRIE_SET_SINGLE … set に相当
- TRIE_SET_MULTI … multiset に相当
- TRIE_SET_NONE … どちらにも該当しない
- ノードに格納するユーザデータを定義できる (user というフィールドがある)
使用法
using MyTrie = Trie<26, 'a', TRIE_SET_SINGLE>; // 文字種類数,先頭文字,モード
MyTrie* root = new MyTrie; // Trie の作成.
MyTrie* p1 = root->insert("abcde"); // 挿入.必要なノードを作ると同時に,「要素」としても登録.
MyTrie* p2 = root->search("abc"); // 要素ではないので p2 == nullptr
MyTrie* p3 = root->search("abcde"); // 要素なので,p3 == p1
MyTrie* p4 = root->get_node("abc"); // 要素かどうかに関係なくノードを取る
// ノードがないときには nullptr が返るが,この場合は,p4 == p2 になる.
assert(p3->reside == 1); // p3が表す文字列は要素である.
assert(p4->reside == 0); // p4が表す文字列は要素でない.
root->insert("abab");
root->insert("abab"); // いまは SINGLE なので,何も起こらない
assert(root->size_st == 2) // 部分木に存在する要素の数.
p3->erase(); // 要素としての削除.葉であってもノードは削除しない.
assert(p3->reside == 0);
assert(p4->size_st == 1);
// 文字列 s の全prefixを処理するループ
// 長い方から:
for (MyTrie* p = root->get_node(s); p; p = p->parent) // ...;
// 短い方から:
MyTrie* p = root; int i = 0;
for (; i <= ssize(s); p = p->get_child_val(s[i++], true) // ...;
インタフェース
テンプレートパラメタ
template <
int bt_size, // 文字種類数
char from, // 最初の文字
int mode, // モード (TRIE_SET_SINGLE, TRIE_SET_MULTI, TRIE_SET_NONE)
typename User = monostate, // ユーザデータの型
typename S = string, // 管理するデータの型.string とか vector<char> とか
bool compact = ..., // 省メモリ型 (下記参照)
bool has_offset = true // オフセットの管理方法
>
struct Trie {
bt_size… 文字種類数.小文字の文字列なら26, 01列なら2など.from… 最初の文字.小文字なら'a', 01文字列なら'0',整数の01列なら0など.mode… TRIE_SET_SINGLE, TRIE_SET_MULTI, TRIE_SET_NONE のいずれか.User… ユーザデータの型.引数無しで構築できなければならない.省略値はmonostateで,これは,何も要素を持たない構造体.S… この trie で管理するデータの型.たいてい string だろうけれど,vector<int>とかでも可.ただし,値は from から from + bt_size までで,char の範囲に入っている必要がある. この文書で,以下「文字列」とあるのは,正確には「型 S のデータ」である.compact… たとえば全ノードに長さ26のベクトルを持たせるというのは無駄なので,ここを true にすると,もう少し領域を節約する.ただし,少し遅くなる (そんなには遅くないと思う).false にすると,固定長 array になる.デフォルトは,文字種類数が8以上63以下のとき true.文字種類数が64以上あると,true では機能しない.また,文字種類数が4未満のときには Trie のサイズ自体が false の方が小さい.has_offset… ノードに,何文字目であるかを持たせるかどうか.false にすると,持たせずに,必要なときには計算する.
コンストラクタ
下の2つのコンストラクタがあるが,通常必要なのは root を作る時だけで,この時は,引数無しの方を用いる.
Trie() = default;
Trie(Trie* p, int offset_);
データメンバ
Trie* parent; // 親
int reside; // このノードが表す文字列が含まれる数 (SINGLEなら0か1).
int size_st; // 部分木に存在する要素の数.
User user; // ユーザデータ
-
parent は親へのポインタ.根の parent は nullptr である.
-
reside と size_t は,モードが TRIE_SET_SINGLE と TRIE_SET_MULTI のときのみ有効である.
-
各ノードが表す文字列が,Trie が表す set/multiset の要素として存在する数を,
resideが示している. -
size_st は,部分木の reside の値の総和である.したがって,set/multiset 内にある, このノードを prefix として持つ文字列の数を示す.
-
-
user はユーザーデータ.型 User はテンプレートパラメタで指定する.デフォルトは
monostate. このフィールドは常に有効だが,特にモードが TRIE_SET_NONE のときには,reside と size_t が無効なので, このフィールドを使わないと,意味のあることはできないであろう.
挿入
文字列を挿入するには,root->insert(s) を実行する.集合に s が要素として挿入され,
そのノードへのポインタが返される.
TRIE_SET_SINGLE, TRIE_SET_MULTI のときは,
返されるノードのデータメンバ reside の値が 1 増やされる (MULTIのとき) か,1 に設定され (SINGLE のとき),
先祖の size_st が更新される.
ルート以外のノード p に対して p->insert(s) を実行することもでき,p が表す文字列 に s を付加した文字列が
対象になる
たとえば,p が "abc" を表しているときに,p->insert("def") を実行すると,
"abcdef" が挿入されることになる.この場合も,size_st の更新は,p までではなく,ルートまで行われる.
削除
要素としての文字列 s を削除するには,
- その文字列を表すノード p に対して
p->erase()を実行するか,または, root->erase(s)を実行する.
どちらも効果は同じである.
ルート以外のノード p に対して p->insert(s) を実行することもでき,p が表す文字列 に s を付加した文字列が
対象になる
TRIE_SET_SINGLE, TRIE_SET_MULTI のときは,
このノードの reside の値が 1 以上だった場合にそれが 1 減らされ,
先祖の size_st が (呼出かたにかかわらずルートまで) 更新される.
このメンバ関数は何も返さない.
検索
文字列 s に対し,
root->get_node(s)を実行すると,s を表すノードが存在すればそのノードへのポインタが返る. 存在しないときには nullptr が返る.root->get_or_create_node(s)を実行すると,s を表すノードが存在すればそのノードへのポインタが返る. 存在しないときには,s を表すノードが作成されてそのノードへのポインタが返る.- TRIE_SET_SINGLE, TRIE_SET_MULTI のときは,
root->search(s)を実行すると, s を表すノードが存在してその reside の値が 1 以上であれば そのノードへのポインタが,そうでなければ nullptr が返る.
ルート以外のノード p に対しても実行でき,p が表す文字列に s を付加した文字列が対象となる.
親子関係
-
親を取るときには,データメンバ parent を参照する.
Trie* parent;ルートの parent は nullptr なので,「ルートまで順にたどる」のは,次のように書ける:
for ( ; p ; p = p->parent ) { .... } -
1つの子供ノードを取るときは,次を用いる:
Trie* get_child_val(int c); Trie* get_child_offset(int d);get_child_valは,cに,文字を指定する.get_child_offsetは,dにオフセットを指定する. これらは,c == from + dという関係にある(fromはテンプレートパラメタで,最初の文字を表す).該当の子供ノードが無いときには,nullptr が返る.
-
次の関数も同様であるが,該当の子供ノードが無いときには,ノードが作成されてそのノードへのポインタが返る.
Trie* get_or_create_child_val(int c); Trie* get_or_create_child_offset(int d); -
ノードが,自分の親に対して何番目の子供なのかを知るためには,次を用いる:
int get_offset();返される値 (int) はオフセットである.(ルート以外の場合) 自分が表す文字は
from + p->get_offset()で得られる.ルートに対して実行されると -1 が返る.
デバッグ用
- ノード p が表す文字列は,
p->repr()で得られる. - TRIE_SET_SINGLE, TRIE_SET_MULTI のときには,
Trie が表す集合の要素を (重複の回数だけならべて)
vector<S>に格納したものが,root->elem_list()で得られる. root->show(bool elemonly = true)では,各ノードの値 (表す文字列,reside, st_size, user) が表示された string が得られる.(User が monostate でないときには,g_show(User) が定義されていることが必要) TRIE_SET_SINGLE, TRIE_SET_MULTI モードで elemonly が true の場合には,reside の値が 1 以上のノードのみ, そうでなければすべてのノード情報が表示される.