¿Cómo ofrece un nodo centinela beneficios sobre NULL?


En la página de wikipedia del Nodo Centinela se dice que los beneficios de un nodo centinela sobre NULL son:

  • Aumento de la velocidad de las operaciones
  • Tamaño reducido del código algorítmico
  • Mayor robustez de la estructura de datos (posiblemente).

Realmente no entiendo cómo las comprobaciones contra un nodo centinela serían más rápidas (o cómo implementarlas correctamente en una lista o árbol vinculado), así que supongo que esto es más una pregunta de dos partes:

  1. ¿Qué hace que el nodo centinela sea un mejor diseño que NULL?
  2. ¿Cómo implementaría un nodo centinela en (por ejemplo) una lista?
Author: sth, 2011-03-22

4 answers

No hay ventaja con los centinelas si solo haces iteraciones simples y no miras los datos en los elementos.

Sin embargo, hay cierta ganancia real cuando se usa para algoritmos de tipo "find". Por ejemplo, imagine una lista enlazada list std::list donde desea encontrar un valor específico x.

Lo que harías sin centinelas es:

for (iterator i=list.begin(); i!=list.end(); ++i) // first branch here
{
  if (*i == x) // second branch here
    return i;
}
return list.end();

Pero con centinelas (por supuesto, end realmente tiene que ser un nodo real para esto...):

iterator i=list.begin();
*list.end() = x;

while (*i != x) // just this branch!
  ++i;

return i;

Usted ve que no hay necesidad de la rama adicional a probar para el final de la lista - el valor siempre está garantizado para estar allí, por lo que devolverá automáticamente end() si x no se puede encontrar en sus elementos "válidos".

Para otra aplicación genial y realmente útil de sentinels, consulte "intro-sort", que es el algoritmo de ordenación que se usa en la mayoría de las implementaciones std::sort. Tiene una variante genial del algoritmo de partición que usa centinelas para eliminar algunas ramas.

 22
Author: ltjax,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-03-21 22:39:57

Creo que un pequeño ejemplo de código sería una mejor explicación que una discusión teórica.

El siguiente es el código para la eliminación de nodos en una lista doblemente vinculada de nodos donde NULL se utiliza para marcar el final de la lista y donde dos punteros first y last se utilizan para mantener la dirección del primer y último nodo:

// Using NULL and pointers for first and last
if (n->prev) n->prev->next = n->next;
        else first = n->next;
if (n->next) n->next->prev = n->prev;
        else last = n->prev;

Y este es el mismo código donde en su lugar hay un nodo ficticio especial para marcar el final de la lista y donde la dirección del primer nodo en la lista se almacena en el campo next del nodo especial y donde el último nodo de la lista se almacena en el campo prev del nodo ficticio especial:

// Using the dummy node
n->prev->next = n->next;
n->next->prev = n->prev;

El mismo tipo de simplificación también está presente para la inserción de nodos; por ejemplo, para insertar nodo n antes de nodo x (teniendo x == NULL o x == &dummy que significa inserción en la última posición) el código sería:

// Using NULL and pointers for first and last
n->next = x;
n->prev = x ? x->prev : last;
if (n->prev) n->prev->next = n;
        else first = n;
if (n->next) n->next->prev = n;
        else last = n;

Y

// Using the dummy node
n->next = x;
n->prev = x->prev;
n->next->prev = n;
n->prev->next = n;

Como puede ver el enfoque de nodo ficticio eliminado para una lista doblemente vinculada, todos los casos especiales y todos condicionales.

La siguiente imagen representa los dos enfoques para la misma lista en memoria...

NULL / dummy node alternatives for a doubly-linked list

 54
Author: 6502,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-01-03 15:31:33

La respuesta a tu pregunta (1) está en la última frase de la entrada enlazada de Wikipedia: "Como nodos que normalmente enlazarían a NULL ahora enlazarían a "nil" (incluyendo nil en sí), elimina la necesidad de una costosa operación de rama para comprobar si hay NULL."

Normalmente necesita probar un nodo para NULL antes de acceder a él. Si en cambio tiene un nodo nil válido, entonces no necesita hacer esta primera prueba, guardando una comparación y una rama condicional, que de lo contrario puede ser caro en CPU superescalar modernas cuando la rama está mal predicha.

 8
Author: Paul R,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-03-21 22:17:59

Intentaré responder en el contexto de la biblioteca de plantillas estándar:

1) En una llamada a "next ()", NULL no indica necesariamente el final de la lista. ¿Qué pasa si se produce un error de memoria? Devolver un nodo centinela, es una forma definitiva de indicar que se había producido el final de la lista, y no algún otro resultado. En otras palabras, NULL podría indicar una variedad de cosas, no solo el final de la lista.

2) Este es solo un método posible: cuando cree su lista, cree un nodo privado que sea no compartido fuera de la clase (llamado "lastNode" por ejemplo). Al detectar que has iterado hasta el final de la lista, haz que "next()" devuelva una referencia a "lastNode". También haga que un método llamado "end()" devuelva una referencia a "lastNode". Por último, dependiendo de cómo implemente su clase, es posible que deba anular el operador de comparación para que esto funcione correctamente.

Ejemplo:

class MyNode{

};

class MyList{

public:
    MyList () : lastNode();

    MyNode * next(){

       if (isLastNode) return &lastNode;
       else return //whatever comes next
    }

    MyNode * end() {

       return &lastNode;
    }

    //comparison operator
    friend bool operator == (MyNode &n1, MyNode &n2){

        return (&n1 == &n2); //check that both operands point to same memory
    }


private:
    MyNode lastNode;
};


int main(){

  MyList list;
  MyNode * node = list.next();

  while ( node != list.end() ){ 

     //do stuff!

     node = list.next();
  }

  return 0; 
}
 0
Author: J T,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-03-21 22:34:50