トライツリー予備学習

トライツリーとは

トライツリーには多くの名前、辞書ツリー、プレフィックスツリーがあります。

トライツリーは、主に文字列または2進数を効率的に格納するために使用されます。これにより、重複する要素の格納を回避し、検索効率を向上させることができます。

私達例として26個の小文字を格納する
ルートノードから始まる26文字があるため、各親ノードには最大26個の子ノードがあります。

1.挿入操作

Trieツリーの作成はルートノードから始まります。文字列「in」を挿入するとします。

  1. 私たちは最初にルートにいます。これはノード0であり、これを使用してP=0示します。Pを見てください。子ノードに接続された文字iエッジのロゴはありますか。エッジが見つからなかったため、新しいノードを作成します。ノードは1番で、エッジは文字iとして識別されます。次に、ノード1であるLingに移動しますP=1このようにして、「in」i文字をTrieツリーに挿入します。

  2. 次に、文字nを挿入し、最初にPを見つけます。これは、ノード番号1でマークされていないものが文字n側であるかどうか、次にさらに新しいノード2であり、エッジは文字nとして識別されます。最後にP = 2に移動します。このようにして、nも挿入します。

  3. nは「in」の最後の文字であるため、ノードP = 2をエンドポイントとしてマークする必要もあります

他の文字列の挿入操作も同じです。子ノードがすでに存在する場合は、再度作成する必要はありません。それを利用する場合は、再度作成してください。

2.操作を見つける

文字列Sがトライツリーに含まれているかどうかを確認するにはどうすればよいですか?

ルートノードから開始し、S [0]-> S [1]-> S [2]-> S [3]…-> S [S.len]とマークされたエッジに沿って移動するだけです。

  1. 最終的にエンドポイントに正常に到達した場合、それはSがTrieツリーにあることを意味します。
  2. 終点に到達する方法がない場合、または終点ではないノードに到達した場合は、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;意味:

  1. xが0に等しい場合、ノード番号iのノードに拡張chaset[j]エッジがない、つまりchaset[j]子ノードに到達していないことを意味します
  2. 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;
}

おすすめ

転載: blog.csdn.net/HangHug_L/article/details/114178852