此时我感觉有点厚。我花了几天时间试图完全用后缀树构建我的头,但由于我没有数学背景,因为他们开始过度使用数学符号系统时,许多解释都没有。最接近我发现的一个很好的解释是 Fast String Searching With Suffix Trees ,但是他掩盖了各种观点,算法的某些方面仍然不清楚。
对于除了我之外的许多其他人来说,在Stack Overflow上对此算法的逐步解释将是非常宝贵的,我敢肯定。
供参考,这是Ukkonen关于算法的论文:http://www.cs.helsinki.fi/u/ukkonen/SuffixT1withFigs.pdf
我的基本理解,到目前为止:
基本算法似乎是O(n 2 ),正如我们在大多数解释中所指出的那样,因为我们需要遍历所有前缀,然后我们需要逐步执行每个前缀每个前缀的后缀。由于他使用了后缀指针技术,Ukkonen的算法显然是独一无二的,尽管我认为 是我无法理解的。
我也无法理解:
以下是已完成的 C#源代码。它不仅工作正常,而且支持自动规范化,并提供更好看的输出文本图表。源代码和示例输出位于:
更新2017-11-04
多年以后,我发现了后缀树的新用途,并在 JavaScript 中实现了该算法。要点如下。它应该没有错误。从同一位置将其转储到js文件npm install chalk
中,然后使用node.js运行以查看一些彩色输出。在同一个Gist中有一个精简版本,没有任何调试代码。
https://gist.github.com/axefrog/c347bf0f5e0723cbd09b1aaed6ec6fc6
答案 0 :(得分:122)
我尝试使用jogojapan的答案中给出的方法实现后缀树,但由于规则使用的措辞,它在某些情况下没有用。此外,我已经提到没有人设法使用这种方法实现一个绝对正确的后缀树。下面我将写一个"概述" jogojapan的答案,对规则进行了一些修改。我还将描述忘记创建 重要 后缀链接的情况。
使用其他变量
让我们使用内部节点的概念 - 除了 root 和 leafs 之外的所有节点都是内部节点。
观察1
当我们需要插入的最终后缀已经存在于树中时,树本身根本没有改变(我们只更新active point
和remainder
)。
观察2
如果在某个时刻active_length
大于或等于当前边缘的长度(edge_length
),我们会将active point
向下移动,直到edge_length
严格大于active_length
{1}}。
现在,让我们重新定义规则:
规则1
如果从活动节点 = root 插入后,活动长度大于0,则:
- 活动节点未更改
- 有效长度递减
- 活动边缘向右移动(到我们必须插入的下一个后缀的第一个字符)
醇>
规则2
如果我们创建新的内部节点 或从内部节点创建插件,这不是第一个 SUCH 内部节点在当前步骤,然后我们通过后缀链接将 SUCH 节点与 THIS 连接起来 EM>
Rule 2
的定义与jogojapan不同,因为我们不仅考虑新创建的内部节点,还考虑内部节点,我们从插入。
规则3
从活动节点(不是 root 节点)插入后,我们必须遵循后缀链接并将活动节点设置为它指向的节点。如果没有后缀链接,请将活动节点设置为 root 节点。无论哪种方式,活动边和活动长度保持不变。
在Rule 3
的定义中,我们还考虑了叶节点的插入(不仅是分裂节点)。
最后,观察3:
当我们想要添加到树的符号已经位于边缘时,我们根据Observation 1
仅更新active point
和remainder
,保持树不变。 但是如果内部节点标记为需要后缀链接,我们必须通过后缀将该节点与我们当前的active node
连接起来链接。
如果我们在这种情况下添加后缀链接,并且如果我们不这样做,那么让我们看一下 cdddcdc 的后缀树示例。
如果我们 DON' 通过后缀链接连接节点:
如果我们 DO 通过后缀链接连接节点:
似乎没有显着差异:在第二种情况下,还有两个后缀链接。但是这些后缀链接正确,其中一个 - 从蓝色节点到红色节点 - 非常重要用于活动点。问题是,如果我们不在此处添加后缀链接,稍后,当我们向树中添加一些新字母时,由于Rule 3
,我们可能会省略向树中添加一些节点,因为根据对它来说,如果没有后缀链接,那么我们必须将active_node
放到根目录。
当我们将最后一个字母添加到树中时,红色节点已经已经存在,然后我们从蓝色节点进行插入(边缘标记' c&#39 ; 强>)。由于蓝色节点有插入,我们将其标记为需要后缀链接。然后,依靠活动点方法,active node
被设置为红色节点。但是我们不能从红色节点插入插入内容,因为字母' 已经位于边缘。这是否意味着蓝色节点必须没有后缀链接?不,我们必须通过后缀链接将蓝色节点与红色节点连接起来。为什么这是正确的?因为有效点方法可以保证我们到达正确的位置,即下一个我们必须处理较短后缀的插入位置。
最后,这是我对后缀树的实现:
答案 1 :(得分:9)
感谢 @jogojapan 的精心解释的教程,我在Python中实现了算法。
@jogojapan提到的一些小问题比我预期的要强得多复杂,需要非常小心对待。我花了几天的时间来实现足够强大(我想)。问题和解决方案如下:
以Remainder > 0
结束事实证明,这种情况也可能在展开步骤中发生,而不仅仅是整个算法的结束。当发生这种情况时,我们可以保留余数,actnode,actedge和actlength 不变,结束当前的展开步骤,并通过保持折叠或展开来开始另一个步骤,具体取决于原始中的下一个字符。 string是否在当前路径上。
跳过节点:当我们按照后缀链接时,更新活动点,然后发现其active_length组件与新的active_node不兼容。我们必须前进到正确的位置进行拆分或插入叶子。这个过程可能不那么简单因为在移动期间actlength和actted一直在变化,当你必须回到根节点时,由于这些举动,actedge 和 actlength 可能错误。我们需要额外的变量来保存这些信息。
@managonov
以某种方式指出了另外两个问题拆分可能会退化尝试拆分边缘时,有时您会发现拆分操作在节点上是正确的。在这种情况下,我们只需要为该节点添加一个新的叶子,将其作为标准的边缘分割操作,这意味着如果有任何后缀链接,则应相应地进行维护。
隐藏的后缀链接 问题1 和问题2 会产生另一种特殊情况。有时我们需要跳过几个节点到正确的点进行拆分,如果我们通过比较余数字符串和路径标签来移动,我们可能超过正确的点。那种情况下,后缀链接将被无意中忽略,如果有的话。在向前移动时,记住正确的点可以避免这种情况。如果拆分节点已存在,或者在展开步骤中发生问题1 ,则应保留后缀链接。
最后,我在 Python 中的实现如下:
提示: 它在上面的代码中包含一个天真的树打印函数,这在调试时非常重要。它为我节省了很多 时间,便于查找特殊情况。
答案 2 :(得分:6)
我的直觉如下:
在主循环的k次迭代之后,您构建了一个后缀树,其中包含以前k个字符开头的完整字符串的所有后缀。
在开始时,这意味着后缀树包含一个表示整个字符串的根节点(这是从0开始的唯一后缀)。
在len(字符串)迭代之后,你有一个包含所有后缀的后缀树。
在循环期间,键是活动点。我的猜测是,这代表后缀树中最深的一点,它对应于字符串前k个字符的正确后缀。 (我认为正确意味着后缀不能是整个字符串。)
例如,假设您已经看过字符'abcabc'。活动点将表示树中与后缀'abc'对应的点。
活动点由(origin,first,last)表示。 这意味着您当前位于树中的点,从节点原点开始,然后以字符串[first:last] <[p]中的字符输入
添加新角色时,您会查看活动点是否仍在现有树中。如果是,那么你就完成了。 否则,您需要在活动点的后缀树中添加一个新节点,回退到下一个最短匹配,然后再次检查。
注1: 后缀指针为每个节点提供了下一个最短匹配的链接。
注2: 添加新节点和回退时,为新节点添加新的后缀指针。 此后缀指针的目标将是缩短活动点的节点。 此节点将已存在,或者在此回退循环的下一次迭代中创建。
注3:典型化部分只是节省了检查活动点的时间。 例如,假设您总是使用origin = 0,并且只是首先和最后一次更改。 要检查活动点,每次沿着所有中间节点都必须遵循后缀树。 通过仅记录距离最后一个节点的距离来缓存跟踪此路径的结果是有意义的。
你能举出“修复”边界变量的代码示例吗?
健康警告:我也发现这个算法特别难以理解,所以请认识到这种直觉可能在所有重要细节中都是错误的......
答案 3 :(得分:5)
@jogojapan you brought awesome explanation and visualisation. But as @makagonov mentioned it's missing some rules regarding setting suffix links. It's visible in nice way when going step by step on http://brenden.github.io/ukkonen-animation/通过单词&#39; aabaaabb&#39;传递不同的$ _SESSION变量。当您从步骤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);
}
});
}
}
答案 4 :(得分:3)
您好我已尝试在ruby中实现上述解释,请查看。 它似乎工作正常。
实现的唯一区别是,我试图使用边缘对象而不是仅仅使用符号。
它也出现在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