Binary Tree Rotación

votos
3

Estoy trabajando en la implementación de un árbol de búsqueda AVL. Hasta el momento he terminado la parte de codificación y he comenzado a probar que para los insectos. Descubrí que están pinchados mis métodos de rotación de nodo y por el amor de dios no puedo entender cuál es el problema.

El algoritmo funciona como debería en el papel pero cuando se ejecuta en una máquina bien ... se escapa nodos del árbol.

Este es el método utilizado para hacer girar un nodo a la izquierda: http://pastebin.com/mPHj29Af

bool avl_search_tree::avl_tree_node::rotate_left()
{
    if (_right_child != NULL) {
        avl_tree_node *new_root = _right_child;
 
        if (_parent != NULL) {
            if (_parent->_left_child == this) {
                _parent->_left_child = new_root;
            } else {
                _parent->_right_child = new_root;
            }
        }
 
        new_root->_parent = _parent;
        _parent = new_root;
 
        _right_child = new_root->_left_child;
        new_root->_left_child = this;
 
        if (_right_child != NULL) {
            _right_child->_parent = this;
        }
 
        //update heights
        update_height();
        new_root->update_height();
 
        return true;
    }
 
    return false;
}

En mi método de inserción Comenté la AVL equilibrar parte y en su lugar sólo estoy tratando de girar el nodo que acaba de insertar a la izquierda. El resultado de la inserción de números enteros en orden ascendente: mi árbol sólo contiene la raíz de inicio (primer nodo está insertada) y todos los demás nodos se filtraron.

Cualquier ayuda en la identificación del problema es muy apreciada como yo estoy empezando a volverse loco.

Para el registro: si yo no uso ninguna rotación del árbol no se escape nodos y funciona como un árbol binario de búsqueda desequilibrada normal (para la inserción y la búsqueda).

Editar: Debido al comentario de AJG85 voy a añadir las observaciones:

Añadí 'cheques' printf al método destructor de avl_search_tree :: avl_tree_node que imprimirá el valor de la clave (en mi caso enteros de 32 bits) antes de la limpieza y, en el método de inserción de la avl_search_tree que imprimirá la tecla acaba de insertar.

Luego, en punto de entrada del programa asigno un avl_search_tree en el montón y añadir llaves a ella en orden ascendente y luego eliminarlo.

Con AVL Equilibrio habilitado Me da la siguiente salida en el terminal:

bool avl_search_tree::insert(const int&) : 1
bool avl_search_tree::insert(const int&) : 2
bool avl_search_tree::insert(const int&) : 3
bool avl_search_tree::insert(const int&) : 4
bool avl_search_tree::insert(const int&) : 5
bool avl_search_tree::insert(const int&) : 6
bool avl_search_tree::insert(const int&) : 7
bool avl_search_tree::insert(const int&) : 8
avl_search_tree::avl_tree_node::~avl_tree_node() : 1

Lo que significa thatall inserciones tuvieron éxito, pero sólo la raíz ha sido eliminado.

Con la AVL El equilibrado comentada funciona como un árbol binario de búsqueda normal. La salida del terminal es:

bool avl_search_tree::insert(const int&) : 1
bool avl_search_tree::insert(const int&) : 2
bool avl_search_tree::insert(const int&) : 3
bool avl_search_tree::insert(const int&) : 4
bool avl_search_tree::insert(const int&) : 5
bool avl_search_tree::insert(const int&) : 6
bool avl_search_tree::insert(const int&) : 7
bool avl_search_tree::insert(const int&) : 8
avl_search_tree::avl_tree_node::~avl_tree_node() : 1
avl_search_tree::avl_tree_node::~avl_tree_node() : 2
avl_search_tree::avl_tree_node::~avl_tree_node() : 3
avl_search_tree::avl_tree_node::~avl_tree_node() : 4
avl_search_tree::avl_tree_node::~avl_tree_node() : 5
avl_search_tree::avl_tree_node::~avl_tree_node() : 6
avl_search_tree::avl_tree_node::~avl_tree_node() : 7
avl_search_tree::avl_tree_node::~avl_tree_node() : 8

Lo que significa que todo se limpia correctamente.

Ahora ... ¿Cómo he llegado a la conclusión de que los métodos de rotación son los problemas? En el marco del subprograma de equilibrio AVL comentado he añadido una línea que gira cada nodo que acaba de insertar a la izquierda. ¿El resultado? Lo mismo que si la subrutina AVL Equilibrio estaba habilitado.

Y con respecto al método update_height (), no altera la estructura del árbol de ninguna manera.

Espero que esto se aclare.

Edición 2:

Para aclarar algunas cosas más, el suyo es cómo se implementa el destructor avl_tree_node:

avl_search_tree::avl_tree_node::~avl_tree_node()
{
    printf(%s : %d\n, __PRETTY_FUNCTION__, *_key);

    if (_left_child != NULL) {
        delete _left_child;
    }

    if (_right_child != NULL) {
        delete _right_child;
    }

    if (_key != NULL) {
        delete _key;
    }
}

_left_child y _right_child son punteros a avl_tree_node objetos asignados en el montón.

Editar 3:

Gracias al segundo comentario de AGJ85 he encontrado el problema. En mis métodos rotar olvidé que realmente tiene que actualizar puntero de la raíz del árbol de la nueva raíz cada vez que se cambió la raíz.

Básicamente la raíz del árbol siempre estaba apuntando al primer nodo insertado y sin actualizar el puntero cuando sea necesario, rotar mis métodos podrían filtrarse raíz del nuevo árbol que fue realmente configurado derecha. :)

Gracias AGJ85!

Publicado el 02/08/2011 a las 18:19
fuente por usuario
En otros idiomas...                            


3 respuestas

votos
2

EDITAR - Maldita sea - no vi que el problema ya está resuelto (respuesta en cuestión). Sin embargo, tal vez hay algunos consejos no respuesta en este salvamento pena.

No he comprobado a fondo, pero creo que va mal en esta línea ...

_right_child = new_root->_left_child;

y que el problema es que es probable que haya sobrescrito new_root->_left_childen la línea ...

_parent->_left_child = new_root;

Lo que creo que debe hacer es, al principio, tiene un bloque de definiciones locales como ...

avl_tree_node *orig_parent      = _parent;
avl_tree_node *orig_this        = this;
avl_tree_node *orig_left_child  = _left_child;
avl_tree_node *orig_right_child = _right_child;

A continuación, utilice las orig_variables locales como las fuentes para las tareas posteriores. Esto ahorra una cierta cantidad de preocuparse de flujo de datos a través de los diversos punteros durante la rotación. El optimizador debe deshacerse de cualquier trabajo redundante vale la pena preocuparse en esto, y no hay mucho de eso de todos modos.

Un par de puntos extra ...

En primer lugar, el C ++ (y C) los identificadores de reserva con los estándares de subrayado iniciales, y con los dobles guiones bajos. Se afirmó que se puede obtener interacciones con sorpresa bibliotecas estándar y el compilador suministrado si no se respeta que - supongo que tendría que estar relacionada con la macro para los identificadores miembros, sin embargo. guiones de arrastre están bien - tiendo a utilizar para incluir guardias.

Una convención común para las variables miembro es añadir un líder mo m_. Aún más común, probablemente, es no tener ningún prefijo o sufijo especial en absoluto.

En segundo lugar, puede (o no) le resulte más fácil de implementar árboles AVL que no tienen vínculos de padres almacenados en los nodos. No he implementado árboles AVL todavía, pero me hizo poner en práctica árboles rojo-negro de una vez. Una serie de algoritmos necesita incluir una búsqueda recursiva como el primer paso - no se puede simplemente hacer una búsqueda estándar que recuerda el nodo encontrado, pero descarta la ruta a ese nodo. Sin embargo, la implementación recursiva no es tan malo, y hay un menor número de punteros a hacer malabares.

Por último, un consejo general - tratando de "ensayo" un algoritmo de este tipo pueden tropezar fácilmente, a menos que estrictamente trabaja a través de él paso a paso, y comprobar todas las fuentes de información que son relevantes (haber ya modificado esta?) A cada paso. Es muy fácil de conseguir en el hábito de saltarse algunos de los detalles para la velocidad. Una marcha en seco con ayuda de máquinas es útil para ejecutar el código paso a paso en un depurador, y ver si los resultados en cada paso están de acuerdo con su papel de funcionamiento en seco.

EDITAR - una nota más - no voy a llamar a esto una propina porque no estoy seguro en este contexto. Por lo general implemento nudos de la estructura de datos con estructuras simples - sin ocultación de datos, muy pocos o ninguno funciones miembro. La mayoría del código se mantienen separados de la estructura de datos, a menudo en una clase de "herramienta". Sé que esto rompe principio de programación orientada a objetos del antiguo "la forma dibuja a sí mismo", pero la OMI que funciona mejor en la práctica.

Respondida el 02/08/2011 a las 20:34
fuente por usuario

votos
3

Gracias al segundo comentario de AGJ85 he encontrado el problema. En mis métodos rotar olvidé que realmente tiene que actualizar puntero de la raíz del árbol de la nueva raíz cada vez que se cambió la raíz.

Básicamente la raíz del árbol siempre estaba apuntando al primer nodo insertado y sin actualizar el puntero cuando sea necesario, rotar mis métodos podrían filtrarse raíz del nuevo árbol que fue realmente configurado derecha. :)

Respondida el 03/08/2011 a las 10:03
fuente por usuario

votos
1

Veo que ha encontrado el error que estás buscando en el código. (Como usted dice, no encontramos actualizando el puntero de la raíz del árbol de la nueva raíz cuando la raíz cambia. Es un paradigma común para la lista e Insertar árbol / Eliminar métodos para devolver un puntero a la cabeza de lista o de la raíz del árbol, y si recuerdas ese paradigma no cometer el error de nuevo.)

En un nivel superior de vista, la técnica que he utilizado para evitar problemas con árbol AVL o Red-Negro árbol de código es utilizar en lugar de un árbol de AA , que tiene un rendimiento similar a ellos, el uso de O (n) el espacio y O (log n tiempo) para insertar, eliminar y buscar. Sin embargo, los árboles de AA son significativamente más fácil de código.

Respondida el 04/08/2011 a las 16:55
fuente por usuario

Cookies help us deliver our services. By using our services, you agree to our use of cookies. Learn more