Listas Enlasadas y Arboles

October 24, 2017 | Author: Flora Betty Patiño Vincenti | Category: Pointer (Computer Programming), Algorithms And Data Structures, Software Engineering, Data, Computer Programming
Share Embed Donate


Short Description

Descripción: Informatica...

Description

TEMA 3 LISTAS ENCADENADAS OBJETIVOS Emplear estructuras dinámicas de datos en el procesamiento de la información. Desarrollar algoritmos para optimizar las operaciones básicas que se llevan a cabo en proceso de ejecución en estructuras lineales. CONTENIDO 3.1 Introducción 3.2 Lista Simplemente Enlazada 3.3 Listas Simplemente Enlazadas Circulares 3.4 Listas Doblemente Enlazadas 3.5 Listas Doblemente Enlazadas Circulares 3.1 LISTAS ENCADENADAS Las listas enlazadas son estructuras de datos lineales, que constan de una secuencia de nodos que se encuentran enlazados uno con otro, mediante un enlace o puntero.

La adición / eliminación en estas estructuras de datos, se realiza en tiempo de ejecución y en forma dinámica.

La cantidad de nodos que una lista enlazada puede contener está limitada por la memoria del computador. TIPOS DE LISTAS. Existen los siguientes tipos de listas enlazadas: 3.2 LISTAS SIMPLEMENTE ENLAZADAS Una lista simplemente enlazada se representa tal como se muestra en la siguiente grafica:

Donde : El nodo Raíz es un puntero al primer elemento de la lista. Cada elemento (nodo) de la lista simplemente enlazada debe tener dos campos:  Un campo información (info) que contiene el valor de ese elemento y puede contener cualquier tipo estándar o definido por el usuario.  Un campo enlace (o puntero) que indica la posición del siguiente elemento de la lista. NODO

Existe una marca para fin de lista, que es la constante NIL, también representada por una barra inclinada o el signo eléctrico de tierra. Declaración de un nodo usando el lenguaje Pascal: Type pnodo = ^nodo nodo = record info : Integer; Sig :pnodo; End; Declaración de un nodo usando el lenguaje C: struct Nodo

{ int info; struct Nodo *sig; }; OPERACIONES EN LISTAS SIMPLEMENTE ENLAZADAS Como cualquier estructura de datos, en las listas simplemente enlazadas se puede realizar las siguientes operaciones básicas:  Inserción  Eliminación  Búsqueda  Recorrido RECORRIDO Para realizar el recorrido en una lista simple, es necesario el uso de un puntero auxiliar ACT, con la cual se visita toda la lista, empezando por la RAIZ y finalizando cuando el puntero ACT apunta a NIL.

ALGORITMO: Recorrido(Dato:Integer) Inicio Actual = Raíz Mientras(Actual Nil) hacer Actual =Actual^.sig Mostar Actual^.inf Fin Mientras Fin. BUSQUEDA Para realizar la búsqueda en una lista simple, es necesario el uso de un puntero auxiliar ACT, con la cual se busca empezando por la RAIZ y finalizando cuando el puntero ACT apunta al elemento buscado o a NIL.

ALGORITMO: Buscar(Dato:Integer) Inicio Actual = Raíz Mientras(ActualNil) y (Actual^.infDato) hacer Actual =Actual^.sig Fin Mientras Si Actual^.inf = Dato entonces Mostar "El Dato Esta en la Lista" Sino Mostrar "No Existe el Dato" Fin si Fin. INSERCION DE DATOS Existen distintas formas de adición de nodos a una lista simple, de acuerdo a los propósitos de uso de estos, entre las cuales se tiene:  Insertar cuando lista está vacía

 Insertar al principio  Insertar al final  Insertar al medio de dos nodo En todas ellas se realiza una tarea común:  Crear una nuevo nodo  Almacenar el elemento  Agregar los enlaces respectivos INSERTAR CUANDO LISTA ESTA VACIA:

Insertar(5)

 New(Nuevo)  Nuevo^.inf= 5  Raiz = nuevo (1)  Nuevo^.sig= nil(2) INSERTAR AL PRINCIPIO:

Insertar(3)

 New(Nuevo)  Nuevo^.inf= 3  Nuevo^.sig = Raiz(1)  Raiz = Nuevo (2) INSERTAR AL FINAL:

Insertar(13)

 New(Nuevo)  Nuevo^.inf= 13  ant^.sig =Nuevo(1)  Nuevo^.sig =Nil(2) INSERTAR AL MEDIO DE DOS NODOS:

Insertar(7)

 New(Nuevo)  Nuevo^.inf= 7  Nuevo^.sig =Act(1)  Ant^.sig = Nuevo(2) ALGORITMO: Insertar (Dato:Entero) Inicio New(Nuevo) Nuevo^.inf = Dato Si Raiz = Nil Entonces Raiz = Nuevo Nuevo^ sig = Nil Si no Anterior = Raiz Actual = Raiz Mientras (Actual Nil) y (Dato> Actual^.Inf) Hacer Anterior = Actual Actual = Actual^.sig Fin Mientras Si Anterior =Actual Entonces Nuevo^.sig = Raiz Raiz = Nuevo Si no Nuevo^.sig = Actual Anterior^.sig = Nuevo Fin si Fin si Fin. ELIMINAR DATOS Para eliminar un dato de una lista simple, es necesario buscarlo en la lista primeramente. La búsqueda se realiza con dos punteros auxiliares ANT y ACT, si el dato se encuentra en la lista, el puntero ACT apunta a ella y el puntero ANT apunta al anterior elemento. Existen dos casos que se presentan al eliminar un dato: 1. Eliminar el primer elemento 2. Eliminar un elemento distinto del primero ELIMINAR EL PRIMER ELEMENTO Dada la siguiente lista:

Eliminar(5)

 Raiz= act^.sig(1)  Dispose(act) ELIMINAR UN ELEMENTO DISTINTO DEL PRIMERO Dada la siguiente lista:

Eliminar(9)

 ant^.sig = act^.sig  Dispose(act) ALGORITMO: Eliminar (Dato:Entero) Inicio Si Raiz = nil then Mostrar ‘No existe Datos’ Si no Anterior = Raiz Actual = Raiz Mientras (Actual Nil) y (Actual^.inf Dato) Hacer Anterior = Actual Actual =Actual^.sig Fin Mientras Si Actual^.inf = Dato entonces Si Anterior = Actual entonces Raiz = Actual^.sig Sino Anterior^.sig = Actual^.sig Fin si Dispose(Actual) Sino Imprimir "No Existe el Dato" Fin si Fin si Fin. PROGRAMA QUE REALIZA LA INSERCION, ELIMINACION Y VISUALIZACION DE DATOS DE UNA LISTA SIMPLEMENTE ENLASADA ------------------------------------------------------------------------*/ #include #include #include #include struct Nodo { int inf;

struct Nodo *sig; }; struct Nodo *raiz=NULL,*nuevo,*ant,*act; char opc; void insertar(int p) { nuevo=(struct Nodo*)malloc(sizeof(struct Nodo)); nuevo->inf=p; if(raiz==NULL) { nuevo->sig=NULL; raiz=nuevo; } else { ant=raiz; act=raiz; while((act->infsig; } if(ant==act) { nuevo->sig=raiz; raiz=nuevo; } else { nuevo->sig=act; ant->sig=nuevo; } } } void eliminar(int dato) { if(raiz==NULL) printf("\n\tLista vacia"); else { ant=raiz; act=raiz; while((act->inf!=dato)&&(act!=NULL)) { ant=act; act=act->sig; } if(act==NULL) printf("\n\tdato no existe"); else { if(ant==act) raiz=act->sig; else {

ant->sig=act->sig; } free(act); } } } void imprimir() { act=raiz; clrscr(); gotoxy(30,1);printf("VISUALIZACION DE DATOS"); while(act!=NULL) { printf("\n\t%5d",act->inf); act=act->sig; } getch(); } void main() { int dato; do { clrscr(); printf("\n\t-----------------------\n"); printf("\tLISTA SIMPLEMENTE ENLASADA\n"); printf("\t-----------------------\n"); printf("\t1 ..........INSERTAR \n"); printf("\t2 ..........ELIMINAR \n"); printf("\t3 ...........MOSTRAR \n"); printf("\t4 .............SALIR \n"); printf("\t-----------------------\n"); printf("\tELIJA UNA OPCION-> "); opc=getche(); switch(opc) { case '1': printf("\n\tDATO A INSERTAR->");scanf("%d",&dato); insertar(dato); break; case '2': printf("\n\tDATO A ELIMINAR->");scanf("%d",&dato); eliminar(dato); break; case '3': imprimir(); break; } }while(opc!='4'); } 3.3 LISTAS SIMPLEMENTE ENLAZADAS CIRCULARES. Una lista circular es aquella en la cual el puntero siguiente del último elemento apunta hacia el primer elemento. Por lo cual en estas listas no existe ni primero ni último elemento, aunque se debe elegir obligatoriamente un puntero para referenciar la lista.

Esta lista presenta la gran ventaja de que cada nodo en una lista circular es accesible desde cualquier nodo. Tiene el inconveniente de que requiere un diseño cuidadoso para evitar caer en un bucle infinito.

Existen dos tipos de listas enlazadas simples circulares:  Listas simples circulares sin nodo cabecera



Listas simples circulares con nodo cabecera

El manejo de la primera es complejo, de tal modo que solo se usaremos las listas enlazadas con nodo cabecera, debido a su fácil manejo. OPERACIONES EN LISTAS SIMPLEMENTE ENLAZADAS CIRCULARES Como cualquier estructura de datos, en las listas simplemente enlazadas circulares se puede realizar las siguientes operaciones básicas:  Inserción  Eliminación  Búsqueda  Recorrido RECORRIDO Para realizar el recorrido en una lista simple circular, es necesario el uso de un puntero auxiliar ACT, con la cual se visita toda la lista, empezando por la RAIZ y finalizando cuando el puntero ACT apunta a NIL.

ALGORITMO: Recorrido(Dato:Integer) Inicio Actual = Raíz^.sig Mientras(Actual Raiz) hacer Actual =Actual^.sig Mostarc Actual^.inf Fin Mientras Fin. BUSQUEDA Para realizar la búsqueda en una lista simple, es necesario el uso de un puntero auxiliar ACT, con la cual se busca empezando por la RAIZ y finalizando cuando el puntero ACT apunta al elemento buscado o a NIL.

ALGORITMO: Buscar(Dato:Integer) Inicio Actual = Raíz^.sig

Mientras(Actual Raiz) y (Actual^.inf Dato) hacer Actual =Actual^.sig Fin Mientras Si Actual^.inf = Dato entonces Mostar "El Dato Esta en la Lista" Sino Mostrar "No Existe el Dato" Fin si Fin. INSERCION DE DATOS Existen distintas formas de adición de nodos a una lista simple circular, de acuerdo a los propósitos de uso de estos. Entre las cuales se tiene:  Insertar cuando lista está vacía  Insertar al medio de dos nodos En todas ellas se realiza una tarea común, que es el de crear una nuevo nodo para almacenar al elemento que será agregado a la lista. INSERTAR CUANDO LISTA ESTA VACIA:

Insertar(5)

 New(Cab)  New(Nuevo)  Nuevo^.inf= 5  Raiz = Cab (1)  Cab^.sig= nuevo (2)  Nuevo^.sig= Raiz (3) INSERTAR AL MEDIO DE DOS NODOS:

Insertar(7)

 New(Nuevo)  Nuevo^.inf= 7  Nuevo^.sig = Act (1)  Ant^.sig = Nuevo (2) ALGORITMO: Insertar (Dato:Entero)

Inicio New(Nuevo) Nuevo^.inf = Dato Si Raiz = Nil Entonces New(Cab) Raiz = Cab Cab^.sig =Nuevo Nuevo^.sig = Raiz Si no Anterior = Raiz Actual = Raiz^ sig Mientras (Actual Raiz) y (Dato> Actual^.Inf) Hacer Anterior = Actual Actual = Actual^.sig Fin Mientras Nuevo^.sig = Actual Anterior^.sig = Nuevo Fin si Fin. ELIMINAR DATOS Para eliminar un dato de una lista simple circular, es necesario buscarlo en la lista primeramente. La búsqueda se realiza con dos punteros auxiliares ANT y ACT, si el dato se encuentra en la lista, el puntero ACT apunta a ella y el puntero ANT apunta al anterior elemento. Existen dos casos que se presentan al eliminar un dato:  Eliminar él ultimo elemento de la lista  Eliminar un elemento distinto del ultimo ELIMINAR EL ULTIMO ELEMENTO DE LA LISTA Dada la siguiente lista:

Eliminar(9)

Para eliminar el ultimo elemento, es necesario liberar la memoria de los nodos que apuntan a ANT y ACT.  Dispose(ant)  Dispose(act)  Raiz = nil (1) ELIMINAR UN ELEMENTO DISTINTO DEL ULTIMO Dada la siguiente lista:

Eliminar(5)

 ant^.sig = act^.sig  Dispose(act) ALGORITMO: Eliminar (Dato:Entero) Inicio Si Raiz = nil then Mostrar ‘No existe Datos’ Si no Anterior = Raiz Actual = Raiz^.sig Mientras (Actual Raiz) y (Actual^.inf Dato) Hacer Anterior = Actual Actual =Actual^.sig Fin Mientras Si Actual^.inf Raiz entonces Si Anterior = Actual^.sig entonces Dispose(Anterior) Raiz = Nil Sino Anterior^.sig = Actual^.sig Fin si Dispose(Actual) Sino Imprimir "No Existe el Dato" Fin si Fin si Fin. /*-----------------------------------------------------------------------PROGRAMA QUE REALIZA LA INSERCION, ELIMINACION Y VISUALIZACION DE DATOS DE UNA LISTA CIRCULAR SIMPLEMENTE ENLAZADA ------------------------------------------------------------------------*/ #include #include #include #include struct Nodo { int inf; struct Nodo *sig; }; struct Nodo *raiz=NULL,*nuevo,*ant,*act,*cab; char opc; void insertar(int p) { nuevo=(struct Nodo*)malloc(sizeof(struct Nodo)); nuevo->inf=p; if(raiz==NULL) { cab=(struct Nodo*)malloc(sizeof(struct Nodo)); raiz=cab;

cab->sig=nuevo; nuevo->sig=raiz; } else { ant=raiz; act=raiz->sig; while((act->infsig; } nuevo->sig=act; ant->sig=nuevo; } } void eliminar(int dato) { if(raiz==NULL) { printf("\n\tNO EXISTEN DATOS"); getch(); } else { ant=raiz; act=raiz->sig; while((act->inf!=dato)&&(act!=raiz)) { ant=act; act=act->sig; } if(act==raiz) { printf("\n\tdato no existe"); getch(); } else { if(act->sig==ant) { raiz=NULL; free(ant); } else ant->sig=act->sig; free(act); } } } void imprimir() { clrscr(); gotoxy(30,1);printf("VISUALIZACION DE DATOS"); act=raiz->sig; while(act!=raiz)

{ printf("\n\t%5d",act->inf); act=act->sig; } getch(); } void main() { int dato; do { clrscr(); printf("\n\t-----------------------\n"); printf("\t LISTAS CIRCULARES \n\tSIMPLEMENTE ENLAZADAS\n"); printf("\t-----------------------\n"); printf("\t1 ..........INSERTAR \n"); printf("\t2 ..........ELIMINAR \n"); printf("\t3 ...........MOSTRAR \n"); printf("\t4 .............SALIR \n"); printf("\t-----------------------\n"); printf("\tELIJA UNA OPCION-> "); opc=getche(); switch(opc) { case '1': printf("\n\tDATO A ELIMINAR->"); scanf("%d",&dato); insertar(dato); break; case '2': printf("\n\tDATO A ELIMINAR->"); scanf("%d",&dato); eliminar(dato); break; case '3': imprimir(); break; } }while(opc!='4'); } 3.4 LISTAS DOBLEMENTE ENLAZADAS. En muchas aplicaciones se requiere recorrer la lista en dos direcciones. Estos recorridos se pueden conseguir manteniendo dos campos de enlace en cada nodo en lugar de uno. Estos enlaces(punteros) se utilizan para denotar la dirección del predecesor y sucesor de un nodo dado. El predecesor se llama enlace izquierdo y el sucesor enlace derecho.

Donde:  Nodo Inicio es un puntero al primer elemento de la lista.  Nodo Fin es un puntero al último elemento de la lista.  Una lista cuya Estructura de Nodo, contiene dos campos de enlace se denominará lista lineal doblemente enlazada. NODO

Donde:   

campo enlace ant. apunta al anterior nodo de la lista. campo info contiene cualquier tipo estándar de datos. campo enlace sig. apunta al siguiente nodo de la lista.

Existe una marca para fin de lista, que es la constante NIL, también representada por una barra inclinada o el signo eléctrico de tierra. Declaración de un nodo usando el lenguaje Pascal: Type pnodo = ^nodo nodo = record info : Integer; Ant, Sig :pnodo; End; Declaración de un nodo usando el lenguaje C: Struct nodo { int info; struct nodo *Ant, * Sig; }; OPERACIONES EN LISTAS DOBLEMENTE ENLAZADAS. Las operaciones básicas para manipular listas doblemente enlazadas son:  Inserción  Eliminación  Recorrido  Búsqueda RECORRIDO En una lista doble circular es posible realizar dos tipos de recorridos, esto debido a que en estas listas existen enlaces en ambas direcciones.  Recorrido en forma ascendente  Recorrido en forma descendente RECORRIDO EN FORMA ASCENDENTE Este recorrido se inicia cuando el puntero apunta a INICIO y se recorre de nodo a nodo con el puntero SIG hasta que el nodo apunte a NIL.

ALGORITMO: Inicio Actual = Inicio Mientras Actual NIL hacer Mostrar Actual^.inf Actual = Actual^.sig Fin Mientras Fin. RECORRIDO EN FORMA DECENDENTE Este recorrido se inicia cuando el puntero apunta a FIN y se recorre de nodo a nodo con el puntero ANT hasta que el puntero apunte a NIL.

ALGORITMO: Inicio Actual = Fin} Mientras Actual Nil Hacer Mostrar Actual^.inf Actual = Actual^.ant Fin Mientras

Fin. INSERCION DE DATOS Existen distintas formas de adición de nodos a una lista doble, de acuerdo a los propósitos de uso de estos. Entre las cuales se tiene:  Insertar cuando lista está vacía  Insertar al principio  Insertar al final  Insertar al medio de dos nodos En todas ellas se realiza una tarea común, que es el de crear una nuevo nodo para almacenar al elemento que será agregado a la lista. INSERTAR CUANDO LISTA ESTA VACIA: Dada la siguiente lista:

Insertar(5)

 New(Nuevo)  Nuevo^.inf= 5  Inicio = nuevo (1)  Fin = nuevo (2)  Nuevo^.ant= nil (3)  Nuevo^.sig= nil (4) INSERTAR AL PRINCIPIO Dada la siguiente lista:

Insertar(3)

 New(Nuevo)  Nuevo^.inf= 3  Inicio = Nuevo(1)  Nuevo^.sig = Act(2)  Act^.ant = Nuevo(3)  Nuevo^.ant = Nil(4) INSERTAR AL FINAL: Dada la siguiente lista:

Insertar(13)

 New(Nuevo)  Nuevo^.inf= 13  ant^.sig = Nuevo(1)  Nuevo^.sig = Nil(2)  Fin = Nuevo(3)  Nuevo^.ant = Ant(4) INSERTAR AL MEDIO DE DOS NODOS: Dada la siguiente lista:

Insertar(7)

 New(Nuevo)  Nuevo^.inf= 7  Ant^.sig = Nuevo(1)  Nuevo^.sig = Act(2)  Act^.ant = Nuevo(3)  Nuevo^.ant = Ant (4) ALGORITMO: Insertar (Dato:Entero) Inicio New(Nuevo) Si Inicio = Nil Entonces Inicio = Nuevo; Final = Nuevo Nuevo^.ant = Nil; Nuevo^.sig = Nil Si no Anterior = Inicio Actual = Inicio Mientras (Actual Nil) y( Actual^.inf < Dato) Hacer Anterior = Actual Actual =Actual^.sig Fin Mientras Si Actual = Anterior Entonces Nuevo^.sig = Inicio Inicio^.ant = Nuevo Inicio = Nuevo Nuevo^.ant = Nil Si no Si Actual = Nil Entonces Final = Nuevo Si no Actual^.ant = Nuevo

Fin si Nuevo^.sig = Actual Anterior^.sig = Nuevo Nuevo^.ant = Anterior Fin si Fin si Fin. ELIMINAR DATOS Para eliminar un dato de una lista doble, es necesario buscarlo en la lista primeramente. La búsqueda se realiza con dos punteros auxiliares ANT y ACT, si el dato se encuentra en la lista, el puntero ACT apunta a ella y el puntero ANT apunta al anterior elemento. Existen dos casos que se presentan al eliminar un dato:  Eliminar el primer elemento  Eliminar un elemento distinto del primero  Eliminar él ultimo elemento ELIMINACIÓN AL PRINCIPIO: Dada la siguiente lista:

Eliminar(5)

1. 2.

Inicio = Inicio^.sig Inicio^.ant = Nil Dispose(act) ELIMINACIÓN AL MEDIO: Dada la siguiente lista:

Eliminar(8)

1. 2.

Ant^.sig = Act^.sig Act^.sig^.ant = Ant Dispose(act) ELIMINACIÓN AL FINAL: Dada la siguiente lista:

Eliminar (9)

1. 2.

Ant^.sig = Act^.sig = nil Final = Ant Dispose(act) ALGORITMO: Eliminar (Valor: Entero) Inicio Anterior = Inicio Actual = Inicio Mientras (Actual Nil) y (Actual^.inf Valor) hacer Anterior = Actual Actual =Actual^.sig Fin Mientras Si Actual^.inf Valor Entonces Mostrar "No Existe el Elemento" Si no Si Actual=Anterior Entonces Si Actual^.sig Nil Entonces Inicio = Inicio^.sig Inicio^.ant = Nil Si no Final = Nil Inicio = Nil Fin si Si no Si Actual^.sig Nil entonces Actual^.sig^.Ant = Anterior Si no Final = Anterior Fin si Anterior^.sig = Actual^.sig Fin si Fin si Fin. /*-------------------------------------------------------------------------------------------------PROGRAMA QUE REALIZA LA INSERCION, ELIMINACION Y VISUALIZACION DE DATOS DE UNA LISTA DOBLEMENTE ENLASADA ------------------------------------------------------------------------*/ #include #include #include struct Nodo { int inf; struct Nodo *sig,*antp; }; struct Nodo *nuevo,*ant,*act,*inicio,*fin; int dato; char op; void insertar(int dato) {

nuevo=(struct Nodo*)malloc(sizeof(struct Nodo)); nuevo->inf=dato; if((inicio==NULL)&&(fin==NULL)) { inicio=nuevo; fin=nuevo; nuevo->sig=NULL; nuevo->antp=NULL; } else { ant=inicio; act=inicio; while((act!=NULL)&&(act->infsig; } if(ant==act) { inicio=nuevo; nuevo->sig=act; act->antp=nuevo; nuevo->antp=NULL; } else { nuevo->sig=act; ant->sig=nuevo; nuevo->antp=ant; if (act!=NULL) act->antp=nuevo; else fin=nuevo; } } } void eliminar(int dato) { if((inicio==NULL)&&(fin==NULL)) printf("Lista vacia"); else { ant=inicio; act=inicio; while((act!=NULL)&&(act->inf!=dato)) { ant=act; act=act->sig; } if(act==NULL) printf("\n\tEL DATO NO EXISTE"); else { if(ant==act) { if(inicio==fin)

{ inicio=NULL; fin=NULL; } else { inicio=inicio->sig; inicio->antp=NULL; } } else { ant->sig=act->sig; if(fin!=act) act->sig->antp=ant; else fin=ant; } free(act); } } } void ascendente() { act=inicio; printf("\n\tRECORRIDO ASCENDENTE"); while(act!=NULL) { printf("\n\t%d",act->inf); act=act->sig; } getch(); } void descendente() { act=fin; printf("\n\tRECORRIDO DESCENDENTE"); if(inicio==NULL) printf("\t\nNO HAY DATOS"); else { while(act!=NULL) { printf("\n\t%d",act->inf); act=act->antp; } } getch(); } void main() { do { clrscr(); printf("\n\t---------------------------\n"); printf("\tLISTAS DOBLEMENTE ENLAZADAS\n"); printf("\t---------------------------\n"); printf("\t1 .................INSERTAR \n");

printf("\t2 .................ELIMINAR \n"); printf("\t3 .....RECORRIDO.ASCENDENTE \n"); printf("\t4 ....RECORRIDO.DESCENDENTE \n"); printf("\t5 ....................SALIR \n"); printf("\t---------------------------\n"); printf("\tELIJA UNA OPCION-> "); scanf("%c",&op); switch(op) { case '1': { printf("\tINGRESE NUMERO:");scanf("%d",&dato); insertar(dato); break; } case '2': { printf("\tINGRESE NUMERO:");scanf("%d",&dato); eliminar(dato); break; } case '3': ascendente(); break; case '4': descendente(); break; } }while(op!='5'); } 3.5 LISTAS CIRCULARES DOBLEMENTE ENLAZADAS. Una lista doble circular es aquella en la cual el puntero siguiente del ultimo elemento apunta hacia el primer elemento. En estas listas no existe ni primero ni último elemento, aunque se debe elegir obligatoriamente un puntero para referenciar la lista.

Esta lista presenta la gran ventaja de que cada nodo en una lista doble circular es accesible desde cualquier nodo. Tiene el inconveniente de que requiere un diseño cuidadoso para evitar caer en un bucle infinito. Existen dos tipos de listas enlazadas simples circulares: Listas dobles circulares sin nodo cabecera

Listas dobles circulares con nodo cabecera

El manejo de la primera es complejo, de tal modo que solo se usaremos las listas enlazadas con nodo cabecera, debido a su fácil manejo. OPERACIONES EN LISTAS DOBLEMENTE ENLAZADAS CIRCULARES. Las operaciones básicas para manipular listas doblemente enlazadas son:  Inserción  Eliminación

 

Recorrido Búsqueda

RECORRIDO En una lista doble circular es posible realizar dos tipos de recorridos, esto debido a que en estas listas existen enlaces en ambas direcciones.  Recorrido en forma ascendente  Recorrido en forma descendente RECORRIDO EN FORMA ASCENDENTE Este recorrido se inicia cuando el puntero apunta a Raiz^.sig y se recorre de nodo a nodo con el puntero SIG hasta llegar al nodo Raiz.

ALGORITMO: Inicio Actual = Raiz^.sig Mientras Actual Raiz hacer Mostrar Actual^.inf Actual = Actual^.sig Fin Mientras Fin. RECORRIDO EN FORMA DECENDENTE Este recorrido se inicia cuando el puntero apunta a Raiz^.ant y se recorre de nodo a nodo con el puntero ANT hasta llegar al nodo Raiz.

ALGORITMO: Inicio Actual = Raiz^.ant Mientras Actual Raiz Hacer Mostrar Actual^.inf Actual = Actual^.ant Fin Mientras Fin. INSERCION DE DATOS Existen dos formas de inserción de datos a una lista doblemente enlazada circular:  Inserción en una lista vacía  Insertar al medio de dos nodos INSERCIÓN EN UNA LISTA VACIA

Insertar(9)

 New(Cab)  New(nuevo)  nuevo^.inf=9  Raiz = Cab (1)  Cab^.sig = Nuevo (2)  Nuevo^.sig = Cab (3)  Cab^.ant = Nuevo (4)  Nuevo^.ant = Cab (5) INSERTAR AL MEDIO DE DOS NODOS:

Insertar(6)

 New(Cab)  New(nuevo)  nuevo^.inf=6  Ant^.sig = Nuevo(1)  Nuevo^.sig = Act(2)  Act^.ant = Nuevo(3)  Nuevo^.ant = Ant(4) ALGORITMO: Insertar (dato:entero) Inicio New(Nuevo) Nuevo^.inf = Dato Si Raiz = Nil Entonces New(NodoC) Raiz = NodoC NodoC^.sig = NodoC NodoC^.ant = Nuevo Nuevo^.ant = NodoC Si no Anterior = Raiz Actual = Raiz^.sig Mientras (Actual Raiz) y (Actual^.inf < dato) Hacer Anterior = Actual Actual =Actual^.sig Fin Mientras

Nuevo^.sig = Actual Actual^.sig = Nuevo Nuevo^.ant = Anterior Actual^.ant = Nuevo Fin si Fin. ELIMINAR DATOS Para eliminar un dato de una lista doble circular, es necesario buscarlo en la lista primeramente. La búsqueda se realiza con dos punteros auxiliares ANT y ACT, si el dato se encuentra en la lista, el puntero ACT apunta a ella y el puntero ANT apunta al anterior elemento. Existen dos casos que se presentan al eliminar un dato:  Eliminar él último elemento  Eliminar un elemento ELIMINAR ÉL ÚLTIMO ELEMENTO

Eliminar (9)

 Dispose(ant)  Dispose(act)  Raiz = Nil ELIMINAR UN ELEMENTO

Eliminar (9)

 Ant^.sig = Act^.sig(1)  Act^.sig ^.ant = Ant(2)  Dispose(act) ALGORITMO: Eliminar (Valor:Entero) Inicio Anterior = Raiz Actual = Raiz^.sig Mientras (Actual Raiz) y (Actual^.inf Valor) Hacer Anterior = Actual

Actual =Actual^.sig Fin Mientras Si (Actual^.inf Valor) Entonces Mostrar "No Existe el Elemento" Si no Si Actual^.sig = Anterior) Entonces Dispose(Anterior) Raiz = Nil Si no Anterior^.sig = Actual^.sig Actual^.sig ^.ant= Anterior Fin si Dispose(Actual) Fin si Fin. /*-----------------------------------------------------------------------PROGRAMA QUE REALIZA LA INSERCION, ELIMINACION Y VISUALIZACION DE DATOS DE UNA LISTA CIRCULAR DOBLEMENTE ENLAZADA ------------------------------------------------------------------------*/ #include #include #include #include struct Nodo { int inf; struct Nodo *sig,*antp; }; struct Nodo *raiz=NULL,*nuevo,*ant,*act,*cab; char opc; void insertar(int p) { nuevo=(struct Nodo*)malloc(sizeof(struct Nodo)); nuevo->inf=p; if(raiz==NULL) { cab=(struct Nodo*)malloc(sizeof(struct Nodo)); raiz=cab; cab->sig=nuevo; nuevo->sig=raiz; nuevo->antp=cab; cab->antp=nuevo; } else { ant=raiz; act=raiz->sig; while((act->infsig; } ant->sig=nuevo; nuevo->sig=act; act->antp=nuevo; nuevo->antp=ant; }

} void eliminar(int dato) { if(raiz==NULL) { printf("\n\tNO EXISTEN DATOS"); getch(); } else { ant=raiz; act=raiz->sig; while((act->inf!=dato)&&(act!=raiz)) { ant=act; act=act->sig; } if(act==raiz) { printf("\n\tdato no existe"); getch(); } else { if(act->sig==ant) { raiz=NULL; free(ant); } else { ant->sig=act->sig; act->sig->antp=ant; } free(act); } } } void imprimir() { act=raiz->sig; clrscr(); gotoxy(30,1);printf("VISUALIZACION DE DATOS"); while(act!=raiz) { printf("\n\t%5d",act->inf); act=act->sig; } getch(); } void main() { int dato; clrscr(); do { clrscr(); printf("\n\t-----------------------\n");

printf("\tLISTAS CIRCULARES DOBLEMENTE ENLAZADAS\n"); printf("\t---------------------------\n"); printf("\t1 ..........INSERTAR \n"); printf("\t2 ..........ELIMINAR \n"); printf("\t3 ...........MOSTRAR \n"); printf("\t4 .............SALIR \n"); printf("\t-----------------------\n"); printf("\tELIJA UNA OPCION-> "); opc=getche(); switch(opc) { case '1': printf("\n\tDATO A ELIMINAR->"); scanf("%d",&dato); insertar(dato); break; case '2': printf("\n\tDATO A ELIMINAR->"); scanf("%d",&dato); eliminar(dato); break; case '3': imprimir(); break; } }while(opc!='4'); } TEMA 4 ARBOLES OBJETIVOS Desarrollar aplicaciones con estructuras ramificadas que optimizan las operaciones básicas. Encontrar nuevas formas de organización de datos en forma ramificada de acuerdo a las características de la aplicación. CONTENIDO 4.1 Introduccion 4.2 Conceptos Asociados 4.3 Arbol Binario de Busqueda 4.4 Arboles Equilibrados o AVL 4.1 DEFINICION. Un árbol es una lista en la que cada uno de sus elementos apunta a uno, ninguno o varios elementos del mismo tipo. Un árbol es una estructura dinámica y homogénea, por tanto está compuesto de elementos del mismo tipo base T, de forma que la estructura puede estar vacía o compuesta por un elemento del tipo base T del que cuelgan un número finito de estructuras árbol similar, a las que llamaremos subárboles, y entre sí son disjuntos. Dos árboles son iguales cuando tengan igual número de nodos, igual contenido en ellos y, además, estén dispuestos de igual manera REPRESENTACIÓN Un árbol es una estructura de datos no lineal que establece una jerarquía entre sus elementos.

Un árbol puede ser representado, en tres formas diferentes:  Matriz de adyacencia.  Lista de adyacencia.

 Estructura dinámica pura. MATRIZ DE ADYACENCIA Es un array [1..n,1..n] OF BOOLEAN donde n es el número de nodos que tiene el árbol y cada posición de la matriz indicará si existe un enlace entre dos nodos. Esto es normal hacerlo en lenguajes en que no pueden crearse componentes dinámicamente, y referenciales por medio de punteros. Ejemplo:

LISTA DE ADYACENCIA Es una tabla de 1 a n, siendo n el número de nodos, donde cada elemento de la tabla representa cada uno de los nodos del árbol y de cada uno de los elementos sale un puntero a una lista que está formada por todos los nodos que están enlazados con él. Ejemplo:

ESTRUCTURA DINÁMICA PURA En ella, todos los componentes o nodos, se representan por punteros explícitos. Vamos a tener un tipo como: PNodo=^Nodo; Nodo=RECORD Info:Tinfo; Enlace1,..., EnlaceN: PNodo; END;

4.2 CONCEPTOS ASOCIADOS A continuación veremos algunos conceptos asociados a los Arboles. Nodo: Cada uno de los elementos de un árbol. Rama: Es cada uno de los enlaces que existe entre los nodos de un árbol. Raíz: Es aquel nodo que no tiene antecesores, es decir, todos son descendientes directos o indirectos de el. Subárbol: Se llama subárbol de raíz m al conjunto de nodos que dependen directa o indirectamente de él, así como al propio m.

Antecesor o padre: Es un nodo del que cuelga algún otro, llamado descendiente o hijo.

Grado de un nodo: Es el número de descendientes directos que tiene, el grado de un árbol es igual al del nodo con mayor grado.

Nivel : Es el número de ramas que hay que recorrer para llegar a él desde la raíz, tendiendo en cuenta que la raíz tiene nivel uno.

Nodo terminal u hoja: Es aquel que tiene grado cero, es decir, que no tiene ningún descendiente. Nodo interno: Es aquel que no es hoja. Longitud de camino interno de un arbol: Es la suma de las longitudes de camino de todos sus nodos.

LCI = 1+2+2+3+3+3 = 14 Longitud de camino externa: Es la suma de las longitudes de camino de todos los Nodos Especiales (Es aquel que se hace colgar de aquellos nodos que no tienen completo el cupo de descendientes).

LCE = 3+4+4+4+4+4+4 = 27 Longitud de camino interna media: Es la longitud de camino interna partido del número de nodos 4.3 ARBOL BINARIO Los árboles de grado 2 tienen una especial importancia. Se les conoce con el nombre de árboles binarios. Se define un árbol binario como un conjunto finito de elementos (nodos) que bien está vació o está formado por una raíz con dos árboles binarios disjuntos, llamados subárbol izquierdo y derecho de la raíz. En los apartados que siguen se considerarán únicamente árboles binarios y, por lo tanto, se utilizará la palabra árbol para referirse a árbol binario. Los árboles de grado superior a 2 reciben el nombre de árboles multicamino. DEFINICION Los árboles binarios se utilizan frecuentemente para representar conjuntos de datos cuyos elementos se identifican por una clave única.

Si el árbol está organizado de tal manera que la clave de cada nodo es mayor que todas las claves del subárbol izquierdo, y menor que todas las claves del subárbol derecho se dice que este árbol es un árbol binario de búsqueda. Ejemplo:

OPERACIONES BASICAS Una tarea muy común a realizar con un árbol es ejecutar una determinada operación con cada uno de los elementos del árbol. Esta operación se considera entonces como un parámetro de una tarea más general que es la visita de todos los nodos o, como se denomina usualmente, del recorrido del árbol. Si se considera la tarea como un proceso secuencial, entonces los nodos individuales se visitan en un orden específico, y pueden considerarse como organizados según una estructura lineal. Existen dos formas básicas de recorrer un árbol: o Recorrido en amplitud. o Recorrido en profundidad. RECORRIDO EN AMPLITUD. En el recorrido por amplitud se visitan los nodos por niveles. Para ello se utiliza una estructura auxiliar tipo "cola" en la que después de mostrar el contenido del nodo, empezando por el nodo raíz, se almacenan los punteros correspondientes a sus hijos izquierdo y derecho. De esta forma si recorremos los nodos de una nivel, mientras mostramos su contenido, almacenamos en la cola los punteros a todos los nodos del nivel siguiente.

Resultado: 20, 10, 30, 5, 15, 25 El algoritmo: Amplitud Inicio Ptr = raiz Cima = 1 Inicio=1 Pila[cima] = Ptr Mientras Iniciocima) cima=inicio=0; printf("%3d",ptr->inf); if(ptr->izq!=NULL) { cima++; cola[cima]=ptr->izq; } if(ptr->der!=NULL) { cima++; cola[cima]=ptr->der; } if((cima>0)&&(inicio==0)) inicio=1; } } //Insertar datos al arbol void insertar(PNodo *&ptr,int dato) { if(ptr==NULL) { ptr=(struct PNodo*)malloc(sizeof(PNodo)); ptr->inf=dato; ptr->izq=NULL; ptr->der=NULL; } else {

if(datoinf) insertar(ptr->izq,dato); else insertar(ptr->der,dato); } } //programa principal void main(void) { int r; clrscr(); randomize(); for(int i=0;iinf); preordenr( p->izq);

preordenr( p->der); } } //Recorrido preorden no recursivo void preorden(PNodo *ptr) { PNodo *pila[15]; int cima; cima=0; pila[cima]=NULL; while(ptr != NULL) { printf("%3d",ptr->inf); if(ptr->der != NULL) { cima++; pila[cima]=ptr->der; } if(ptr->izq != NULL) ptr=ptr->izq; else { ptr=pila[cima]; cima--; } } } //Insertar datos al arbol void insertar(PNodo *&ptr,int dato) { if(ptr==NULL) { ptr=(struct PNodo*)malloc(sizeof(PNodo)); ptr->inf=dato; ptr->izq=NULL; ptr->der=NULL; } else { if(datoinf) insertar(ptr->izq,dato); else insertar(ptr->der,dato); } } //programa principal void main(void) { int r; clrscr(); randomize(); for(int i=0;iizq); printf("%3d",p->inf); InOrdenr( p->der); } } //Recorrido InOrden no recursivo void InOrden(PNodo *ptr) { PNodo *pila[15]; int cima,td; cima=0; pila[cima]=NULL; while(ptr != NULL) { while(ptr != NULL) { cima++; pila[cima]=ptr; ptr=ptr->izq; } ptr=pila[cima]; cima--; td=0; while(ptr != NULL && !td) { printf("%3d",ptr->inf); if(ptr->der != NULL) { ptr=ptr->der; td=1; } else

{ ptr=pila[cima]; cima--; } } } } //Insertar datos al arbol void insertar(PNodo *&ptr,int dato) { if(ptr==NULL) { ptr=(struct PNodo*)malloc(sizeof(PNodo)); ptr->inf=dato; ptr->izq=NULL; ptr->der=NULL; } else { if(datoinf) insertar(ptr->izq,dato); else insertar(ptr->der,dato); } } //programa principal void main(void) { int r; clrscr(); randomize(); for(int i=0;i 0 entonces Mostrar prt^.inf Ptr = pila[cima] Cima = cima-1 Sino Ptr = -ptr Salir=true Fin_si Fin_mientras Fin_mientras Fin.

Codigo fuente Recorrido en PostOrden #include #include #include struct PNodo{ int inf; struct PNodo *izq,*der;

}; PNodo *raiz=NULL; //Recorrido PostOrden recursivo void PostOrdenr(PNodo *p) { if(p != NULL) { PostOrdenr( p->izq); PostOrdenr( p->der); printf("%3d",p->inf); } } //Recorrido PostOrden no recursivo void PostOrden(PNodo *ptr) { struct tpila { int posi; PNodo *punt; } pila[15]; int cima,signo; cima=0; pila[cima].punt=NULL; pila[cima].posi=0; while(ptr != NULL) { while(ptr != NULL) { cima++; pila[cima].punt=ptr; pila[cima].posi=1; if(ptr->der != NULL) { cima++; pila[cima].punt=ptr->der; pila[cima].posi=-1; } ptr=ptr->izq; } ptr=pila[cima].punt; signo=pila[cima].posi; cima--; while(ptr != NULL && signo>0) { printf("%3d",ptr->inf); ptr=pila[cima].punt; signo=pila[cima].posi; cima--; } } } //Insertar datos al arbol void insertar(PNodo *&ptr,int dato) { if(ptr==NULL)

{ ptr=(struct PNodo*)malloc(sizeof(PNodo)); ptr->inf=dato; ptr->izq=NULL; ptr->der=NULL; } else { if(datoinf) insertar(ptr->izq,dato); else insertar(ptr->der,dato); } } //programa principal void main(void) { int r; clrscr(); randomize(); for(int i=0;i raiz^.inf entonces Insertar (raiz^.der,dato) Sino Insertar(raiz^.izq,dato) Fin_si Fin_si Fin. ELIMINAR UN DATO Existen varios casos de eliminación en un arbol binario de búsqueda: Nodo que no tiene hijos:

Solución : Ptr = nil Nodo que tiene un hijo:

Solución : Ptr = ptr^.der

Solución : Ptr = ptr^.izq Nodo que tiene dos hijos: En este caso existen dos posibilidades de reemplazo, donde el nodo a ser eliminado puede ser reemplazado por los siguientes nodos: caso a).- El nodo del subárbol izquierdo que se encuentra mas a la derecha

caso b).- El nodo del subárbol derecho que se encuentra mas a la izquierda

ALGORITMO : Eliminar(dato:integer;Var ptr:pnodo) Inicio

Si ptr = nil entonces Mostrar "no existe el elemento" Sino Si dato > ptr^.inf entoces Eliminar (dato,ptr^.der) Sino Si dato < ptr^.inf entonces Eliminar(dato,ptr^.izq) Sino Aux =ptr Si aux^.izq=nil entonces Ptr =aux^.der Sino Si aux^.der = nil entonces Ptr=aux^.izq Sino Reemplazar(aux^.izq) Fin_si Fin_si Free(aux) Fin_si Fin_si Fin_si Fin. Reemplazar(Var Ader:pnodo) Inicio Si Ader^.der nil entonces Reeplazar(Ader^.der) Sino Ptr^.inf = Ader^.inf aux = Ader Ader = Ader^.izq Fin_si Fin. Codigo fuente ABB usando punteros (recursivo) #include #include #include typedef int TipoDato; /* Vamos a guardar enteros */ typedef struct t { /* El tipo base en sí: */ TipoDato dato; /* - un dato */ struct t* hijoIzq; /* - puntero a su hijo izquierdo */ struct t* hijoDer; /* - puntero a su hijo derecho */ } TipoBase; typedef TipoBase* puntero; /* El puntero al tipo base */ void Escribir(puntero punt) { if (punt) /* Si no hemos llegado a una hoja */ { Escribir(punt->hijoIzq); /* Mira la izqda recursivamente */ printf("%d ",punt->dato); /* Escribe el dato del nodo */ Escribir(punt->hijoDer); /* Y luego mira por la derecha */ } }

void Insertar(puntero* punt, TipoDato valor) { puntero actual= *punt; if (actual == NULL) /* Si hemos llegado a una hoja */ { *punt = (puntero) malloc (sizeof(TipoBase)); /* Reserv. memoria */ actual= *punt; actual->dato = valor; /* Guardamos el dato */ actual->hijoIzq = NULL; /* No tiene hijo izquierdo */ actual->hijoDer = NULL; /* Ni derecho */ } else /* Si no es hoja */ if (actual->dato > valor) /* Y encuentra un dato mayor */ Insertar(&actual->hijoIzq, valor); /* Mira por la izquierda */ else /* En caso contrario (menor) */ Insertar(&actual->hijoDer, valor); /* Mira por la derecha */ } //Buscar el nodo puntero aux; void buscar(puntero *ptr1) { if((*ptr1)->hijoDer!=NULL) buscar(&(*ptr1)->hijoDer); else { aux->dato = (*ptr1)->dato; aux = *ptr1; (*ptr1) = (*ptr1)->hijoIzq; } } //Eliminacion de nodos void eliminar(puntero *ptr,int dato) { if(ptr==NULL) printf("El dato no existe"); else { if(dato < (*ptr)->dato) eliminar(&(*ptr)->hijoIzq,dato); else { if(dato>(*ptr)->dato) eliminar(&(*ptr)->hijoDer,dato); else { aux=*ptr; if((*ptr)->hijoDer==NULL) (*ptr)=(*ptr)->hijoIzq; else if((*ptr)->hijoIzq==NULL) (*ptr)=(*ptr)->hijoDer; else buscar(&aux->hijoIzq); free(aux); } }

} } /* Cuerpo del programa */ int main() { puntero arbol = NULL; clrscr(); Insertar(&arbol, 5); Insertar(&arbol, 3); Insertar(&arbol, 7); Insertar(&arbol, 2); Insertar(&arbol, 4); Insertar(&arbol, 8); Insertar(&arbol, 9); printf("Recorrido InOrden :"); Escribir(arbol); eliminar(&arbol, 9); printf("\nEliminar 9: "); Escribir(arbol); eliminar(&arbol, 4); printf("\nEliminar 4: "); Escribir(arbol); getch(); return 0; } Codigo fuente ABB usando variables de referencia (recursivo) #include #include #include struct nodo { int inf; nodo *izq,*der; }; typedef nodo *pnodo; pnodo raiz=NULL; void preorden(pnodo p) { if(p!= NULL) { printf("%d ",p->inf); preorden( p->izq); preorden( p->der); } } void mostrar(pnodo p) { if(p!= NULL) { mostrar( p->izq); printf("%d ",p->inf); mostrar( p->der); } } void postorden(pnodo p) { if(p!= NULL)

{ postorden( p->izq); postorden( p->der); printf("%d ",p->inf); } } void insertar(pnodo &p, int dato) { if(p == NULL) { p=(pnodo) malloc(sizeof(struct nodo)); p->izq=NULL; p->der=NULL; p->inf=dato; } else { if( dato < p->inf ) insertar(p->izq,dato); else insertar(p->der,dato); } } //Buscar el nodo pnodo aux; void buscar(pnodo &ptr1) { if(ptr1->der!=NULL) buscar(ptr1->der); else { aux->inf=ptr1->inf; aux=ptr1; ptr1=ptr1->izq; } } //Eliminacion de nodos void eliminar(pnodo &ptr,int dato) { if(ptr==NULL) printf("El dato no existe"); else { if(datoinf) eliminar(ptr->izq,dato); else { if(dato>ptr->inf) eliminar(ptr->der,dato); else { aux=ptr; if(ptr->der==NULL) ptr=ptr->izq; else if(ptr->izq==NULL) ptr=ptr->der;

else buscar(aux->izq); free(aux); } } } } void main(void) { clrscr(); insertar(raiz,5); insertar(raiz,15); insertar(raiz,1); insertar(raiz,6); insertar(raiz,12); insertar(raiz,16); insertar(raiz,13); printf("\nRecorrido PreOrden\n"); preorden(raiz); printf("\nRecorrido InOrden\n"); mostrar(raiz); printf("\nRecorrido PostOrden\n"); postorden(raiz); eliminar(raiz,1); printf("\n\nEliminar(1) : "); mostrar(raiz); eliminar(raiz,15); printf("\n\nEliminar(15) : "); mostrar(raiz); eliminar(raiz,16); printf("\n\nEliminar(6) : "); mostrar(raiz); eliminar(raiz,13); printf("\n\nEliminar(13) : "); mostrar(raiz); getch(); } Codigo fuente ABB (sin recursividad) #include #include #include #define TRUE 1 #define FALSE 0 /* Estructuras y tipos */ typedef struct _nodo { int dato; struct _nodo *derecho; struct _nodo *izquierdo; } tipoNodo; typedef tipoNodo *pNodo; typedef tipoNodo *Arbol; /* Comprobar si un Arbol es vacio */ int Vacio(Arbol r) { return r==NULL; } /* Comprobar si un nodo es hoja */

int EsHoja(pNodo r) { return !r->derecho && !r->izquierdo; } /* Insertar un dato en el Arbol ABB */ void Insertar(Arbol *a, int dat) { pNodo padre = NULL; pNodo actual = *a; /* Buscar el dato en el Arbol, manteniendo un puntero al nodo padre */ while(!Vacio(actual) && dat != actual->dato) { padre = actual; if(dat < actual->dato) actual = actual->izquierdo; else if(dat > actual->dato) actual = actual->derecho; } /* Si se ha encontrado el elemento, regresar sin insertar */ if(!Vacio(actual)) return; /* Si padre es NULL, entonces el Arbol estaba vacio, el nuevo nodo será el nodo raiz */ if(Vacio(padre)) { *a = (Arbol)malloc(sizeof(tipoNodo)); (*a)->dato = dat; (*a)->izquierdo = (*a)->derecho = NULL; } /* Si el dato es menor que el que contiene el nodo padre, lo insertamos en la rama izquierda */ else if(dat < padre->dato) { actual = (Arbol)malloc(sizeof(tipoNodo)); padre->izquierdo = actual; actual->dato = dat; actual->izquierdo = actual->derecho = NULL; } /* Si el dato es mayor que el que contiene el nodo padre, lo insertamos en la rama derecha */ else if(dat > padre->dato) { actual = (Arbol)malloc(sizeof(tipoNodo)); padre->derecho = actual; actual->dato = dat; actual->izquierdo = actual->derecho = NULL; } } /* Eliminar un elemento de un Arbol ABB */ void Borrar(Arbol *a, int dat) { pNodo padre = NULL; pNodo actual; pNodo nodo; int aux; actual = *a; /* Mientras sea posible que el valor esta en el Arbol */ while(!Vacio(actual)) { if(dat == actual->dato) { /* Si el valor esta en el nodo actual */ if(EsHoja(actual)) { /* Y si ademas es un nodo hoja: lo borramos */ if(padre) /* Si tiene padre (no es el nodo raiz) */ /* Anulamos el puntero que le hace referencia */ if(padre->derecho == actual) padre->derecho = NULL; else if(padre->izquierdo == actual) padre->izquierdo = NULL;

free(actual); /* Borrar el nodo */ actual = NULL; return; } else { /* Si el valor esta en el nodo actual, pero no es hoja */ padre = actual; /* Buscar nodo mas izquierdo de rama derecha */ if(actual->derecho) { nodo = actual->derecho; while(nodo->izquierdo) { padre = nodo; nodo = nodo->izquierdo; } } /* O buscar nodo mas derecho de rama izquierda */ else { nodo = actual->izquierdo; while(nodo->derecho) { padre = nodo; nodo = nodo->derecho; } } /* Intercambiar valores de no a borrar u nodo encontrado y continuar, cerrando el bucle. El nodo encontrado no tiene por qué ser un nodo hoja, cerrando el bucle nos aseguramos de que solo se eliminan nodos hoja. */ aux = actual->dato; actual->dato = nodo->dato; nodo->dato = aux; actual = nodo; } } else { /* Todavia no hemos encontrado el valor, seguir buscándolo */ padre = actual; if(dat > actual->dato) actual = actual->derecho; else if(dat < actual->dato) actual = actual->izquierdo; } } } /* Recorrido de Arbol en inorden */ void InOrden(Arbol a) { if(a->izquierdo) InOrden(a->izquierdo); printf("%d, ",a->dato); if(a->derecho) InOrden(a->derecho); } /* Recorrido de árbol en preorden void PreOrden(Arbol a) { printf("%d, ",a->dato); if(a->izquierdo) PreOrden(a->izquierdo); if(a->derecho) PreOrden(a->derecho); } /* Recorrido de árbol en postorden */ void PostOrden(Arbol a) { if(a->izquierdo) PostOrden(a->izquierdo);

*/

if(a->derecho) PostOrden(a->derecho); printf("%d, ",a->dato); } /* Programa de ejemplo */ int main() { Arbol ArbolInt=NULL; int altura; int nnodos; clrscr(); /* Insercion de nodos en Arbol: */ Insertar(&ArbolInt, 10); Insertar(&ArbolInt, 5); Insertar(&ArbolInt, 12); Insertar(&ArbolInt, 4); Insertar(&ArbolInt, 1); Insertar(&ArbolInt, 15); Insertar(&ArbolInt, 18); Insertar(&ArbolInt, 16); /* Mostrar el Arbol en tres ordenes distintos: */ printf("InOrden: "); InOrden(ArbolInt); printf("\n"); printf("PreOrden: "); PreOrden(ArbolInt); printf("\n"); printf("PostOrden: "); PostOrden(ArbolInt); printf("\n"); printf("\n"); Borrar(&ArbolInt, 1); printf("Borrar 1: "); InOrden(ArbolInt); printf("\n"); Borrar(&ArbolInt, 4); printf("Borrar 4: "); InOrden(ArbolInt); printf("\n"); Borrar(&ArbolInt, 15); printf("Borrar 15: "); InOrden(ArbolInt); printf("\n"); getch(); return 0; } BÚSQUEDA DE DATOS La búsqueda de un nodo comienza en el nodo raíz y sigue estos pasos: · La clave buscada se compara con la clave del nodo raíz. · Si las claves son iguales, la búsqueda se detiene, o si el subárbol esta vació. · Si la clave buscada es mayor que la clave raíz, la búsqueda se reanuda en el subárbol derecho. · Si la clave buscada es menor que la clave raíz, la búsqueda se reanuda en el subárbol izquierdo. 4.4 ÁRBOLES EQUILIBRADOS O AVL Es una suavización de las restricciones para formar árboles perfectamente equilibrados. Un árbol AVL (llamado así por las iniciales de sus inventores: Adelson-Velskii y Landis) es un árbol binario de búsqueda en el que para cada nodo, las alturas de sus subárboles izquierdo y derecho no difieren en más de 1. El algoritmo para mantener un árbol AVL equilibrado se basa en reequilibrados locales, de modo que no es necesario explorar todo el árbol después de cada inserción o borrado.

DEFINICIÓN La definición no sólo es simple, sino que además conduce a un procedimiento de reequilibrado relativamente sencillo, y a una longitud de camino media prácticamente idéntica a la del árbol perfectamente equilibrado. En un árbol AVL, se pueden realizar en O(lon n) unidades de tiempo, incluso en el peor de los casos, las siguientes operaciones: Encontrar un nodo con una clave dada. Insertar un nodo con una clave dada. Borrar un nodo con una clave dada. Vamos a añadir un campo nuevo a la declaración del tipo Tarbol, que se llamará factor de equilibrio (equ), este factor de equilibrio (equ) es la diferencia entre las alturas del árbol derecho y el izquierdo: FE = altura subárbol derecho - altura subárbol izquierdo; Por definición, para un árbol AVL, este valor debe ser -1, 0 ó 1. Condiciones para variar equ: o La inserción se hace en las hojas o Cuando creamos un nuevo nodo el campo de equilibrio vale 0, ya que la altura del subárbol izquierdo es igual que la del derecho. o Cuando insertamos por la derecha incrementamos el valor del campo de equilibrio. o Cuando insertamos por la izquierda decrementamos el valor del campo de equilibrio. INSERCIÓN EN AVL La inserción se hace siempre en las hojas, y vamos a tener un campo de equilibrio, además una variable global llamada crecido de tipo BOOLEAN, que en el momento de insertar lo vamos a poner a TRUE para después controlar si se ha desequilibrado o no. Se sube restando o sumando 1 hasta llegar a un cero. Si a la hora de sumar o restar un uno se sale del rango hay que reequilibrar. DESEQUILIBRIOS Y REEQUILIBRIOS Al insertar un nuevo nodo en un árbol equilibrado se pueden producir desequilibrios, que quedarán representados en los casos mostrados a continuación. En las figuras, las cajas rectangulares representan subárboles, y la altura añadida por la inserción se indica con cruces. Simples transformaciones restauran el equilibrio deseado. DESEQUILIBRIO IZQUIERDA - IZQUIERDA SIMPLE. Dado el siguiente Arbol Balanceado, donde se observa que el nodo B ya se encuentra crecido en 1 nivel en su subarbol izquierdo.

Se añade al subarbol izquierdo del nodo A un nuevo dato "X", lo que causa un desequilibrio en el arbol, ya que el subarbol izquierdo del nodo B se encuentra crecido ahora en 2 niveles.

Se corrige con la rotación izquierda-izquierda simple, que consiste en subir el nodo A, que tendrá al final el campo de equilibrio a 0.

DESEQUILIBRIO DERECHA -DERECHA SIMPLE. Se reequilibra con rotación derecha-derecha simple. Es el reflejado del anterior.

La figura muestra la situación antes y después de la rotación simple, donde el elemento X fue insertado en E, y b corresponde al nodo N. Antes de la inserción, la altura de N es la altura de C+1. Idealmente, para recuperar la condición de balance se necesitaria bajar A en un nivel y subir E en un nivel, lo cual se logra cambiando las referencias derecha de b e izquierda de d, quedando este último como nueva raíz del árbol, N'. Nótese que ahora el nuevo árbol tiene la misma altura que antes de insertar el elemento, C+1, lo cual implica que no puede haber nodos desbalanceados más arriba en el árbol, por lo que es necesaria una sola rotación simple para devolver la condición de balance al árbol. Nótese también que el árbol sigue cumpliendo con la propiedad de ser ABB.

DESEQUILIBRIO IZQUIERDO-DERECHO COMPUESTO Dado el siguiente Arbol Balanceado, donde se observa que el nodo B ya se encuentra crecido en 1 nivel en su subarbol izquierdo.

Se añade al subarbol derecho del nodo A un nuevo dato "X", lo que causa un desequilibrio en el arbol, ya que el subarbol izquierdo del nodo B se encuentra crecido ahora en 2 niveles.

Se corrige con la rotación izquierda-derecha compuesta. Se sube el nodo C que pasa a tener el campo de equilibrio a 0.

DESEQUILIBRIO DERECHO - IZQUIERDO COMPUESTO

Se reequilibra con rotación derecha-izquierdo compuesto, es el reflejado del anterior.

Para el caso de la figura, la altura de N antes de la inserción era G+1. Para recuperar el balance del árbol es necesario subir C y E y bajar A, lo cual se logra realizando dos rotaciones simples: la primera entre d y f, y la segunda entre d, ya rotado, y b, obteniéndose el resultado de la figura. A este proceso de dos rotaciones simples se le conoce como rotación doble, y como la altura del nuevo árbol N' es la misma que antes de la inserción del elemento, ningún elemento hacia arriba del árbol queda desbalanceado, por lo que solo es necesaria una rotación doble para recuperar el balance del árbol después de la inserción. Nótese que el nuevo árbol cumple con la propiedad de ser ABB después de la rotación doble.

EJEMPLO DE INSERCIONES DE NODOS

A continuación se simulan las inserciones de nodos en un árbol de búsqueda equilibrado, partiendo del árbol vació. Por comodidad se supone que el campo clave es entero. El factor de equilibrio actual de un nodo y el nuevo al añadir un nodo se representan como superíndices de los nodos. Los punteros n, nl y n2 referencia al nodo que viola la condición de equilibrio y a los descendientes en el camino de búsqueda.

Insertar las claves 68-45-29:

Una vez insertado el nodo con la clave 29, al regresar por el camino de búsqueda cambia los factores de equilibrio, así el del nodo 45 pasa a -1, y en el nodo 68 se pasa a -2. Se ha roto el criterio de equilibrio y debe de reestructurarse. Al ser los factores de equilibrio -l y -2 deben de realizarse una rotación de los nodos II para rehacer el equilibrio. Los movimientos de los punteros para realizar esta rotación II n^.izqdo = n1^.drcho n1^.drcho = n n = n1

Realizada la rotación, los factores de equilibrio serán siempre O en las rotaciones simples. El árbol queda de la forma siguiente:

Inserción de las claves 75 y 90

Una vez insertado el nodo con la clave 90, a la derecha del nodo 75, y regresar por el camino de búsqueda para así calcular los nuevos factores de equilibrio, se observa que dicho factor queda incrementado en 1 pues la inserción ha sido por la derecha. En el nodo con clave 68 queda roto el equilibrio. Para reestructurar se realiza una rotación DL Los movimientos de los punteros para realizar esta rotación DD:

n^.Drcho = n1^. Izqdo n1^.Izqdo = n n = n1 Una vez realizada la rotación, los factores de equilibrio de los nodos implicados será 0, como ocurre en todas las rotaciones simples, el árbol queda como sigue:

Insertamos la clave 70

para insertar el nodo con la clave 70 se sigue el camino : Derecha de 45 izquierda de 75 y se inserta por la derecha al nodo 68. al regresar por el camino de búsqueda. Queda, los factores de equilibrio se incrementan en 1 si se fue por la rama derecha, se decrementa en ¡ si se fue por la rama izquierda. En el nodo 45 cl balanceo se ha roto. La rotación de los nados para reestablecer el equilibrio es DI. Los movimientos de los punteros para realizar esta rotación DI n1^. Izqdo = n2^. Drcho n1^. Drcho = n1 n^. Drcho = n2^. Izqdo n2^. Izqdo = n n = n2

Los factores de equilibrio de los nodos implicados en la rotación dependen del valor antes de la inserción del nodo referenciado por n2 según esta tabla: si

n2 ^.fe= -1 n2^.fe = 0

n2^. fe =1

n^.fe

0

0

-1

n1^.fe

1

0

0

n2^.fe

0

0

0

Con esta rotación el árbol quedaría

Inserción de la clave 34

El camino seguido para insertar el nodo con clave 34 ha seguido el camino de izquierda dc 68, izquierda de 45, derecha de 29. Al regresar por el camino de búsqueda, el factor de equilibrio del nodo 29 se incrementa en 1 por seguir el camino de la rama derecha, el del nodo 45 se decrementa en 1 por seguir la rama izquierda y pasa a ser -2, se ha roto el criterio de equilibrio. La rotación de los nodos para reestablecer el equilibrio es ID. Los movimientos de los punteros para realizar esta rotación ID n1^. Drcho = n2^.Izqdo n2^. Izgdo = nl n^. Izgdo = n2^.Drcho n2^. Drcho = n n = n2

Los factores de equilibrio de los nodos implicados en la rotación dependen del valor antes de la inserción del nodo referenciado por n2, según esta tabla: si n^.fe

n2 ^.fe= -1 n2^.fe = 0 1 0

n2^. fe =1 0

n1^.fe

0

0

-1

n2^.fe

0

0

0

Con esta rotación el árbol quedaría

IMPLEMENTACIÓN DE LA INSERCIÓN

Un algoritmo que inserte y reequilibre dependerá en forma crítica de la forma en que se almacene la información relativa al equilibrio del árbol. Una solución consiste en mantener la información sobre el equilibrio, de forma completamente implícita, en la estructura misma del árbol. En este caso, sin embargo, el factor de equilibrio debe "averiguarse" cada vez que se encuentre afectado por una inserción, con el consiguiente trabajo resultante. Otra forma (la que vamos a usar) consiste en atribuir a, y almacenar con, cada nodo un factor de equilibrio explícito. La definición de nodo se amplía entonces a: TYPE Nodo=RECORD Clave:Tclave; Info:Tinfo; Izq,Der:^Nodo; Equi:-1..1 END

El proceso de inserción de un nodo consta fundamentalmente de las tres partes consecutivas siguientes: · Seguir el camino de búsqueda hasta que se comprueba que la clave aún no está en el árbol. · Insertar el nuevo nodo y determinar el factor de equilibrio resultante. · Volver siguiendo el camino de búsqueda y comprobar el factor de equilibrio de cada nodo. Aunque este método realiza algunas comprobaciones que son redundantes, una vez que se establece el equilibrio, éste no necesita comprobarse en los antecesores del nodo en cuestión. En cada paso se debe mandar información sobre si la altura del subárbol (en el cual se ha realizado la inserción) ha aumentado o no. Por lo tanto se extiende la lista de parámetros del procedimiento con el BOOLEAN Crecido, que debe ser un parámetro de tipo variable, ya que se utiliza para transmitir un resultado.

procedure insertar (dato : word; var p : pnodo; var h : boolean); var p1,p2:puntero; begin if p = nil then begin new (p); h:= true; with p^ do begin inf:= dato; izq:= nil; der:= nil; equi:= 0; contador:= 1; end; end else if dato < P^.Inf Then begin Insertar (dato,p^.Izq,h); if h then {la rama izq. ha crecido} case p^.equi of +1: begin p^.equi:=0; h:= false; end; 0: p^.equi:=-1; -1: begin {reequilibrar} p1 := p^.izq; if pl^.equi = -1 then begin { rotacion II simple } p^.izq := pl^.der; p1^.der:=p; p^.equi=0; p := p1; end else begin { rotación ID doble} p2 := p1^.der; p1^.der := p2^.izq; p2^.izq := p1; p^.izq := p2^.der; p2^.der := p; if p2^.equi = -1 then p^.equi := +1 else p^.equi :=0; if p2^.equi = +1 then p1^.equi := -1 else p1^.equi :=0; p := p2; end; p^.equi := 0; h := false; end; end end else if dato > p^.inf then begin insertar (dato, p^.der, h); if h then {la rama derecha ha crecido} case p^.equi of

-1: begin p^.equi:=0; h:=false; end; 0: p^.equi :=+1; +1: begin {reequilibrar} p1 := p^.der; if p1^.equi = +1 then begin {rotacion DD simple} p^.der := p1^.izq; p1^.izq := p; p^.equi := 0; p := p1; end else begin { rotación DI doble } p2 := p1^.izq; p1^.izq := p2^.der; p2^.der := p1; p^.der := p2^.izq; p2^.izq := p; if p2^.equi = +1 then p^.equi := -1 else p^.equi := 0; if p2^.equi = -1 then p1^.equi := +1 else p1^.equi := 0; p := p2; end; p^.equi := 0; h := false; end; end end else begin p^.contador := p^.contador +1; h:=false; end; end; end;

BORRADO EN AVL

Vamos a ver solo las distintas posibilidades que se pueden dar al borrar un nodo en el lado derecho. A la izquierda es simétrico.

EQUILIBRIOS Y DESEQUILIBRIOS

CASO 1. RAIZ.

Caso 1.1: Si alguno de los subárboles que salen de la raíz está vació, entonces el otro estará vació o solo tiene un nodo, por ser un árbol equilibrado. Si solo tiene dos nodos se sube el no-borrado hacia arriba. Si solo está el nodo a borrar, el árbol acaba vació. Caso 1.2: Si no hay ningún subárbol vació se sube el nodo de más a la derecha del subárbol izquierdo, se intercambia los valores de la raíz por los de ese nodo, y después es borra este último.

CASO 2. BORRADO EN EL SUBÁRBOL DERECHO. Caso 2.1: Si el campo de equilibrio tiene un cero, los dos subárboles son iguales. Entonces lo borramos y el campo de equilibrio pasa a -1. Caso 2.2: Si tiene un 1, entonces el subárbol derecho tiene una altura más que el izquierdo. Al borrar equilibramos y pasa a ser 0 ya que restamos 1. Se puede haber desequilibrado por la izquierda porque al borrar se ha disminuido en uno la altura del árbol. Caso 2.3: Si tiene un -1, la altura del subárbol izquierdo es mayor que la del derecho. Al borrar en el derecho se rompe el equilibrio, que pasa a -2.Hay tres casos.

Caso 2.3.1

Caso 2.3.2

Caso 2.3.3

Que visto de otra forma, puede ser:

Mediante rotación izquierda-derecha compuesta queda:

Hay otros dos casos, que el bloque 2'2 sea el más pequeño, o que lo sea el 2'1.Tienen altura N-2 y por lo demás se tratan igual.

EJEMPLO DE BORRADO DE NODOS

Una vez eliminado el nodo siguiendo los criterios establecidos anteriormente, se regresa por el camino de búsqueda calculando los nuevos factores de equilibrio (Fe) de los nodos visitados. Si en alguno de los nodos se viola el criterio de equilibrio, debe de restaurarse el equilibrio. En el algoritmo de inserción, una vez que era efectuada una rotación el proceso terminaba ya que los nodos antecedentes mantenían el mismo factor de equilibrio. En la eliminación debe de continuar el proceso puesto que se puede producir más de una rotación en el retroceso realizado por el camino de búsqueda, pudiendo llegar hasta la raíz del árbol. En los procedimientos se utiliza el argumento boolean hh, será activado cuando la altura del subárbol disminuya debido a que se haya eliminado un nodo, o bien porque al reestructurar haya quedado reducida la altura del subárbol.

En el árbol de la figura va a ser eliminado el nodo con la clave 42: al ser un nodo hoja el borrado es simple, se suprime el nodo. Al volver por el camino de búsqueda para determinar los Fe, resulta que el Fe del nodo con clave 39 pasaría a ser -2 ya que ha decrementado la altura de la rama derecha, es violado el criterio de equilibrio. Hay que reestructurar el árbol de raíz 39

El árbol resultante es

Rotación ID por que N^.fe ß (1+1) y n1^.fe < 0 El árbol resultado es :

En estos dos ejemplos se observa que después de realizar la eliminación de un nodo, y cuando se regresa por el camino de búsqueda, el factor de equilibrio del nodo visitado disminuye en 1 si la eliminación se hizo por su rama derecha y se incrementa en 1 si la eliminación se hizo por su rama izquierda. Consideremos ahora este árbol equilibrado:

Se elimina el nodo de clave 25. Como es un nodo hoja se suprime. La supresión se hace por la rama izquierda, por lo que la altura de la rama derecha correspondiente aumenta en 1, y lo mismo ocurre con el factor de equilibrio. Los factores de equilibrio quedan:

Rotación DD por que N^.fe = 1+1 N1^.fe >= 1 El árbol resultante es:

Al seguir regresando por el camino de búsqueda, el nodo raíz debe de incrementar su Fe con lo que pasaría a +2, por consiguiente hay que restaurar el árbol, la rotación es derecha-izquierda ya que

N^.fe 1+1 y n1^.fe = 0 then begin if N1^. Fe = 0 then hh;= false; {No disminuye de nuevo la altura} Rotaciondd(N, N1) end else Rotaciondi(N, N1) end; end; end;

En el procedimiento Equilibnar2 al disminuir la altura de la rama derecha, el factor de equilibrio queda decrementado en 1. De producirse una violación del criterio de equilibrio, la rotación será del tipo izquierda-izquierda, o izquierda-derecha. procedure Equilibrar2(var N:Ptrae: var hh; bolean) {hh: activado cuando ha disminuido en altura la rama izquierda del nodo N} var Nl :Ptrae: begin case N^.Fe of 1: N^.Fe := 0; 0: begin N^.Fe := -1; hh:= false end; -1: begin {Hay que restaurar el equilibrio} Nl:= N^.Izqdo; {Es determinado el tipo de rotación} if N1^.Fe R^.info then begin borrar_balanceado(R^.Drcho.,hh ,x) if hh then Equilibrar2(R, Hh) end else begin {ha sido encontrado el nodo} q:= R; if q^.Drcho= nil then begin R:= q^.Izqdo; hh:= true{Disminuye la altura} end else if q ^.Izqdo=nil then begin R:=q^.drcho; hh:= true end else begin bor(q^.Izqdo,hh); if hh then Equilibrar1(R, hh) end; dispose(q); end; end;

Codigo fuente de Arboles AVL #include #include #include typedef int TipoDato; typedef struct t { TipoDato dato; int equi; struct t* hijoIzq; izquierdo */ struct t* hijoDer; */ } TipoBase; typedef

TipoBase* puntero;

/*

Vamos a guardar enteros

/* /*

El tipo base en sí: */ - un dato */

/*

- puntero a su hijo

/*

- puntero a su hijo derecho

/*

*/

El puntero al tipo base */

void Escribir(puntero punt,int fila,int col, int inc) { if (punt) /* Si no hemos llegado a una hoja */ {

gotoxy(fila,col); cprintf("%d
View more...

Comments

Copyright ©2017 KUPDF Inc.
SUPPORT KUPDF