トライツリーとは
トライツリーには多くの名前、辞書ツリー、プレフィックスツリーがあります。
トライツリーは、主に文字列または2進数を効率的に格納するために使用されます。これにより、重複する要素の格納を回避し、検索効率を向上させることができます。
私達例として26個の小文字を格納する:
ルートノードから始まる26文字があるため、各親ノードには最大26個の子ノードがあります。
1.挿入操作
Trieツリーの作成はルートノードから始まります。文字列「in」を挿入するとします。
-
私たちは最初にルートにいます。これはノード0であり、これを使用して
P=0
示します。Pを見てください。子ノードに接続された文字iエッジのロゴはありますか。エッジが見つからなかったため、新しいノードを作成します。ノードは1番で、エッジは文字iとして識別されます。次に、ノード1であるLingに移動しますP=1
。このようにして、「in」i文字をTrieツリーに挿入します。 -
次に、文字nを挿入し、最初にPを見つけます。これは、ノード番号1でマークされていないものが文字n側であるかどうか、次にさらに新しいノード2であり、エッジは文字nとして識別されます。最後にP = 2に移動します。このようにして、nも挿入します。
-
nは「in」の最後の文字であるため、ノードP = 2をエンドポイントとしてマークする必要もあります。
他の文字列の挿入操作も同じです。子ノードがすでに存在する場合は、再度作成する必要はありません。それを利用する場合は、再度作成してください。
2.操作を見つける
文字列Sがトライツリーに含まれているかどうかを確認するにはどうすればよいですか?
ルートノードから開始し、S [0]-> S [1]-> S [2]-> S [3]…-> S [S.len]とマークされたエッジに沿って移動するだけです。
- 最終的にエンドポイントに正常に到達した場合、それはSがTrieツリーにあることを意味します。
- 終点に到達する方法がない場合、または終点ではないノードに到達した場合は、SがTrieツリーにないことを意味します。
3.2次元配列シミュレーショントライツリー
3.1ストレージ機能の実現
int trie[M][26];
26個の小文字を格納するトライツリーを例に
とると、Mの値は、すべての入力文字列の文字数の合計に依存します。最悪の場合、各文字にノードが確立される可能性が
あります。ほとんどの26のブランチ。
2次元配列を使用して、Trieツリーをシミュレートします。
1. trie[i]
2次元配列のi番目の行を表し、各行の番号iは、上の図のTrieツリーのノード番号pの意味に対応します。i==p
ルートノードの26個の子ノードのノード番号が1から26でない可能性があることに注意してください。これは、この文字が表示される順序に関連しています。2次元配列の233行目で、ルートノードの3番目のブランチであるノード番号が233で
ある可能性trie[0][3]=233;
があります。trie[233]
これは、変数の追加int idx=1;
メンテナンスを定義する必要があります。No。0ノードはルートノードであり、2つの方法はありません。毎回、ルート検索、挿入、つまりidxを最初から直接開始します。異なる文字を格納する場合、最初に表示される文字は、最初に伸びるエッジに格納されます。
idxの役割は、単一リンクリストでのidxの役割と同等です。
2.各行には26の列があります。つまり、26のブランチがあります。ここでは、azからのみ、つまり0から25までです。
charset[i]=ch;
iは0から25で、小文字のaからzに対応します。charset[i]=i+'a';
三、trie[i][j]=x;
意味:
- xが0に等しい場合、ノード番号iのノードに拡張
chaset[j]
エッジがない、つまりchaset[j]
子ノードに到達していないことを意味します。 - xが0に等しくない場合、ノード番号iのノードには突出した
chaset[j]
エッジがある、つまりchaset[j]
子ノードがあり、ノード番号の添え字がxである場合はx> iである必要があることを意味します。
では、文字はどの程度正確に格納されますか?
trie[0]
これはルートノードです。ノード番号iは0です。2次元配列全体に格納されている値の意味は、その次のノードの番号です。
trir[i][j]=x
値を格納するこのようなプロセスは、ノードiからノードxへのプロセスを意味します。一の延びるcharset[j]
側面を、すなわち文字格納されcharset[j]
、それはノード間のエッジでは、格納されているノード自体を介して、しません。
3.2マーク終了機能の実現
上の図のティーパスを例として取り上げます。図のノードのみが終了タグをマークし、すべてに文字列「tea」のみが含まれ、文字列「te」は含まれません。
パスint、 nノードを確認してください。とtノード。どちらにも終了タグがあるため、文字列「in」と「int」はトライツリーに格納されます。
int cnt[M];
cnt配列を開いて、Trieツリーの各ノードの数、つまり2次元配列の各行を維持します。
cnt[i]=x;
の意味は次のとおりです。ノード番号iで終わる文字列の数はxですが、ノード番号iのノードを指すことができるパスは1つだけなので、特定の文字列の出現回数がカウントされます。
タイトル説明
文字列のコレクションを維持するには、次の2つの操作がサポートされます。
「Ix」は文字列xをセットに挿入し、「Qx」は文字列がセットに
何回出現するかを尋ねます。
N個の操作があります。全長入力文字列は10超えない5、文字列には小文字の英字のみが含まれます。
入力形式
最初の行には、オペランドを表す整数Nが含まれています。
次のN行、各行には操作命令が含まれています。命令は「Ix」または「Qx」のいずれかです。
出力形式
クエリコマンド「Qx」ごとに、結果として整数を出力する必要があります。これは、xがセットに出現する回数を表します。
各結果は1行を占めます。
データ範囲
1≤N≤2∗ 10 4
入力例:
5
I abc
Q abc
Q ab
I ab
Q ab
サンプル出力:
1
0
1
アルゴリズムの実装
#include <iostream>
#define read(x) scanf("%d",&x)
using namespace std;
const int M=1e5+10;
int trie[M][26],cnt[M],idx=1; "0号结点是根节点,有效的字符存储是从下标1开始的,i->j的边才表示一个字符:0->1"
"0->1的边每次存储哪个字符是不确定的,i->j同理"
void insert(char *str)
{
int p=0; //从根节点开始插入,p表示节点编号
for (int i=0;str[i]!='\0';i++) {
int t=str[i]-'a'; //指向具体哪个分支
if (trie[p][t]==0) trie[p][t]=idx++; //如果分支不存在的话,开辟一个新结点
p=trie[p][t]; //指向该分支存储在的下一个结点,形成了边,到这一步时才说明插入字符str[i]了
}
cnt[p]++; //统计字符串个数
}
int find(char *str)
{
int p=0; //从根节点开始查找,p表示节点编号
for (int i=0;str[i]!='\0';i++) {
int t=str[i]-'a';
if (!trie[p][t]) return 0; //顶点p->triep[p][t]不可达,说明不存在边str[i]了,即没有存储字符str[i]
p=trie[p][t];
}
return cnt[p];
}
int main()
{
int n;
read(n);
char op[3],str[M];
while (n--) {
scanf("%s%s",op+1,str); "尝试从下标1开始输入字符串,注意'\0'也要占位置"
if(op[1]=='I') insert(str);
else printf("%d\n",find(str));
}
return 0;
}