Construcción del árbol de sufijos de Ukkonen – Parte 6

Este artículo es la continuación de los siguientes cinco artículos: 
Construcción del árbol de sufijos de Ukkonen: parte 1  
Construcción del árbol de sufijos de Ukkonen: parte 2  
Construcción del árbol de sufijos de Ukkonen: parte 3  
Construcción del árbol de sufijos de Ukkonen: parte 4  Construcción del árbol de sufijos de Ukkonen :
parte 5 Parte 2 , Parte 3 , Parte 4 y Parte 5 , antes de mirar el artículo actual, donde hemos visto algunos conceptos básicos sobre el árbol de sufijos, el algoritmo de ukkonen de alto nivel, el enlace de sufijo y tres trucos de implementación y puntos activos junto con una string de ejemplo «abcabxabcd» donde Pasamos por todas las fases de la construcción del árbol de sufijos. 

Aquí, veremos la estructura de datos utilizada para representar el árbol de sufijos y la implementación del código.
Al final del artículo de la Parte 5 , hemos discutido algunas de las operaciones que haremos mientras construimos el árbol de sufijos y más tarde cuando usemos el árbol de sufijos en diferentes aplicaciones. 
Podría haber diferentes estructuras de datos posibles en las que podamos pensar para cumplir con los requisitos donde alguna estructura de datos puede ser lenta en algunas operaciones y algunas rápidas. Aquí usaremos lo siguiente en nuestra implementación:

Tendremos la estructura SuffixTreeNode para representar cada Node en el árbol. La estructura SuffixTreeNode tendrá los siguientes miembros: 

  • niños : esta será una array del tamaño del alfabeto. Esto almacenará todos los Nodes secundarios del Node actual en diferentes bordes comenzando con diferentes caracteres.
  • suffixLink : esto apuntará a otro Node donde el Node actual debería apuntar a través del enlace de sufijo.
  • inicio, fin : estos dos almacenarán los detalles de la etiqueta de borde desde el Node principal hasta el Node actual. El intervalo (inicio, fin) especifica el borde por el cual el Node está conectado a su Node principal. Cada borde conectará dos Nodes, un padre y un hijo, y el intervalo (inicio, fin) de un borde determinado se almacenará en el Node hijo. Digamos que hay dos Nodes A (padre) y B (hijo) conectados por un borde con índices (5, 8), luego estos índices (5, 8) se almacenarán en el Node B.
  • suffixIndex : no será negativo para las hojas y dará un índice de sufijo para la ruta desde la raíz hasta esta hoja. Para el Node que no es hoja, será -1 .

Esta estructura de datos responderá a las consultas requeridas rápidamente como se muestra a continuación:  

  • ¿Cómo comprobar si un Node es root? — La raíz es un Node especial, sin padre, por lo que su inicio y fin serán -1, para todos los demás Nodes, los índices de inicio y fin no serán negativos.
  • ¿Cómo comprobar si un Node es un Node interno o hoja? – suffixIndex ayudará aquí. Será -1 para el Node interno y no negativo para los Nodes hoja.
  • ¿Cuál es la longitud de la etiqueta de ruta en algún borde? — Cada borde tendrá índices de inicio y final y la longitud de la etiqueta de la ruta será final-inicio+1
  • ¿Cuál es la etiqueta de ruta en algún borde? — Si la string es S, la etiqueta de la ruta será una substring de S desde el índice inicial hasta el índice final inclusive, [inicio, fin].
  • ¿Cómo verificar si hay un borde saliente para un carácter c dado desde un Node A? — Si A->children no es NULL, hay una ruta, si es NULL, no hay ruta.
  • ¿Cuál es el valor del carácter en un borde a cierta distancia d de un Node A? — El carácter a la distancia d del Node A será S[A->start + d], donde S es la string.
  • ¿Hacia dónde apunta un Node interno mediante un enlace de sufijo? — El Node A apuntará a A->suffixLink
  • ¿Cuál es el índice de sufijo en un camino desde la raíz hasta la hoja? — Si el Node hoja es A en la ruta, entonces el índice de sufijo en esa ruta será A->suffixIndex

A continuación se muestra la implementación en C de la construcción del árbol de sufijos de Ukkonen. El código puede parecer un poco largo, probablemente debido a una buena cantidad de comentarios. 

C

// A C program to implement Ukkonen's Suffix Tree Construction
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define MAX_CHAR 256
 
struct SuffixTreeNode {
    struct SuffixTreeNode *children[MAX_CHAR];
 
    //pointer to other node via suffix link
    struct SuffixTreeNode *suffixLink;
 
    /*(start, end) interval specifies the edge, by which the
    node is connected to its parent node. Each edge will
    connect two nodes, one parent and one child, and
    (start, end) interval of a given edge will be stored
    in the child node. Lets say there are two nods A and B
    connected by an edge with indices (5, 8) then this
    indices (5, 8) will be stored in node B. */
    int start;
    int *end;
 
    /*for leaf nodes, it stores the index of suffix for
    the path from root to leaf*/
    int suffixIndex;
};
 
typedef struct SuffixTreeNode Node;
 
char text[100]; //Input string
Node *root = NULL; //Pointer to root node
 
/*lastNewNode will point to newly created internal node,
waiting for it's suffix link to be set, which might get
a new suffix link (other than root) in next extension of
same phase. lastNewNode will be set to NULL when last
newly created internal node (if there is any) got it's
suffix link reset to new internal node created in next
extension of same phase. */
Node *lastNewNode = NULL;
Node *activeNode = NULL;
int count=0;
 
/*activeEdge is represented as input string character
index (not the character itself)*/
int activeEdge = -1;
int activeLength = 0;
 
// remainingSuffixCount tells how many suffixes yet to
// be added in tree
int remainingSuffixCount = 0;
int leafEnd = -1;
int *rootEnd = NULL;
int *splitEnd = NULL;
int size = -1; //Length of input string
 
Node *newNode(int start, int *end)
{
    count++;
    Node *node =(Node*) malloc(sizeof(Node));
    int i;
    for (i = 0; i < MAX_CHAR; i++)
        node->children[i] = NULL;
 
    /*For root node, suffixLink will be set to NULL
    For internal nodes, suffixLink will be set to root
    by default in current extension and may change in
    next extension*/
    node->suffixLink = root;
    node->start = start;
    node->end = end;
 
    /*suffixIndex will be set to -1 by default and
    actual suffix index will be set later for leaves
    at the end of all phases*/
    node->suffixIndex = -1;
    return node;
}
 
int edgeLength(Node *n) {
    return *(n->end) - (n->start) + 1;
}
 
int walkDown(Node *currNode)
{
    /*activePoint change for walk down (APCFWD) using
    Skip/Count Trick (Trick 1). If activeLength is greater
    than current edge length, set next internal node as
    activeNode and adjust activeEdge and activeLength
    accordingly to represent same activePoint*/
    if (activeLength >= edgeLength(currNode))
    {
        activeEdge =
         (int)text[activeEdge+edgeLength(currNode)]-(int)' ';
        activeLength -= edgeLength(currNode);
        activeNode = currNode;
        return 1;
    }
    return 0;
}
 
void extendSuffixTree(int pos)
{
    /*Extension Rule 1, this takes care of extending all
    leaves created so far in tree*/
    leafEnd = pos;
 
    /*Increment remainingSuffixCount indicating that a
    new suffix added to the list of suffixes yet to be
    added in tree*/
    remainingSuffixCount++;
 
    /*set lastNewNode to NULL while starting a new phase,
    indicating there is no internal node waiting for
    it's suffix link reset in current phase*/
    lastNewNode = NULL;
 
    //Add all suffixes (yet to be added) one by one in tree
    while(remainingSuffixCount > 0) {
 
        if (activeLength == 0) {
            //APCFALZ
            activeEdge = (int)text[pos]-(int)' ';
        }
        // There is no outgoing edge starting with
        // activeEdge from activeNode
        if (activeNode->children[activeEdge] == NULL)
        {
            //Extension Rule 2 (A new leaf edge gets created)
            activeNode->children[activeEdge] =
                                  newNode(pos, &leafEnd);
 
            /*A new leaf edge is created in above line starting
            from an existing node (the current activeNode), and
            if there is any internal node waiting for it's suffix
            link get reset, point the suffix link from that last
            internal node to current activeNode. Then set lastNewNode
            to NULL indicating no more node waiting for suffix link
            reset.*/
            if (lastNewNode != NULL)
            {
                lastNewNode->suffixLink = activeNode;
                lastNewNode = NULL;
            }
        }
        // There is an outgoing edge starting with activeEdge
        // from activeNode
        else
        {
            // Get the next node at the end of edge starting
            // with activeEdge
            Node *next = activeNode->children[activeEdge];
            if (walkDown(next))//Do walkdown
            {
                //Start from next node (the new activeNode)
                continue;
            }
            /*Extension Rule 3 (current character being processed
            is already on the edge)*/
            if (text[next->start + activeLength] == text[pos])
            {
                //If a newly created node waiting for it's
                //suffix link to be set, then set suffix link
                //of that waiting node to current active node
                if(lastNewNode != NULL && activeNode != root)
                {
                    lastNewNode->suffixLink = activeNode;
                    lastNewNode = NULL;
                }
 
                //APCFER3
                activeLength++;
                /*STOP all further processing in this phase
                and move on to next phase*/
                break;
            }
 
            /*We will be here when activePoint is in middle of
            the edge being traversed and current character
            being processed is not on the edge (we fall off
            the tree). In this case, we add a new internal node
            and a new leaf edge going out of that new node. This
            is Extension Rule 2, where a new leaf edge and a new
            internal node get created*/
            splitEnd = (int*) malloc(sizeof(int));
            *splitEnd = next->start + activeLength - 1;
 
            //New internal node
            Node *split = newNode(next->start, splitEnd);
            activeNode->children[activeEdge] = split;
 
            //New leaf coming out of new internal node
            split->children[(int)text[pos]-(int)' '] =
                                      newNode(pos, &leafEnd);
            next->start += activeLength;
            split->children[activeEdge] = next;
 
            /*We got a new internal node here. If there is any
            internal node created in last extensions of same
            phase which is still waiting for it's suffix link
            reset, do it now.*/
            if (lastNewNode != NULL)
            {
            /*suffixLink of lastNewNode points to current newly
            created internal node*/
                lastNewNode->suffixLink = split;
            }
 
            /*Make the current newly created internal node waiting
            for it's suffix link reset (which is pointing to root
            at present). If we come across any other internal node
            (existing or newly created) in next extension of same
            phase, when a new leaf edge gets added (i.e. when
            Extension Rule 2 applies is any of the next extension
            of same phase) at that point, suffixLink of this node
            will point to that internal node.*/
            lastNewNode = split;
        }
 
        /* One suffix got added in tree, decrement the count of
        suffixes yet to be added.*/
        remainingSuffixCount--;
        if (activeNode == root && activeLength > 0) //APCFER2C1
        {
            activeLength--;
            activeEdge = (int)text[pos -
                            remainingSuffixCount + 1]-(int)' ';
        }
           
        //APCFER2C2
        else if (activeNode != root)
        {
            activeNode = activeNode->suffixLink;
        }
    }
}
 
void print(int i, int j)
{
    int k;
    for (k=i; k<=j; k++)
        printf("%c", text[k]);
}
 
//Print the suffix tree as well along with setting suffix index
//So tree will be printed in DFS manner
//Each edge along with it's suffix index will be printed
void setSuffixIndexByDFS(Node *n, int labelHeight)
{
    if (n == NULL) return;
 
    if (n->start != -1) //A non-root node
    {
        //Print the label on edge from parent to current node
        print(n->start, *(n->end));
    }
    int leaf = 1;
    int i;
    for (i = 0; i < MAX_CHAR; i++)
    {
        if (n->children[i] != NULL)
        {
            if (leaf == 1 && n->start != -1)
                printf(" [%d]\n", n->suffixIndex);
 
            //Current node is not a leaf as it has outgoing
            //edges from it.
            leaf = 0;
            setSuffixIndexByDFS(n->children[i],
                  labelHeight + edgeLength(n->children[i]));
        }
    }
    if (leaf == 1)
    {
        n->suffixIndex = size - labelHeight;
        printf(" [%d]\n", n->suffixIndex);
    }
}
 
void freeSuffixTreeByPostOrder(Node *n)
{
    if (n == NULL)
        return;
    int i;
    for (i = 0; i < MAX_CHAR; i++)
    {
        if (n->children[i] != NULL)
        {
            freeSuffixTreeByPostOrder(n->children[i]);
        }
    }
    if (n->suffixIndex == -1)
        free(n->end);
    free(n);
}
 
/*Build the suffix tree and print the edge labels along with
suffixIndex. suffixIndex for leaf edges will be >= 0 and
for non-leaf edges will be -1*/
void buildSuffixTree()
{
    size = strlen(text);
    int i;
    rootEnd = (int*) malloc(sizeof(int));
    *rootEnd = - 1;
 
    /*Root is a special node with start and end indices as -1,
    as it has no parent from where an edge comes to root*/
    root = newNode(-1, rootEnd);
 
    activeNode = root; //First activeNode will be root
    for (i=0; i<size; i++)
        extendSuffixTree(i);
    int labelHeight = 0;
    setSuffixIndexByDFS(root, labelHeight);
 
    //Free the dynamically allocated memory
    freeSuffixTreeByPostOrder(root);
}
 
// driver program to test above functions
int main(int argc, char *argv[])
{
    strcpy(text, "abbc"); buildSuffixTree();
    printf("Number of nodes in suffix tree are %d\n",count);
    return 0;
}

Salida (cada borde del árbol, junto con el índice de sufijo del Node secundario en el borde, se imprime en orden DFS. Para comprender mejor la salida, conéctela con la última figura número 43 en el artículo anterior de la Parte 5 ):

abbc [0]
b [-1]
bc [1]
c [2]
c [3]
Number of nodes in suffix tee are 6

Ahora que podemos construir un árbol de sufijos en tiempo lineal, podemos resolver muchos problemas de strings de manera eficiente: 

  • Verifique si un patrón P dado es una substring de texto T (Útil cuando el texto es fijo y el patrón cambia, KMP de lo contrario
  • Encuentre todas las ocurrencias de un patrón P dado presente en el texto T
  • Encuentra la substring repetida más larga
  • Creación de arrays de sufijos de tiempo lineal

Los problemas básicos anteriores se pueden resolver mediante el recorrido DFS en el árbol de sufijos. 
Pronto publicaremos artículos sobre los problemas anteriores y otros como los siguientes:  

y más _
¿Pon a prueba tu comprensión? 

  1. Dibuje el árbol de sufijos (con el enlace de sufijo adecuado, los índices de sufijo) para la string «AABAACAADAABAAABAA$» en papel y vea si coincide con el código de salida.
  2. Toda prórroga debe seguir una de las tres reglas: Regla 1, Regla 2 y Regla 3. 
    A continuación se detallan las reglas aplicadas en cinco prórrogas consecutivas en alguna Fase i (i > 5), cuáles son válidas: 
    A) Regla 1, Regla 2 , Regla 2, Regla 3, Regla 3 
    B) Regla 1, Regla 2, Regla 2, Regla 3, Regla 2 
    C) Regla 2, Regla 1, Regla 1, Regla 3, Regla 3 
    D) Regla 1, Regla 1, Regla 1, Regla 1, Regla 1 
    E) Regla 2, Regla 2, Regla 2, Regla 2, Regla 2 
    F) Regla 3, Regla 3, Regla 3, Regla 3, Regla 3 
     
  3. ¿Cuáles son las secuencias válidas de arriba para la Fase 5?
  4. Cada Node interno DEBE tener su enlace de sufijo establecido en otro Node (interno o raíz). ¿Puede un Node recién creado apuntar a un Node interno ya existente o no? ¿Puede suceder que un nuevo Node creado en la extensión j no obtenga su enlace de sufijo correcto en la siguiente extensión j+1 y obtenga el correcto en extensiones posteriores como j+2, j+3, etc.?
  5. Trate de resolver los problemas básicos discutidos anteriormente.

Hemos publicado los siguientes artículos sobre aplicaciones de árboles de sufijos:  

Referencias
http://web.stanford.edu/~mjkay/gusfield.pdf  
Algoritmo de árbol de sufijos de Ukkonen en inglés sencillo
Este artículo es una contribución de Anurag Singh . Escriba comentarios si encuentra algo incorrecto o si desea compartir más información sobre el tema tratado anteriormente.
 

Publicación traducida automáticamente

Artículo escrito por GeeksforGeeks-1 y traducido por Barcelona Geeks. The original can be accessed here. Licence: CCBY-SA

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *