web-dev-qa-db-ja.com

平易な英語でのUkkonenの接尾辞木アルゴリズム

この時点では少し太い気がします。私は頭に接尾辞ツリー構造を完全にラップすることを試みるのに何日も費やしました、しかし、私は数学的背景を持っていないので、それらが数学記号を過度に使い始めるので説明の多くは私を避けます。私が見つけた良い説明に最も近いのは接尾辞木を使った高速文字列検索ですが、彼はさまざまな点についてざっと目を通し、アルゴリズムのいくつかの側面は不明のままです。

ここでのスタックオーバーフローに関するこのアルゴリズムの段階的な説明は、私以外にも他の多くの人にとってかけがえのないものになるでしょう、と私は確信しています。

参考のために、ここでアルゴリズムに関するUkkonenの論文です: http://www.cs.helsinki.fi/u/ukkonen/SuffixT1withFigs.pdf

私の基本的な理解はこれまでのところ:

  • 与えられた文字列Tの各接頭辞Pを反復処理する必要があります。
  • 接頭辞Pの各接尾辞Sを繰り返して、それをツリーに追加する必要があります。
  • 接尾辞Sをツリーに追加するには、Sの各文字を反復処理する必要があります。反復は、Sの同じ文字セットCで始まる既存のブランチをたどり、次の場合にEdgeを子孫ノードに分割することからなります。接尾辞の異なる文字に到達します。一致するEdgeがない場合はORに進みます。 Cに一致するEdgeが見つからない場合は、Cに新しいリーフEdgeが作成されます。

基本的なアルゴリズムはO(n2ほとんどの説明で指摘されているように、すべての接頭辞をステップスルーする必要があるので、次に各接頭辞の接尾辞をステップスルーする必要があります。 Ukkonenのアルゴリズムは、彼が使っている接尾辞ポインタ技術のおかげで明らかにユニークですが、私はthatが理解できないものだと思います。

私も理解できない

  • 「アクティブポイント」がいつ、どのように割り当てられ、使用され、変更されるのか
  • アルゴリズムの正規化の側面で何が起こっているのか
  • 私がこれまで見てきた実装では、使用している境界変数を「修正」する必要があるのはなぜですか

完成した C# ソースコードです。これは正しく機能するだけでなく、自動正規化をサポートし、出力のテキストグラフをより見栄え良くレンダリングします。ソースコードと出力例は次の場所にあります。

https://Gist.github.com/2373868


更新2017-11-04

何年にもわたって接尾辞ツリーの新しい使い方を見つけ、 JavaScript にアルゴリズムを実装しました。要旨は以下です。それはバグがないはずです。同じ場所からjsファイル(npm install chalk)にダンプし、node.jsを実行してカラフルな出力を確認します。同じGistには、デバッグ用のコードを一切含まない、バージョンを簡略化したものがあります。

https://Gist.github.com/axefrog/c347bf0f5e0723cbd09b1aaed6ec6fc6

1033
Nathan Ridley

Jogojapanの回答に記載されているアプローチでサフィックスツリーを実装しようとしましたが、ルールに使用されている文言が原因で機能しない場合がありました。さらに、このアプローチを使用して絶対に正しい接尾辞ツリーを実装することはできなかったと述べました。以下に、ルールにいくつかの修正を加えたjogojapanの回答の「概要」を書きます。 important接尾辞リンクの作成を忘れた場合も説明します。

使用される追加の変数

  1. active point-トリプル(active_node; active_Edge; active_length)。新しいサフィックスの挿入を開始する場所を示します。
  2. 残り-追加する必要がある接尾辞の数を示します明示的に。たとえば、Wordが 'abcaabca'で、残り= 3の場合、最後の3つの接尾辞を処理する必要があることを意味します:bcacaおよびa

内部ノードの概念を使用してみましょう-rootおよびleafs内部ノードです。

観測1

挿入する必要のある最後のサフィックスがツリーに既に存在することが判明した場合、ツリー自体はまったく変更されません(active pointおよびremainderのみを更新します)。

観測2

ある時点でactive_lengthが現在のEdge(Edge_length)の長さ以上である場合、active pointEdge_lengthより厳密に大きくなるまでactive_lengthを下に移動します。

それでは、ルールを再定義しましょう。

ルール1

active node= rootからの挿入後、active lengthが0より大きい場合、次のようになります。

  1. アクティブノードは変更されません
  2. アクティブな長さは減少します
  3. アクティブエッジは右にシフトされます(次のサフィックスの最初の文字に挿入する必要があります)

ルール2

新しい内部ノードまたはを作成する場合、内部ノードからインサーターを作成し、これは最初のSUCHinternal node現在のステップで、前のSUCHノードとTHISサフィックスlinkを介してリンクします。

Rule 2のこの定義はjogojapan 'とは異なります。ここでは、新しく作成された内部ノードだけでなく、挿入する内部ノードも考慮に入れています。

ルール

active noderootノードではない)から挿入した後、接尾辞リンクをたどり、active nodeを指すノードに設定する必要があります。接尾辞がない場合リンク、アクティブノードルートノードに設定します。いずれにしても、アクティブエッジおよびアクティブ長は変更されません。

Rule 3のこの定義では、(スプリットノードだけでなく)リーフノードの挿入も考慮します。

そして最後に、観察3:

ツリーに追加するシンボルが既にEdgeにある場合、Observation 1に従って、active pointremainderのみを更新し、ツリーは変更しないままにします。 BUT内部ノードサフィックスリンクが必要とマークされている場合)は、サフィックスリンクを介してそのノードを現在のactive nodeに接続する必要があります。

このような場合に接尾辞リンクを追加し、追加しない場合は、cdddcdcの接尾辞ツリーの例を見てみましょう。

  1. DO N'Tがサフィックスリンクを介してノードを接続する場合:

    • 最後の文字cを追加する前:

    • 最後の文字cを追加した後:

  2. DOがサフィックスリンクを介してノードを接続する場合:

    • 最後の文字cを追加する前:

    • 最後の文字cを追加した後:

有意な違いはないようです:2番目の場合、さらに2つのサフィックスリンクがあります。しかし、これらの接尾辞リンクは正しいであり、そのうちの1つ-青いノードから赤いノードまで-は、active pointを使用したアプローチでは非常にimportantです。問題はここに接尾辞リンクを置かない場合、後で新しい文字をツリーに追加するときに、Rule 3のためにツリーにいくつかのノードを追加することを省略できます。 、その後、active_nodeをルートに配置する必要があります。

ツリーに最後の文字を追加すると、青いノードから挿入する前に、赤いノードに既に存在するがありました(エッジは'c'とラベル付けされています)。青いノードから挿入があったので、接尾辞linkが必要です)とマークします。その後、active pointアプローチに基づいて、active nodeが赤いノードに設定されました。 '' c 'がすでにEdgeにあるため、赤いノードから挿入しません。青いノードにはサフィックスリンクがないままにしておく必要があるということですか?いいえ、青いノードを接続する必要があります接尾辞リンクを介して赤いノードを持つノード。なぜ正しいのですか?active pointアプローチにより、正しい場所、つまり_の挿入を処理する必要がある次の場所に到達することが保証されるため短いサフィックス。

最後に、サフィックスツリーの実装を次に示します。

  1. Java
  2. C++

この「概要」とjogojapanの詳細な回答が、誰かが自分のサフィックスツリーを実装するのに役立つことを願っています。

126
makagonov

@jogojapanによるよく説明されたチュートリアルをありがとう、Pythonでアルゴリズムを実装しました。

@jogojapanで言及されたいくつかの小さな問題は、予想以上に洗練されたであり、非常に慎重に扱う必要があることが判明しました。私の実装を取得するのに数日かかりました十分な堅牢性(私は思う)。問題と解決策は次のとおりです。

  1. Remainder > 0で終わるこの状況は、アルゴリズム全体の終わりだけでなく、展開手順中でも発生する可能性があることがわかります。その場合、残り、actnode、actedge、およびactlength変更なしを残し、現在の展開ステップを終了し、元の文字列の次の文字がオンかどうかに応じて折り畳みまたは展開を続けることで別のステップを開始できます現在のパスかどうか。

  2. Leap Over Nodes:サフィックスリンクをたどると、アクティブポイントを更新し、そのactive_lengthコンポーネントが新しいactive_nodeでうまく機能しないことがわかります。 前に進む分割する適切な場所に移動するか、リーフを挿入する必要があります。このプロセスはそれほど単純ではありません移動中にactlengthとactedgeがずっと変化し続けるため、ルートノードactedgeおよびactlength間違っているである可能性があります。その情報を保持するには、追加の変数が必要です。

    enter image description here

他の2つの問題は、何らかの形で@managonovによって指摘されています。

  1. 分割は縮退する可能性がありますEdgeを分割しようとすると、ノード上で分割操作が正しく行われることがあります。その場合、そのノードに新しいリーフを追加するだけで、標準のエッジ分割操作として使用できます。つまり、サフィックスリンクがある場合は、それに応じて維持する必要があります。

  2. 隠しサフィックスリンクproblem 1およびproblem 2によって発生する別の特殊なケースがあります。時々、いくつかのノードを分割するために適切なポイントにホップする必要があります。残りの文字列とパスラベルを比較して移動する場合、surpass正しいポイントになる場合があります。その場合、接尾辞のリンクがあれば、意図せずに無視されます。これは、前進するときに正しい点を覚えておくによって回避できます。分割ノードが既に存在する場合、または展開手順中にproblem 1が発生する場合、接尾辞リンクを維持する必要があります。

最後に、Pythonの実装は次のとおりです。

ヒント:上記のコードにはナイーブツリー印刷関数が含まれていますが、これはデバッグ中に非常に重要です。それは私に多くの時間を節約し、特別なケースを見つけるのに便利です。

9
mutux

私の直感は次のとおりです。

メインループのk回の繰り返しの後、最初のk文字で始まる完全な文字列のすべての接尾辞を含む接尾辞ツリーを構築しました。

開始時には、これはサフィックスツリーが文字列全体を表す単一のルートノードを含むことを意味します(これが0から始まる唯一のサフィックスです)。

Len(文字列)を繰り返した後、すべての接尾辞を含む接尾辞ツリーができます。

ループの間、キーはアクティブポイントです。私の推測では、これは文字列の最初のk文字の固有の接尾辞に対応する接尾辞ツリーの最も深い位置を表しています。 (私は正しいとは、接尾辞が文字列全体にならないことを意味します。)

たとえば、 'abcabc'という文字を見たとします。アクティブポイントは、接尾辞 'abc'に対応するツリー内のポイントを表します。

アクティブポイントは(Origin、first、last)で表されます。つまり、あなたは現在、ノードOriginから始めてstring [first:last]の文字を入力することによって、あなたがたどり着くツリーの中にいるということです。

新しい文字を追加すると、アクティブポイントがまだ既存のツリー内にあるかどうかがわかります。それがそれならそれであなたは完了です。それ以外の場合は、アクティブポイントのサフィックスツリーに新しいノードを追加し、次に短い一致にフォールバックして、もう一度確認する必要があります。

注1:サフィックスポインタは、各ノードの次に短い一致へのリンクを示します。

注意2:新しいノードを追加してフォールバックすると、新しいノードに新しいサフィックスポインタが追加されます。この接尾辞ポインタの宛先は、短縮されたアクティブポイントのノードになります。このノードは既に存在するか、このフォールバックループの次の繰り返しで作成されます。

注3:正規化部分は単にアクティブポイントをチェックする時間を節約します。たとえば、常にOrigin = 0を使用し、最初と最後を変更したとします。アクティブポイントを確認するには、すべての中間ノードに沿って毎回サフィックスツリーをたどる必要があります。最後のノードからの距離だけを記録することによって、このパスをたどった結果をキャッシュすることは理にかなっています。

境界変数を「修正」することの意味のコード例を教えてください。

健康警告:私もこのアルゴリズムを理解するのが特に難しいと思ったので、この直感はすべての重要な詳細において不正確である可能性が高いことに注意してください。

6
Peter de Rivaz

@ jogojapanあなたは素晴らしい説明と視覚化をもたらしました。しかし@makagonovが述べたように、接尾辞リンクの設定に関するいくつかの規則が欠けています。 http://brenden.github.io/ukkonen-animation/ / Word 'aabaaabb'から順に進んでいくと、すてきに見えます。手順10から手順11に進むと、ノード5からノード2へのサフィックスリンクはありませんが、アクティブポイントはそこに突然移動します。

@makagonov私はJavaの世界に住んでいるので、STの構築ワークフローを把握するためにあなたの実装に従うことを試みましたが、それは私にとって困難でした:

  • エッジをノードと組み合わせる
  • 参照の代わりにインデックスポインタを使う
  • 文を中断します。
  • ステートメントを継続します。

それで、私はそのようなJavaでの実装に終わりました。それはすべてのステップをより明確に反映し、他のJavaユーザーの学習時間を短縮することを願っています。

import Java.util.Arrays;
import Java.util.HashMap;
import Java.util.Map;

public class ST {

  public class Node {
    private final int id;
    private final Map<Character, Edge> edges;
    private Node slink;

    public Node(final int id) {
        this.id = id;
        this.edges = new HashMap<>();
    }

    public void setSlink(final Node slink) {
        this.slink = slink;
    }

    public Map<Character, Edge> getEdges() {
        return this.edges;
    }

    public Node getSlink() {
        return this.slink;
    }

    public String toString(final String Word) {
        return new StringBuilder()
                .append("{")
                .append("\"id\"")
                .append(":")
                .append(this.id)
                .append(",")
                .append("\"slink\"")
                .append(":")
                .append(this.slink != null ? this.slink.id : null)
                .append(",")
                .append("\"edges\"")
                .append(":")
                .append(edgesToString(Word))
                .append("}")
                .toString();
    }

    private StringBuilder edgesToString(final String Word) {
        final StringBuilder edgesStringBuilder = new StringBuilder();
        edgesStringBuilder.append("{");
        for(final Map.Entry<Character, Edge> entry : this.edges.entrySet()) {
            edgesStringBuilder.append("\"")
                    .append(entry.getKey())
                    .append("\"")
                    .append(":")
                    .append(entry.getValue().toString(Word))
                    .append(",");
        }
        if(!this.edges.isEmpty()) {
            edgesStringBuilder.deleteCharAt(edgesStringBuilder.length() - 1);
        }
        edgesStringBuilder.append("}");
        return edgesStringBuilder;
    }

    public boolean contains(final String Word, final String suffix) {
        return !suffix.isEmpty()
                && this.edges.containsKey(suffix.charAt(0))
                && this.edges.get(suffix.charAt(0)).contains(Word, suffix);
    }
  }

  public class Edge {
    private final int from;
    private final int to;
    private final Node next;

    public Edge(final int from, final int to, final Node next) {
        this.from = from;
        this.to = to;
        this.next = next;
    }

    public int getFrom() {
        return this.from;
    }

    public int getTo() {
        return this.to;
    }

    public Node getNext() {
        return this.next;
    }

    public int getLength() {
        return this.to - this.from;
    }

    public String toString(final String Word) {
        return new StringBuilder()
                .append("{")
                .append("\"content\"")
                .append(":")
                .append("\"")
                .append(Word.substring(this.from, this.to))
                .append("\"")
                .append(",")
                .append("\"next\"")
                .append(":")
                .append(this.next != null ? this.next.toString(Word) : null)
                .append("}")
                .toString();
    }

    public boolean contains(final String Word, final String suffix) {
        if(this.next == null) {
            return Word.substring(this.from, this.to).equals(suffix);
        }
        return suffix.startsWith(Word.substring(this.from,
                this.to)) && this.next.contains(Word, suffix.substring(this.to - this.from));
    }
  }

  public class ActivePoint {
    private final Node activeNode;
    private final Character activeEdgeFirstCharacter;
    private final int activeLength;

    public ActivePoint(final Node activeNode,
                       final Character activeEdgeFirstCharacter,
                       final int activeLength) {
        this.activeNode = activeNode;
        this.activeEdgeFirstCharacter = activeEdgeFirstCharacter;
        this.activeLength = activeLength;
    }

    private Edge getActiveEdge() {
        return this.activeNode.getEdges().get(this.activeEdgeFirstCharacter);
    }

    public boolean pointsToActiveNode() {
        return this.activeLength == 0;
    }

    public boolean activeNodeIs(final Node node) {
        return this.activeNode == node;
    }

    public boolean activeNodeHasEdgeStartingWith(final char character) {
        return this.activeNode.getEdges().containsKey(character);
    }

    public boolean activeNodeHasSlink() {
        return this.activeNode.getSlink() != null;
    }

    public boolean pointsToOnActiveEdge(final String Word, final char character) {
        return Word.charAt(this.getActiveEdge().getFrom() + this.activeLength) == character;
    }

    public boolean pointsToTheEndOfActiveEdge() {
        return this.getActiveEdge().getLength() == this.activeLength;
    }

    public boolean pointsAfterTheEndOfActiveEdge() {
        return this.getActiveEdge().getLength() < this.activeLength;
    }

    public ActivePoint moveToEdgeStartingWithAndByOne(final char character) {
        return new ActivePoint(this.activeNode, character, 1);
    }

    public ActivePoint moveToNextNodeOfActiveEdge() {
        return new ActivePoint(this.getActiveEdge().getNext(), null, 0);
    }

    public ActivePoint moveToSlink() {
        return new ActivePoint(this.activeNode.getSlink(),
                this.activeEdgeFirstCharacter,
                this.activeLength);
    }

    public ActivePoint moveTo(final Node node) {
        return new ActivePoint(node, this.activeEdgeFirstCharacter, this.activeLength);
    }

    public ActivePoint moveByOneCharacter() {
        return new ActivePoint(this.activeNode,
                this.activeEdgeFirstCharacter,
                this.activeLength + 1);
    }

    public ActivePoint moveToEdgeStartingWithAndByActiveLengthMinusOne(final Node node,
                                                                       final char character) {
        return new ActivePoint(node, character, this.activeLength - 1);
    }

    public ActivePoint moveToNextNodeOfActiveEdge(final String Word, final int index) {
        return new ActivePoint(this.getActiveEdge().getNext(),
                Word.charAt(index - this.activeLength + this.getActiveEdge().getLength()),
                this.activeLength - this.getActiveEdge().getLength());
    }

    public void addEdgeToActiveNode(final char character, final Edge edge) {
        this.activeNode.getEdges().put(character, Edge);
    }

    public void splitActiveEdge(final String Word,
                                final Node nodeToAdd,
                                final int index,
                                final char character) {
        final Edge activeEdgeToSplit = this.getActiveEdge();
        final Edge splittedEdge = new Edge(activeEdgeToSplit.getFrom(),
                activeEdgeToSplit.getFrom() + this.activeLength,
                nodeToAdd);
        nodeToAdd.getEdges().put(Word.charAt(activeEdgeToSplit.getFrom() + this.activeLength),
                new Edge(activeEdgeToSplit.getFrom() + this.activeLength,
                        activeEdgeToSplit.getTo(),
                        activeEdgeToSplit.getNext()));
        nodeToAdd.getEdges().put(character, new Edge(index, Word.length(), null));
        this.activeNode.getEdges().put(this.activeEdgeFirstCharacter, splittedEdge);
    }

    public Node setSlinkTo(final Node previouslyAddedNodeOrAddedEdgeNode,
                           final Node node) {
        if(previouslyAddedNodeOrAddedEdgeNode != null) {
            previouslyAddedNodeOrAddedEdgeNode.setSlink(node);
        }
        return node;
    }

    public Node setSlinkToActiveNode(final Node previouslyAddedNodeOrAddedEdgeNode) {
        return setSlinkTo(previouslyAddedNodeOrAddedEdgeNode, this.activeNode);
    }
  }

  private static int idGenerator;

  private final String Word;
  private final Node root;
  private ActivePoint activePoint;
  private int remainder;

  public ST(final String Word) {
    this.Word = Word;
    this.root = new Node(idGenerator++);
    this.activePoint = new ActivePoint(this.root, null, 0);
    this.remainder = 0;
    build();
  }

  private void build() {
    for(int i = 0; i < this.Word.length(); i++) {
        add(i, this.Word.charAt(i));
    }
  }

  private void add(final int index, final char character) {
    this.remainder++;
    boolean characterFoundInTheTree = false;
    Node previouslyAddedNodeOrAddedEdgeNode = null;
    while(!characterFoundInTheTree && this.remainder > 0) {
        if(this.activePoint.pointsToActiveNode()) {
            if(this.activePoint.activeNodeHasEdgeStartingWith(character)) {
                activeNodeHasEdgeStartingWithCharacter(character, previouslyAddedNodeOrAddedEdgeNode);
                characterFoundInTheTree = true;
            }
            else {
                if(this.activePoint.activeNodeIs(this.root)) {
                    rootNodeHasNotEdgeStartingWithCharacter(index, character);
                }
                else {
                    previouslyAddedNodeOrAddedEdgeNode = internalNodeHasNotEdgeStartingWithCharacter(index,
                            character, previouslyAddedNodeOrAddedEdgeNode);
                }
            }
        }
        else {
            if(this.activePoint.pointsToOnActiveEdge(this.Word, character)) {
                activeEdgeHasCharacter();
                characterFoundInTheTree = true;
            }
            else {
                if(this.activePoint.activeNodeIs(this.root)) {
                    previouslyAddedNodeOrAddedEdgeNode = edgeFromRootNodeHasNotCharacter(index,
                            character,
                            previouslyAddedNodeOrAddedEdgeNode);
                }
                else {
                    previouslyAddedNodeOrAddedEdgeNode = edgeFromInternalNodeHasNotCharacter(index,
                            character,
                            previouslyAddedNodeOrAddedEdgeNode);
                }
            }
        }
    }
  }

  private void activeNodeHasEdgeStartingWithCharacter(final char character,
                                                    final Node previouslyAddedNodeOrAddedEdgeNode) {
    this.activePoint.setSlinkToActiveNode(previouslyAddedNodeOrAddedEdgeNode);
    this.activePoint = this.activePoint.moveToEdgeStartingWithAndByOne(character);
    if(this.activePoint.pointsToTheEndOfActiveEdge()) {
        this.activePoint = this.activePoint.moveToNextNodeOfActiveEdge();
    }
  }

  private void rootNodeHasNotEdgeStartingWithCharacter(final int index, final char character) {
    this.activePoint.addEdgeToActiveNode(character, new Edge(index, this.Word.length(), null));
    this.activePoint = this.activePoint.moveTo(this.root);
    this.remainder--;
    assert this.remainder == 0;
  }

  private Node internalNodeHasNotEdgeStartingWithCharacter(final int index,
                                                         final char character,
                                                         Node previouslyAddedNodeOrAddedEdgeNode) {
    this.activePoint.addEdgeToActiveNode(character, new Edge(index, this.Word.length(), null));
    previouslyAddedNodeOrAddedEdgeNode = this.activePoint.setSlinkToActiveNode(previouslyAddedNodeOrAddedEdgeNode);
    if(this.activePoint.activeNodeHasSlink()) {
        this.activePoint = this.activePoint.moveToSlink();
    }
    else {
        this.activePoint = this.activePoint.moveTo(this.root);
    }
    this.remainder--;
    return previouslyAddedNodeOrAddedEdgeNode;
  }

  private void activeEdgeHasCharacter() {
    this.activePoint = this.activePoint.moveByOneCharacter();
    if(this.activePoint.pointsToTheEndOfActiveEdge()) {
        this.activePoint = this.activePoint.moveToNextNodeOfActiveEdge();
    }
  }

  private Node edgeFromRootNodeHasNotCharacter(final int index,
                                             final char character,
                                             Node previouslyAddedNodeOrAddedEdgeNode) {
    final Node newNode = new Node(idGenerator++);
    this.activePoint.splitActiveEdge(this.Word, newNode, index, character);
    previouslyAddedNodeOrAddedEdgeNode = this.activePoint.setSlinkTo(previouslyAddedNodeOrAddedEdgeNode, newNode);
    this.activePoint = this.activePoint.moveToEdgeStartingWithAndByActiveLengthMinusOne(this.root,
            this.Word.charAt(index - this.remainder + 2));
    this.activePoint = walkDown(index);
    this.remainder--;
    return previouslyAddedNodeOrAddedEdgeNode;
  }

  private Node edgeFromInternalNodeHasNotCharacter(final int index,
                                                 final char character,
                                                 Node previouslyAddedNodeOrAddedEdgeNode) {
    final Node newNode = new Node(idGenerator++);
    this.activePoint.splitActiveEdge(this.Word, newNode, index, character);
    previouslyAddedNodeOrAddedEdgeNode = this.activePoint.setSlinkTo(previouslyAddedNodeOrAddedEdgeNode, newNode);
    if(this.activePoint.activeNodeHasSlink()) {
        this.activePoint = this.activePoint.moveToSlink();
    }
    else {
        this.activePoint = this.activePoint.moveTo(this.root);
    }
    this.activePoint = walkDown(index);
    this.remainder--;
    return previouslyAddedNodeOrAddedEdgeNode;
  }

  private ActivePoint walkDown(final int index) {
    while(!this.activePoint.pointsToActiveNode()
            && (this.activePoint.pointsToTheEndOfActiveEdge() || this.activePoint.pointsAfterTheEndOfActiveEdge())) {
        if(this.activePoint.pointsAfterTheEndOfActiveEdge()) {
            this.activePoint = this.activePoint.moveToNextNodeOfActiveEdge(this.Word, index);
        }
        else {
            this.activePoint = this.activePoint.moveToNextNodeOfActiveEdge();
        }
    }
    return this.activePoint;
  }

  public String toString(final String Word) {
    return this.root.toString(Word);
  }

  public boolean contains(final String suffix) {
    return this.root.contains(this.Word, suffix);
  }

  public static void main(final String[] args) {
    final String[] words = {
            "abcabcabc$",
            "abc$",
            "abcabxabcd$",
            "abcabxabda$",
            "abcabxad$",
            "aabaaabb$",
            "aababcabcd$",
            "ababcabcd$",
            "abccba$",
            "mississipi$",
            "abacabadabacabae$",
            "abcabcd$",
            "00132220$"
    };
    Arrays.stream(words).forEach(Word -> {
        System.out.println("Building suffix tree for Word: " + Word);
        final ST suffixTree = new ST(Word);
        System.out.println("Suffix tree: " + suffixTree.toString(Word));
        for(int i = 0; i < Word.length() - 1; i++) {
            assert suffixTree.contains(Word.substring(i)) : Word.substring(i);
        }
    });
  }
}
5
Kamil

こんにちは私はRubyで上記で説明された実装を実装しようとしました、それをチェックしてください。それはうまくいくようです。

実装の唯一の違いは、シンボルを使用する代わりにEdgeオブジェクトを使用しようとしたことです。

https://Gist.github.com/suchitpuri/9304856 にもあります。

    require 'pry'


class Edge
    attr_accessor :data , :edges , :suffix_link
    def initialize data
        @data = data
        @edges = []
        @suffix_link = nil
    end

    def find_Edge element
        self.edges.each do |Edge|
            return Edge if Edge.data.start_with? element
        end
        return nil
    end
end

class SuffixTrees
    attr_accessor :root , :active_point , :remainder , :pending_prefixes , :last_split_Edge , :remainder

    def initialize
        @root = Edge.new nil
        @active_point = { active_node: @root , active_Edge: nil , active_length: 0}
        @remainder = 0
        @pending_prefixes = []
        @last_split_Edge = nil
        @remainder = 1
    end

    def build string
        string.split("").each_with_index do |element , index|


            add_to_edges @root , element        

            update_pending_prefix element                           
            add_pending_elements_to_tree element
            active_length = @active_point[:active_length]

            # if(@active_point[:active_Edge] && @active_point[:active_Edge].data && @active_point[:active_Edge].data[0..active_length-1] ==  @active_point[:active_Edge].data[active_length..@active_point[:active_Edge].data.length-1])
            #   @active_point[:active_Edge].data = @active_point[:active_Edge].data[0..active_length-1]
            #   @active_point[:active_Edge].edges << Edge.new(@active_point[:active_Edge].data)
            # end

            if(@active_point[:active_Edge] && @active_point[:active_Edge].data && @active_point[:active_Edge].data.length == @active_point[:active_length]  )
                @active_point[:active_node] =  @active_point[:active_Edge]
                @active_point[:active_Edge] = @active_point[:active_node].find_Edge(element[0])
                @active_point[:active_length] = 0
            end
        end
    end

    def add_pending_elements_to_tree element

        to_be_deleted = []
        update_active_length = false
        # binding.pry
        if( @active_point[:active_node].find_Edge(element[0]) != nil)
            @active_point[:active_length] = @active_point[:active_length] + 1               
            @active_point[:active_Edge] = @active_point[:active_node].find_Edge(element[0]) if @active_point[:active_Edge] == nil
            @remainder = @remainder + 1
            return
        end



        @pending_prefixes.each_with_index do |pending_prefix , index|

            # binding.pry           

            if @active_point[:active_Edge] == nil and @active_point[:active_node].find_Edge(element[0]) == nil

                @active_point[:active_node].edges << Edge.new(element)

            else

                @active_point[:active_Edge] = node.find_Edge(element[0]) if @active_point[:active_Edge]  == nil

                data = @active_point[:active_Edge].data
                data = data.split("")               

                location = @active_point[:active_length]


                # binding.pry
                if(data[0..location].join == pending_prefix or @active_point[:active_node].find_Edge(element) != nil )                  


                else #tree split    
                    split_Edge data , index , element
                end

            end
        end 
    end



    def update_pending_prefix element
        if @active_point[:active_Edge] == nil
            @pending_prefixes = [element]
            return

        end

        @pending_prefixes = []

        length = @active_point[:active_Edge].data.length
        data = @active_point[:active_Edge].data
        @remainder.times do |ctr|
                @pending_prefixes << data[-(ctr+1)..data.length-1]
        end

        @pending_prefixes.reverse!

    end

    def split_Edge data , index , element
        location = @active_point[:active_length]
        old_edges = []
        internal_node = (@active_point[:active_Edge].edges != nil)

        if (internal_node)
            old_edges = @active_point[:active_Edge].edges 
            @active_point[:active_Edge].edges = []
        end

        @active_point[:active_Edge].data = data[0..location-1].join                 
        @active_point[:active_Edge].edges << Edge.new(data[location..data.size].join)


        if internal_node
            @active_point[:active_Edge].edges << Edge.new(element)
        else
            @active_point[:active_Edge].edges << Edge.new(data.last)        
        end

        if internal_node
            @active_point[:active_Edge].edges[0].edges = old_edges
        end


        #setup the suffix link
        if @last_split_Edge != nil and @last_split_Edge.data.end_with?@active_point[:active_Edge].data 

            @last_split_Edge.suffix_link = @active_point[:active_Edge] 
        end

        @last_split_Edge = @active_point[:active_Edge]

        update_active_point index

    end


    def update_active_point index
        if(@active_point[:active_node] == @root)
            @active_point[:active_length] = @active_point[:active_length] - 1
            @remainder = @remainder - 1
            @active_point[:active_Edge] = @active_point[:active_node].find_Edge(@pending_prefixes.first[index+1])
        else
            if @active_point[:active_node].suffix_link != nil
                @active_point[:active_node] = @active_point[:active_node].suffix_link               
            else
                @active_point[:active_node] = @root
            end 
            @active_point[:active_Edge] = @active_point[:active_node].find_Edge(@active_point[:active_Edge].data[0])
            @remainder = @remainder - 1     
        end
    end

    def add_to_edges root , element     
        return if root == nil
        root.data = root.data + element if(root.data and root.edges.size == 0)
        root.edges.each do |Edge|
            add_to_edges Edge , element
        end
    end
end

suffix_tree = SuffixTrees.new
suffix_tree.build("abcabxabcd")
binding.pry
3
Suchit Puri