Laboratorio de Estructuras de Datos II Segundo examen parcial Fecha límite de entrega: Jueves 19 de noviembre de 2009, 3:59:59 p.m. Los árboles B son estructuras de datos jerárquicas que se utilizan para almacenar y manipular datos ordenados de forma muy eficiente, ya que por su estructura y sus propiedades las inserciones y las eliminaciones se realizan en un tiempo logarítmico amortizado. Por esta razón, se utilizan para crear índices dentro de las bases de datos relacionales, para agilizar la búsqueda de registros. Un nodo de un árbol B de orden n puede tener (n) hijos, por lo cual contendrá n-1 datos. Por ejemplo, un árbol B de orden 4 tiene la siguiente estructura: 5 2 3 4 7 8 9 15 12 14 25 35 En esta estructura (para el ejemplo un árbol de enteros, de orden 4), se debe garantizar que: Los datos dentro de cada nodo se encuentran ordenados de forma ascendente. Todos los datos de los hijos almacenados a la izquierda de cualquier dato deben ser menores que él y todos los datos de los hijos almacenados a la derecha de cualquier dato deben ser mayores que él. Todas las hojas del árbol se encuentran en el mismo nivel Inserción en un árbol B El proceso de insertar un valor en un árbol B se realiza siempre en los nodos hoja, de la siguiente forma: Si el árbol está vacío, se debe crear un nuevo nodo B e insertar el nuevo valor. Este nodo se convierte en la raíz del árbol. Si el árbol no está vacío, primero se deberá verificar que el nuevo dato no exista dentro del árbol. Para ello se deberá realizar una búsqueda empezando desde la raíz. Si el nuevo dato no existe, el proceso de búsqueda deberá hallar el nodo hoja (B) en el cual se va a insertar el nuevo nodo. Una vez encontrado el nodo hoja (B) en el cual se almacenará el nuevo dato se pueden presentar dos casos: La hoja (B) tiene espacio para almacenar el nuevo dato. Se debe insertar el dato dentro del nodo B y terminar. o La hoja (B) no tiene espacio: En este caso la hoja se deberá dividir en dos nodos B. Se deberán disponer los valores (que estaban almacenados en la hoja más el nuevo valor) de forma ascendente y se tomará el valor de la mitad (n / 2). Este valor se deberá llevar al nodo B padre (si no existe, se deberá crear uno). El hijo izquierdo del dato que se llevó al padre deberá contener los valores menores que él en la hoja original, y el hijo derecho deberá contener los datos mayores que él en la hoja original. Si al llevar un dato al nodo padre no existe espacio para almacenarlo, se deberá dividir el nodo en dos (como en el paso descrito anteriormente). o Implementación de un nodo de árbol B El nodo B se deberá implementar como una lista de nodos binarios, cada uno de los cuales tiene un apuntador izquierdo y derecho a sus nodos B hijos. Tenga en cuenta que los nodos binarios dentro del nodo B comparten el apuntador derecho e izquierdo con sus vecinos. La siguiente gráfica ilustra cómo se debe implementar la estructura de datos para el nodo B (y el nodo binario dentro del nodo B). padre izq der Problema Se deberá implementar un programa que lea comandos desde la entrada estándar para insertar y buscar personas (cada una con codigo, primer nombre, segundo nombre, primer apellido y segundo apellido) en un árbol B que se encuentra inicialmente vacío. El programa deberá terminar cuando encuentre una línea en el archivo con la palabra “salir”. El criterio de ordenamiento de las personas en el árbol B es su codigo. Cada vez que se busque una persona dentro del árbol se deberá imprimir el número de pasos que se realizaron para encontrarla, comparados con el número de pasos necesarios para buscar la persona en un árbol AVL. Los comandos que se deben implementar son: orden n cargar archivo buscar #codigo Define el orden del árbol B: El máximo número de hijos que puede tener cualquier nodo dentro del árbol. Cargar los datos del árbol B a partir de un archivo llamado archivo Busca la persona cuyo codigo es #codigo dentro del árbol B e salir imprime el número de pasos que tuvo que realizar para encontrar el valor. En caso que no se encuentre la persona en el árbol también deberá imprimir el número de pasos que realizó. El número de pasos debe incluir la búsqueda dentro de cada nodo B. Para cada caso se deberá imprimir también el número de pasos que se tuvo que realizar para encontrar el valor en un árbol AVL. Termina la ejecución. El formato del archivo que se debe leer con el comando cargar es el siguiente: #El archivo puede contener comentarios o líneas en blanco que deben ser ignorados #Cada línea describe a una persona. Los campos para cada persona están separados #por espacios, y son los siguientes: #codigo primer_nombre segundo_nombre primer_apellido segundo_apellido #Los únicos campos obligatorios para cada persona son el codigo, el #primer nombre y el primer apellido. Los campos faltantes se representan con “.” 13500710 juan . diaz . 10200340 pedro pablo perez . Salida del programa El programa sólo deberá imprimir cuando reciba un comando “buscar”. Por ejemplo, para el comando: buscar 13500710 El programa deberá imprimir si la persona existe o no dentro del árbol, el número de comparaciones que se realizaron en el árbol B, y el número de comparaciones que se tendrían que realizar en el árbol AVL. Anexo: Algoritmos sugeridos para la implementación de los Árboles B /* Insertar . Algoritmo para insertar un elemento en un árbol B. Parámetros: n: Referencia a la raíz del árbol (nodo b) dato: Dato a insertar en el árbol */ insertar(a, dato) baux = a /* Apuntador a la raíz del árbol (nodo b) */ ant = NULL /* Apuntador a la lista enlazada dentro del nodo b*/ nuevobin = crear_nodobin(dato) padre = NULL mientras baux != NULL /* Sacar el inicio de la lista de datos del nodo b*/ ant = NULL tmp = head(baux->datos) mientras tmp != NULL and tmp->dato < dato ant = tmp tmp = next(tmp) //Siguiente de la lista! fin_mientras padre = baux //Almacenar el apuntador al padre si tmp != NULL /* Se puede encontrar en este nodo b */ si dato == tmp->dato /* Existe en este nodo b?*/ retornar //Ya existe el dato! fin_si baux = tmp->izq //De lo contrario ir por la izquierda sino /* Se termino la lista de datos, bajar al hijo derecho */ baux = ant->der fin_si fin_mientras insertar_nodobin(padre, nuevobin) fin_algoritmo /* Insertar_nodobin . Algoritmo para insertar un nodo binario en un nodo B. Parámetros: a: Referencia al nodo B en el cual se debe insertar el nodo binario nuevo: Nodo binario a insertar dentro del nodo B */ insertar_nodobin (a, nuevo) si a == NULL //Nueva raiz! raiz = crear_nodob() a = raiz //Se deben actualizar los padres de los nodos B izquierdo y derecho //del nuevo nodo binario si nuevo->izq != NULL //El nodo binario a insertar trae nodo B izquierdo? nuevo->izq->padre = a fin_si si nuevo->der != NULL //el nodo binario a insertar trae nodo B derecho? nuevo->der->padre = a fin_si fin_si ant = NULL tmp = head(a->datos) //Buscar el sitio en el cual se debe insertar el nodo dentro del nodo b mientras tmp != NULL && tmp->dato < nuevo->dato ant = tmp tmp = next(tmp) fin_mientras anterior = NULL siguiente = NULL //ant apunta al nodo anterior, tmp apunta al nodo siguiente //Insertar el nodo después de ant. Si ant es nulo, se inserta al inicio de //la lista insert_after(a->datos, nuevo) si ant != NULL anterior = ant fin_si si tmp != NULL siguiente = tmp fin_si //Actualizar la referencia derecha del nodo binario anterior si anterior != NULL anterior->der = nuevo->izq; fin_si //Actualizar la referencia izquierda del nodo binario siguiente si siguiente != NULL siguiente->izq = nuevo->der fin_si balancear(a) fin_algoritmo /* Balancear. Algoritmo para balancear un nodo B Parametros: a: Referencia al nodo B a balancear */ balancear(a) aux = a //Mientras el nodo este desbalanceado, balancear mientras aux != NULL y aux->datos->count == ORDEN ant=NULL it = head(aux->datos) //Hallar el nodo binario de la mitad dentro del nodo B para i desde 0 hasta ORDEN / 2 ant=it it=next(it); fin_para //it queda apuntando al nodo de la lista que se debe subir //Sacar la referencia al nodo binario que se debe subir al padre n = it //Crear el nuevo nodo B que contiene lo datos n/2 + 1 ... n del nodo B actual nuevob = crear_nodob() nuevob->padre = aux->padre //El padre de nuevob es el mismo que aux //El nodo binario que se sube al padre tiene como //hijo izquierdo al nodo B original y como //hijo derecho el nuevo nodo B n->izq = aux n->der = nuevob //Avanzar al siguiente nodo binario: //Estos nodos binarios se deben insertar en el nuevo nodob it = next(it) mientras it!=NULL tmp = it //Actualizar el padre de los nodos B hijos del nuevo nodo B si tmp->izq != NULL tmp->izq->padre = nuevob fin_si si it->next == NULL) //Ultimo nodo? //actualizar la referencia derecha si tmp->der != NULL tmp->der->padre = nuevob fin_si fin_si //Insertar el nodo binario en el nuevo nodo B insertar_nodobin(nuevob, tmp) //avanzar al siguiente nodo binario it=next(it) fin_mientras //Ahora borrar los nodos sobrantes del nodo B original mientras tail(aux->datos) != ant pop_back(aux->datos) fin_mientras //Ahora llevar el nodo binario al nodo B padre! insertar_nodobin(aux->padre, n) //Iterar con el padre, para verificar si quedo balanceado aux = aux->padre fin_mientras fin_algoritmo /* Buscar . Algoritmo para buscar un elemento en un arbol B. Parametros: a : Referencia a la raiz del arbol (nodo b) dato: Dato a buscar en el arbol */ buscar(a, dato) baux = a /* Apuntador a la raiz del arbol (nodo b) */ ant = NULL /* Apuntador a la lista enlazada dentro del nodo b*/ encontrado=FALSE mientras baux != NULL and encontrado == FALSE /* Sacar el inicio de la lista de datos del nodo b*/ ant = NULL tmp = head(baux->datos) /* Comparar el dato con el primero de la lista */ si dato == tmp->dato encontrado=TRUE sino si dato < tmp->dato /* Bajar al hijo izquierdo */ baux = tmp->izq sino /* Recorrer la lista de datos */ ant = NULL mientras tmp != NULL and tmp->dato < dato ant = tmp tmp = next(tmp) //Siguiente de la lista! fin_mientras si tmp != NULL /* Se puede encontrar en este nodo b */ si dato == tmp->dato /* Existe en este nodo b?*/ encontrado=TRUE sino baux = tmp->izq fin_si sino /* Se termino la lista de datos, bajar al hijo derecho */ si dato > ant->dato baux = ant->der fin_si fin_si fin_si fin_mientras retornar encontrado fin_algoritmo