web-dev-qa-db-ja.com

トライとサフィックスツリーとサフィックス配列

どの構造が最高のパフォーマンス結果を提供しますか。トライ(接頭辞ツリー)、接尾辞ツリー、または接尾辞配列?他に同様の構造はありますか? Javaこれらの構造の実装とは何ですか?

編集:この場合、テキストで辞書の名前を識別するために、名前の大きな辞書と自然言語テキストの大きなセットの間で文字列照合を行います。

40
David Campos

トライは、この種のデータ構造として最初に発見されたものです。

サフィックスツリーは、トライよりも改良されています(線形エラー検索を可能にするサフィックスリンクがあり、サフィックスツリーはトライの不要なブランチをトリミングするため、それほど多くのスペースを必要としません)。

接尾辞配列は、接尾辞ツリーに基づいて取り除かれたデータ構造です(接尾辞リンクなし(遅いエラー一致)、それでもパターン一致は非常に高速です)。

トライはスペースを使いすぎるため、実世界での使用には適していません。

サフィックスツリーは、トライよりも軽くて高速で、DNAのインデックス付けや一部の大規模Web検索エンジンの最適化に使用されます。

一部のパターン検索では、suffix配列は、suffixツリーよりも低速ですが、使用するスペースが少なく、Suffixツリーよりも広く使用されています。

同じデータ構造のファミリー:

他の実装もあります。CSTは、サフィックス配列といくつかの追加のデータ構造を使用したサフィックスツリーの実装であり、サフィックスツリー検索機能の一部を取得します。

FCSTはさらにそれを採用し、サフィックス配列を使用してサンプルサフィックスツリーを実装します。

DFCSTは、FCSTの動的バージョンです。

拡大する:

2つの重要な要素は、スペースの使用と操作の実行時間です。現代のマシンではこれは重要ではないと思うかもしれませんが、一人の人間のDNAにインデックスを付けるには40ギガバイトのメモリが必要です(非圧縮で最適化されていないサフィックスツリーを使用)。そして、この多くのデータに対してこのインデックスの1つを構築するには、数日かかることがあります。 Googleを想像してみてください。検索可能なデータがたくさんあり、すべてのWebデータに対して大きなインデックスが必要であり、誰かがWebページを作成するたびにインデックスを変更するわけではありません。彼らはそのために何らかの形のキャッシングを持っています。ただし、メインインデックスはおそらく静的です。また、数週間ごとに、新しいWebサイトとデータをすべて収集し、新しいインデックスを作成して、新しいものが完成したら古いものを置き換えます。インデックスに使用するアルゴリズムはわかりませんが、パーティション化されたデータベースのサフィックスツリープロパティを持つサフィックス配列である可能性があります。

CSTは8ギガバイトを使用しますが、サフィックスツリーの操作速度は大幅に低下します。

サフィックス配列は、約700メガから2ギガまで同じことができます。ただし、サフィックスアレイを持つDNAの遺伝的エラーは見つかりません(つまり、ワイルドカードを使用したパターンの検索ははるかに遅くなります)。

FCST(完全に圧縮されたサフィックスツリー)は、800〜1.5ギガのサフィックスツリーを作成できます。 CSTに向かって速度の低下がやや小さい。

DFCSTはFCSTよりも20%多くのスペースを使用し、FCSTの静的実装では速度を失います(ただし、動的インデックスは非常に重要です)。

データ構造RAMスペースコストを操作の速度向上で補うことは非常に難しいため、サフィックスツリーの実行可能な(スペースに関して)実装は多くありません。

つまり、サフィックスツリーには、エラーのあるパターンマッチングに関する非常に興味深い検索結果があります。 aho corasickはそれほど高速ではありませんが(一部の操作ではほぼ同じ速度で、エラーマッチングではありません)、ボイヤームーアはほこりの中に残されています。

57

どのような作業を行う予定ですか? libdivsufsort は、かつてCで最も優れたサフィックス配列の実装でした。

4
Chad Brewbaker

接尾辞ツリーを使用すると、O(n + m + k)時間で辞書をテキストに一致させる何かを書くことができます。ここで、nは辞書の文字、mはテキストの文字、kは一致の数です。このため、試行はかなり遅くなります。サフィックス配列とは何かわからないので、コメントはできません。

とは言っても、コードを書くのは簡単なことではなく、必要な機能を提供するJavaライブラリを知っていることはありません。

2
swestrup

私はサフィックスオートマシンを好みます。あなたは私のウェブサイトを通して詳細を見つけることができます: http://www.fogsail.net/2019/03/06/20190306/

ここに画像の説明を入力

最初に、通常の構成を使用した場合、すべてのサフィックスを移動するにはO(n ^ 2)が必要です

基数ソートを使用して、サフィックス配列を最初の文字でソートします。

しかし、最初の文字をソートすると、その情報を使用できます。

詳細は画像で示されています(中国語は無視)

最初のキーで配列をソートします。結果は赤い長方形で表示されます

????????????????????????????????、???????? ????????????????????、......-- >> ????????????????????? ??????????、????????????????????????????、....

ここに画像の説明を入力

#include <iostream>
#include <cstdio>
#include <vector>
#include <queue>
#include <cstring>
#include <algorithm>

using namespace std;
const int maxn = 1001 * 100 + 10;

struct suffixArray {
    int str[maxn], sa[maxn], rank[maxn], lcp[maxn];
    int c[maxn], t1[maxn], t2[maxn];
    int n;

    void init() { n = 0; memset(sa, 0, sizeof(sa)); }

    void buildSa(int Rdx) {
        int i, *x = t1, *y = t2;
        for(i = 0; i < Rdx; i++) c[i] = 0;
        for(i = 0; i < n; i++) c[x[i] = str[i]]++;
        for(i = 1; i < Rdx; i++) c[i] += c[i-1];
        for(i = n-1; i >= 0; i--) sa[--c[x[i]]] = i;

        for(int offset = 1; offset <= n; offset <<= 1) {
            int p = 0;
            for(i = n-offset; i < n; i++) y[p++] = i;
            for(i = 0; i < n; i++) if(sa[i] >= offset) y[p++] = sa[i] - offset;

            // radix sort
            for(i = 0; i < Rdx; i++) c[i] = 0;
            for(i = 0; i < n; i++) c[x[y[i]]]++;
            for(i = 1; i < Rdx; i++) c[i] += c[i-1];
            for(i = n-1; i >= 0; i--) { sa[--c[x[y[i]]]] = y[i]; y[i] = 0; }

            // rebuild x and y
            swap(x, y); x[sa[0]] = 0; p = 1;
            for(i = 1; i < n; i++)
                x[sa[i]] = y[sa[i-1]] == y[sa[i]] && y[sa[i-1]+offset] == y[sa[i]+offset] ? p-1 : p++;
            if(p >= n) break;
            Rdx = p;
        }
    }
1
Fogsail Chen

トライvsサフィックスツリー

どちらのデータ構造も非常に高速なルックアップを保証します。検索時間はクエリWordの長さに比例し、複雑度時間O(m)ここで、mはクエリWordの長さです。

つまり、10文字のクエリWordがある場合、それを見つけるには最大で10の手順が必要です。

Trie :文字列を格納するためのツリーで、すべての共通の接頭辞に対して1つのノードがあります。文字列は追加のリーフノードに格納されます。

suffix tree :指定された文字列のサフィックスに対応するトライのコンパクトな表現で、1つの子を持つすべてのノードがその親とマージされます。

defはfrom:アルゴリズムとデータ構造の辞書

通常、辞書の単語(Lexicon)または文字列のセットのインデックス付けに使用されるTrieの例D = {abcd、abcdd、bxcdf、.....、zzzz}

テキストのすべてのサフィックスで同じデータ構造「Trie」を使用してテキストにインデックスを付けるために使用されるサフィックスツリーT = abcdabcg Tのすべてのサフィックス= {abcdabcg、abcdabc、abcdab、abcda、abcd、abc、ab、a}

今では文字列のグループのように見えます。この文字列のグループ(Tのすべてのサフィックス)の上にTrieを構築します。

両方のデータ構造の構築は線形であり、時間と空間でO(n))がかかります。

辞書(文字列のセット)の場合:n =すべての単語の文字の合計。テキスト内:n =テキストの長さ。

suffix array:圧縮されたsapceでサフィックスツリーを表すテクニックです。これは、文字列のサフィックスのすべての開始位置の配列です。

検索時間は、接尾辞ツリーよりも遅くなります。

詳細については、ウィキペディアにアクセスしてください。このトピックについて話している良い記事があります。

1
ibra

編集:この場合、テキスト上の辞書の名前を識別するために、名前の大きな辞書と自然言語のテキストの大きなセットの間で文字列照合を行いたいと思います。

これは Aho-Corasickアルゴリズム のアプリケーションのように聞こえます:(線形時間で)辞書からオートマトンを作成します。これを使用して、複数のテキスト内の任意の辞書単語の出現をすべて検索できます。 (これも線形時間で)。

(ウィキペディアのページの「外部リンク」セクションからリンクされている これらの講義ノート の説明は、ページ自体の説明よりもはるかに読みやすくなっています。)

1

This 誘導ソートアルゴリズム(saisと呼ばれます)の実装には、Javaサフィックス配列を構築するためのバージョンがあります。

0
hexcoder