トライ、またはロードされたツリー

こんにちは、Habrahabr。 今日私は、ロードされたツリープレフィックスツリーまたはトライ)の 辞書のような素晴らしいデータ構造について話したいです。



これは何ですか



ロードされたツリーは、連想配列インターフェースを実装するデータ構造です。つまり、キーと値のペアを保存できます。 ほとんどの場合、文字列はキーとして機能しますが、バイトシーケンスとして表現できるデータ型(つまり、any)はキーとして使用できることに注意してください。



どのように機能しますか?



ロードされたツリーは、ノードにキーが保存されていないという点で通常のn項ツリーと異なります。 代わりに、1文字のラベルがノードに保存され、特定のノードに対応するキーは、ツリーのルートからこのノードへのパス、またはこのパスで検出されたノードのラベルで構成される文字列です。 この場合、ツリーのルートは明らかに空白キーに対応します。



図では、キーc、cap、car、cdr、go、if、is、itを含むロードされたツリーの例を見ることができます。

典型的な積んだ木






車のキーが強調表示された同じツリー。

典型的な積んだ木






ツリー内のノードにはルートから一意のパスがあり、そのため何らかのキーがあるため、ツリーに「追加の」キーが含まれていることはすぐにわかります。 「余分な」キーの問題を回避するために、ブール特性がツリーの各ノードに追加され、ノードが実際に存在するか、他のノードへの道路に沿って中間であるかを示します。



基本操作



ロードされたツリーは連想配列のインターフェースを実装しているため、キーの挿入、削除、検索という3つの主要な操作を区別できます。 多くのツリーと同様に、ロードされたツリーには自己相似性のプロパティがあります。つまり、そのサブツリーのいずれもフルロードされたツリーです。 そのようなサブツリー内のすべてのキーには共通のプレフィックスがあることは簡単にわかります(「プレフィックスツリー」という名前の由来です)。これは、このツリーの特定の操作を区別できることを意味します-時間O(|プレフィックス|)



キー検索


既に述べたように、ノードに対応するキーは、ルートからこのノードへのパスに含まれるノードのラベルの連結です。 このプロパティは、当然ながらキー検索アルゴリズムを意味します(ちなみに、アルゴリズムの追加と削除)。



キーを指定します。キーは、ツリーで見つける必要があります。 ラベルが次のキーシンボルと一致するノードに移動するたびに、ツリーのルートから下位レベルに移動します。 すべてのキーシンボルが処理された後、降下が停止したノードが目的のノードになります。 降下中にキーの次の文字に対応するラベルを持つノードがなかった場合、または降下が中間の頂点(重要ではない頂点)で停止した場合、目的のキーはツリーにありません。



このアルゴリズムの時間の複雑さは明らかにO(| Key |)です。



このアルゴリズムは、ブロック線図でより詳細に示されています。



キー検索アルゴリズム








挿入


ツリーにキーを追加するアルゴリズムは、検索アルゴリズムに非常に似ています。



追加するキーと値のペアが与えられたとします。 キー検索アルゴリズムの場合と同様に、ツリーのルートから下位レベルに移動し、そのたびにラベルがキーの次の文字に一致するノードに移動します。 キーのすべてのシンボルが処理された後、下降が停止したノードは、値が割り当てられるノードになります(もちろん、ノードは値を持つものとしてマークされる必要があります)。 降下中にキーの次の文字に対応するラベルを持つノードがない場合、目的のラベルを持つ新しい中間ノードを作成し、現在のノードの子孫として指定する必要があります。



キーを追加する時間の複雑さはO(|キー|)です。



フローチャートのアルゴリズム図:



キー挿入アルゴリズム








削除する


キーの削除も非常に簡単です。



ツリーから削除する必要があるキーを指定します。 このキーを検索します。 キーがディクショナリに存在する場合、そのキーが対応するノードを知っているので、単に中間キーとしてマークし、以降の検索で「非表示」にすることができます。

その後、「切断された」ノードからルートに上昇し、リーフであるすべてのノードを同時に削除できますが、この場合のメモリ節約は重要ではありません。ノードがリーフであるかどうかを効果的に判断するには、追加のノード特性を導入する必要があります。



削除アルゴリズムの一時的な評価はすでにO(| Key |)に慣れています。



リソース要件



メモリ/ CPU時間消費の観点から読み込まれたツリーは、ハッシュテーブルとバランスツリーよりも劣らず、これらのパラメーターでそれらを上回ることがあります。



CPU時間


挿入、削除、および検索操作の複雑さはO(|キー|)です。 バランスの取れたツリーはこれらの操作をO(ln(n))で実行しますが、漸近的な動作により、キーを比較するのに必要な時間が隠されます。 ハッシュテーブルの場合、状況は似ています-アクセス時間がO(1 + a)であっても、ハッシュを取得するにはO(| Key |)が必要です(もちろん、事前に計算されていない場合)。



ロードされたツリー、ハッシュテーブル、赤黒ツリーのパフォーマンスを比較したチャートは、 英語版ウィキペディアで見ることができます。



記憶


メモリの消費において、ロードされたツリーは、多くの場合、ハッシュテーブルとバランスツリーよりも優れています。 これは、ロードされたツリーの多くのキーに同じプレフィックスがあり、それらのキーが使用するメモリがあるためです。 また、平衡型ツリーとは異なり、ロードされたツリーでは、各ノードにキーを保存する必要はありません。



最適化



ロードされたツリーの最適化には、主に2つのタイプがあります。



なぜこれがすべて必要なのですか?



実際には、ロードされたツリーのスコープは非常に大きく、連想配列インターフェースの実装が必要な場所であればどこでも使用できます。 特にロードされたツリーは、辞書、スペルチェッカー、およびその他のT9を実装するのに便利です。つまり、特定のプレフィックスを持つキーのセットをすばやく取得する必要があるタスクで便利です。 また、ロードされたツリーは、その作業でよく知られているAho-Korasikアルゴリズムを使用します。



それだけです。 読者がこの素晴らしいデータ構造について知りたいと思ったことを願っています。このデータ構造は、Habréで言及されることはめったにありません。



All Articles