📜  Ukkonen的后缀树构造–第1部分(1)

📅  最后修改于: 2023-12-03 15:35:27.376000             🧑  作者: Mango

Ukkonen的后缀树构造–第1部分

介绍

Ukkonen算法是一种用于构建后缀树的算法,它的时间复杂度是线性的,即 O(n)。它将每一个后缀插入后缀树中,在插入过程中,它会利用一些特殊技巧来避免显式地构造出字符串的所有后缀。这种技巧可以使得算法的时间复杂度来到线性。

在本篇文章中,我们将介绍Ukkonen算法的前半部分,也就是如何在O(n^2)的时间内构造每一个后缀的后缀树。

算法步骤

Ukkonen算法构造字符串S的后缀树的过程如下所示:

  1. 初始化一个根节点,标号为0。
  2. 从第一个字符开始遍历字符串S,从根节点开始形成第1个后缀的后缀树,节点编号为1开始。
    1. 从根节点开始匹配后缀的每个字符, 如果匹配成功,则转移到下一个节点继续匹配; 如果失败,则新建一个节点,将当前字符作为标签标记在新节点上,节点编号为当前节点编号+1,并将新节点与当前节点相连。
    2. 重复步骤 2.1,直到遍历完整个字符串的所有字符。
  3. 如果已经完成遍历,则后缀树构造完成。
代码实现

我们将用Python语言实现Ukkonen算法,实现代码如下:

class Node:
    def __init__(self, label="", id=0):
        self.label = label
        self.id = id
        self.children = {}

class SuffixTree:
    def __init__(self, string):
        self.string = string
        self.root = Node()
        self.active_node = self.root
        self.active_edge = ""
        self.active_length = 0
        self.leaf_end = -1
        self.remaining_suffix_count = 0
        self.split_end = 0
        self.last_parent_node = None

    def edge_length(self, node):
        if node == self.root:
            return 0
        return len(node.label)

    def walk_down(self, current_node):
        if self.active_length >= self.edge_length(current_node):
            self.active_edge = self.string[self.active_edge_index + self.edge_length(current_node)]
            self.active_length -= self.edge_length(current_node)
            self.active_node = current_node
            return True
        return False

    def extend_suffix_tree(self, pos):
        self.leaf_end = pos
        self.remaining_suffix_count += 1
        self.last_parent_node = None

        while self.remaining_suffix_count > 0:
            if self.active_length == 0:
                self.active_edge_index = pos

            if self.active_edge not in self.active_node.children:
                leaf_node = Node(label=self.string[pos:], id=len(self.active_node.children))
                self.active_node.children[self.active_edge] = leaf_node

                if self.last_parent_node is not None:
                    self.last_parent_node.suffix_link = self.active_node
                    self.last_parent_node = None
            else:
                next_node = self.active_node.children[self.active_edge]
                if self.walk_down(next_node):
                    continue
                if self.string[next_node.label[self.active_length]] == self.string[pos]:
                    self.active_length += 1
                    if self.last_parent_node is not None and self.active_node != self.root:
                        self.last_parent_node.suffix_link = self.active_node
                        self.last_parent_node = None
                    break

                self.split_end = self.active_edge_index + self.active_length - 1
                split_node = Node(label=next_node.label[:self.active_length], id=len(self.active_node.children))
                self.active_node.children[self.active_edge] = split_node

                leaf_node = Node(label=self.string[pos:], id=len(split_node.children))
                split_node.children[self.string[self.split_end]] = next_node
                split_node.children[self.string[pos]] = leaf_node
                if self.last_parent_node is not None:
                    self.last_parent_node.suffix_link = split_node

                self.last_parent_node = split_node

            self.remaining_suffix_count -= 1
            if self.active_node == self.root and self.active_length > 0:
                self.active_length -= 1
                self.active_edge_index = pos - self.remaining_suffix_count + 1
            else:
                self.active_node = self.active_node.suffix_link if self.active_node.suffix_link is not None else self.root
总结

以上就是基于Ukkonen算法构造后缀树的第1部分。通过以上代码实现,我们可以看到在O(n^2)的时间内构造一个字符串的后缀树。在下一篇文章中,我们将会介绍Ukkonen算法的第2部分。