📅  最后修改于: 2023-12-03 15:20:48.407000             🧑  作者: Mango
本文介绍的是Ukkonen算法的第4部分,讲解了如何在$O(n)$时间内构建后缀树。
我们需要按照下面的方式进行修改:
为了实现这些修改,Ukkonen提出了特殊类型的节点,称为Active Point(活动点)和Remaining Suffix(剩余后缀)。
一个活动点由三个参数组成:
剩余后缀是指色来脚节点与当前位置之间的字符串。
下面是构建后缀树的算法:
为了优化算法的速度,Ukkonen提出了常数时间内维护活动节点和剩余后缀的方法。这样,构建整个后缀树只需要$O(n)$的时间。
代码实现如下:
# 定义节点类
class Node:
def __init__(self, start, end):
self.children = {}
self.start = start
self.end = end
self.link = None
def add_child(self, key, value):
self.children[key] = value
def has_child(self, key):
return key in self.children
def get_child(self, key):
return self.children[key]
# 构建后缀树
def build_suffix_tree(string):
# 添加初始后缀
string += "$"
# 初始化根节点
root = Node(-1, -1)
# 初始化活动点
active_node = root
active_edge = ""
active_length = 0
# 初始化剩余后缀
remainder = 0
# 循环每个字符
for i in range(len(string)):
# 初始化当前字符
current_char = string[i]
# 将剩余后缀与当前字符进行匹配,并移动活动点
last_node = None
remainder += 1
while remainder > 0:
if active_length == 0:
active_edge = current_char
if not active_node.has_child(active_edge):
# 如果当前节点没有该子节点,则创建一个新的叶子节点
new_leaf = Node(i, len(string)-1)
active_node.add_child(active_edge, new_leaf)
# 创建后缀链接
if last_node is not None:
last_node.link = active_node
last_node = active_node
else:
# 获取该子节点
next_node = active_node.get_child(active_edge)
# 如果该子节点的前缀与当前字符匹配,则增加活动长度
if match(next_node, string, i - remainder + 1, i):
active_length += 1
# 创建后缀链接
if last_node is not None:
last_node.link = active_node
last_node = active_node
# 结束循环
break
else:
# 如果该子节点的前缀与当前字符不匹配,则将活动点移动到该子节点
split_end = next_node.start + active_length - 1
split_child = Node(next_node.start, split_end)
active_node.children[active_edge] = split_child
split_leaf = Node(i, len(string)-1)
split_child.add_child(current_char, split_leaf)
next_node.start += active_length
split_child.add_child(string[next_node.start], next_node)
# 创建后缀链接
if last_node is not None:
last_node.link = split_child
last_node = split_child
remainder -= 1
if active_node == root and active_length > 0:
active_length -= 1
active_edge = string[i-remainder+1]
else:
active_node = active_node.link if active_node.link is not None else root
# 将当前字符作为下一个活动点的起始节点
if active_node == root:
active_edge = string[i+1]
else:
active_edge = string[active_node.start + active_length]
# 初始化下一个活动点
active_length = 0
active_node = active_node if active_node != root else active_node.get_child(string[i+1])
# 处理未分配的后缀链接
if last_node is not None:
last_node.link = active_node
return root
# 辅助函数
def match(node, string, start, end):
return (node.end - node.start) >= (end - start) and string[start:start + node.end - node.start + 1] == string[node.start:node.end + 1]
# 测试用例
root = build_suffix_tree("mississippi")
assert root.has_child("m")
assert root.has_child("i")
assert root.has_child("s")
assert not root.has_child("p")
assert root.get_child("i").has_child("s")
assert root.get_child("i").get_child("s").get_child("sippi").end == 9