Está en la página 1de 74

TEMA 3 LISTAS ENCADENADAS OBJETIVOS Emplear estructuras dinmicas de datos en el procesamiento de la informacin.

Desarrollar algoritmos para optimizar las operaciones bsicas que se llevan a cabo en proceso de ejecucin en estructuras lineales. CONTENIDO 3.1 Introduccin 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 adicin / eliminacin en estas estructuras de datos, se realiza en tiempo de ejecucin y en forma dinmica.

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 Raz es un puntero al primer elemento de la lista. Cada elemento (nodo) de la lista simplemente enlazada debe tener dos campos: Un campo informacin (info) que contiene el valor de ese elemento y puede contener cualquier tipo estndar o definido por el usuario. Un campo enlace (o puntero) que indica la posicin del siguiente elemento de la lista. NODO

Existe una marca para fin de lista, que es la constante NIL, tambin representada por una barra inclinada o el signo elctrico de tierra. Declaracin de un nodo usando el lenguaje Pascal: Type pnodo = ^nodo nodo = record info : Integer; Sig :pnodo; End; Declaracin 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 bsicas: Insercin Eliminacin Bsqueda 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 = Raz Mientras(Actual <> Nil) hacer Actual =Actual^.sig Mostar Actual^.inf Fin Mientras Fin. BUSQUEDA Para realizar la bsqueda 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 = Raz Mientras(Actual<>Nil) 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 adicin de nodos a una lista simple, de acuerdo a los propsitos de uso de estos, entre las cuales se tiene: Insertar cuando lista est vaca

Insertar al principio Insertar al final Insertar al medio de dos nodo En todas ellas se realiza una tarea comn: 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 bsqueda 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<conio.h> #include<stdio.h> #include<stdlib.h> #include<ctype.h> 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->inf<p)&&(act!=NULL)) { ant=act; act=act->sig; } 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 diseo 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 fcil manejo. OPERACIONES EN LISTAS SIMPLEMENTE ENLAZADAS CIRCULARES Como cualquier estructura de datos, en las listas simplemente enlazadas circulares se puede realizar las siguientes operaciones bsicas: Insercin Eliminacin Bsqueda 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 = Raz^.sig Mientras(Actual <> Raiz) hacer Actual =Actual^.sig Mostarc Actual^.inf Fin Mientras Fin. BUSQUEDA Para realizar la bsqueda 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 = Raz^.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 adicin de nodos a una lista simple circular, de acuerdo a los propsitos de uso de estos. Entre las cuales se tiene: Insertar cuando lista est vaca Insertar al medio de dos nodos En todas ellas se realiza una tarea comn, 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 bsqueda 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<conio.h> #include<stdio.h> #include<stdlib.h> #include<ctype.h> 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->inf<p)&&(act!=raiz)) { ant=act; act=act->sig; } 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 direccin 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 estndar de datos. campo enlace sig. apunta al siguiente nodo de la lista.

Existe una marca para fin de lista, que es la constante NIL, tambin representada por una barra inclinada o el signo elctrico de tierra. Declaracin de un nodo usando el lenguaje Pascal: Type pnodo = ^nodo nodo = record info : Integer; Ant, Sig :pnodo; End; Declaracin de un nodo usando el lenguaje C: Struct nodo { int info; struct nodo *Ant, * Sig; }; OPERACIONES EN LISTAS DOBLEMENTE ENLAZADAS. Las operaciones bsicas para manipular listas doblemente enlazadas son: Insercin Eliminacin Recorrido Bsqueda 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 adicin de nodos a una lista doble, de acuerdo a los propsitos de uso de estos. Entre las cuales se tiene: Insertar cuando lista est vaca Insertar al principio Insertar al final Insertar al medio de dos nodos En todas ellas se realiza una tarea comn, 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 bsqueda 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 ELIMINACIN AL PRINCIPIO: Dada la siguiente lista:

Eliminar(5)

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

1. 2.

Eliminar(8)

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

1. 2.

Eliminar (9)

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<conio.h> #include<stdio.h> #include<stdlib.h> struct Nodo { int inf; struct Nodo *sig,*antp; }; struct Nodo *nuevo,*ant,*act,*inicio,*fin; int dato; char op; void insertar(int dato) {

1. 2.

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->inf<dato)) { ant=act; act=act->sig; } 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 diseo 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 fcil manejo. OPERACIONES EN LISTAS DOBLEMENTE ENLAZADAS CIRCULARES. Las operaciones bsicas para manipular listas doblemente enlazadas son: Insercin Eliminacin

Recorrido Bsqueda

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 insercin de datos a una lista doblemente enlazada circular: Insercin en una lista vaca Insertar al medio de dos nodos INSERCIN 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 bsqueda 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<conio.h> #include<stdio.h> #include<stdlib.h> #include<ctype.h> 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->inf<p)&&(act!=raiz)) { ant=act; act=act->sig; } 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 bsicas. Encontrar nuevas formas de organizacin de datos en forma ramificada de acuerdo a las caractersticas de la aplicacin. 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 dinmica y homognea, por tanto est compuesto de elementos del mismo tipo base T, de forma que la estructura puede estar vaca o compuesta por un elemento del tipo base T del que cuelgan un nmero finito de estructuras rbol similar, a las que llamaremos subrboles, y entre s son disjuntos. Dos rboles son iguales cuando tengan igual nmero de nodos, igual contenido en ellos y, adems, estn dispuestos de igual manera REPRESENTACIN Un rbol es una estructura de datos no lineal que establece una jerarqua entre sus elementos.

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

Estructura dinmica pura. MATRIZ DE ADYACENCIA Es un array [1..n,1..n] OF BOOLEAN donde n es el nmero de nodos que tiene el rbol y cada posicin de la matriz indicar si existe un enlace entre dos nodos. Esto es normal hacerlo en lenguajes en que no pueden crearse componentes dinmicamente, y referenciales por medio de punteros. Ejemplo:

LISTA DE ADYACENCIA Es una tabla de 1 a n, siendo n el nmero 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 estn enlazados con l. Ejemplo:

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

4.2 CONCEPTOS ASOCIADOS A continuacin 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. Raz: Es aquel nodo que no tiene antecesores, es decir, todos son descendientes directos o indirectos de el. Subrbol: Se llama subrbol de raz 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 algn otro, llamado descendiente o hijo.

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

Nivel : Es el nmero de ramas que hay que recorrer para llegar a l desde la raz, tendiendo en cuenta que la raz tiene nivel uno.

Nodo terminal u hoja: Es aquel que tiene grado cero, es decir, que no tiene ningn 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 nmero 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 raz con dos rboles binarios disjuntos, llamados subrbol izquierdo y derecho de la raz. En los apartados que siguen se considerarn 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 subrbol izquierdo, y menor que todas las claves del subrbol derecho se dice que este rbol es un rbol binario de bsqueda. Ejemplo:

OPERACIONES BASICAS Una tarea muy comn a realizar con un rbol es ejecutar una determinada operacin con cada uno de los elementos del rbol. Esta operacin se considera entonces como un parmetro de una tarea ms 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 especfico, y pueden considerarse como organizados segn una estructura lineal. Existen dos formas bsicas 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 despus de mostrar el contenido del nodo, empezando por el nodo raz, 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 Inicio<=cima hacer ptr=Pila[Inicio] inicio++ Mostrar ptr^.inf Si ptr^der <> nil entonces Cima=cima+1 Pila[cima] = ptr^.der Fin_si

Si ptr^izq <> nil entonces Cima=cima+1 Pila[cima] = ptr^.izq Fin_si Fin_mientras Fin. Codigo fuente Recorrido en Amplitud #include<stdio.h> #include<conio.h> #include<stdlib.h> struct PNodo{ int inf; struct PNodo *izq,*der; }; PNodo *raiz=NULL; //Recorrido en Amplitud o Niveles void amplitud(PNodo *ptr) { PNodo *cola[15]; int cima,inicio; cima=inicio=1; cola[cima]=ptr; while(inicio!=0&&cima!=0) { ptr=cola[inicio]; inicio++; if(inicio>cima) 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(dato<ptr->inf) insertar(ptr->izq,dato); else insertar(ptr->der,dato); } } //programa principal void main(void) { int r; clrscr(); randomize(); for(int i=0;i<=10;i++) { r=random(50); insertar(raiz,r); } printf("\nRECORRIDO POR AMPLITUD (NO RECURSIVO):"); amplitud(raiz); getch(); } RECORRIDO EN PROFUNDIDAD. Para visualizar o consultar los datos en un rbol se necesita recorrer el rbol. Al contrario que las listas enlazadas, los rboles no tienen un primer valor, un segundo valor, etc. Existen mtodos de recorrido de un rbol binario, de acuerdo al orden en que se visitan los nodos, de forma que ser preciso elegir cuidadosamente el tipo de recorrido. Segn sea la estrategia a seguir, los recorridos se conocen como: Recorrido inorden Recorrido preorden Recorrido postorden Las tres etapas bsicas en el recorrido de un arbol binario recursivamente son: - Visitar el nodo (Raiz) - Recorrer el subrbol izquierdo (izq.) - Recorrer el subrbol derecho (der.) RECORRIDO PREORDEN Si el rbol no esta vaci, el mtodo implica los siguientes pasos: - Visitar el nodo (Raiz) - Recorrer el subrbol izquierdo (izq.) - Recorrer el subrbol derecho (der.) El algoritmo usando recursividad: preorden (var p:pnodo) inicio si p <>nil entonces escribir p^.inf preorden (p^.izq) preorden (p^.der) fin si fin En el rbol, los nodos se han numerado en el orden que son visitados en el recorrido preorden

Primero se visita el nodo raz A. Despus se visita el subrbol izquierdo. Este consta de los nodos (B, D y E), por lo que siguiendo con el orden (Raiz, izq., der.), se visita primero B, luego D y por ultimo E. Por ultimo se visita el subrbol derecho que consta de los nodos (C, F y G). Siguiendo el orden (Raiz, izq., der.), se visita primero C, luego F y por ultimo G. El recorrido es: A-B-D-E-C-F-G El algoritmo sin usar recursividad: Preorden Inicio Cima = 1 Pila[cima] = nil Ptr = raiz Mientras ptr <> nil hacer Mostrar ptr^.inf Si ptr^der <> nil entonces Cima=cima+1 Pila[cima] = ptr^.der Fin_si Si ptr^.izq <> nil entonces Ptr = ptr^.izq Sino Ptr = pila[cima] Cima=cima-1 Fin_si Fin_mientras Fin. Codigo fuente Recorrido en PreOrden #include<stdio.h> #include<conio.h> #include<stdlib.h> struct PNodo{ int inf; struct PNodo *izq,*der; }; PNodo *raiz=NULL; //Recorrido preorden recursivo void preordenr(PNodo *p) { if(p != NULL) { printf("%3d",p->inf); 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(dato<ptr->inf) insertar(ptr->izq,dato); else insertar(ptr->der,dato); } } //programa principal void main(void) { int r; clrscr(); randomize(); for(int i=0;i<=10;i++) { r=random(50); insertar(raiz,r); } printf("\nRecorrido Preorden (NO RECURSIVO):");

preorden(raiz); printf("\nRecorrido Preorden ( RECURSIVO):"); preordenr(raiz); getch(); } RECORRIDO INORDEN Si el rbol no esta vaci, el mtodo implica los siguientes pasos: - Recorrer el subrbol izquierdo (Izq) - Visitar el nodo (Raiz) - Recorrer el subrbol derecho (Der) El algoritmo usando recursividad: inorden (var p:pnodo) inicio si p <> nil entonces inorden (p^.izq) escribir p^.inf inorden (p^.der) fin si fin En el rbol, los nodos se han numerado en el orden que son visitados en el recorrido enorden.

El primer recorrido es el subrbol izquierdo del nodo raz. Este consta de los nodos (B, D y E), por lo que siguiendo con el orden (Izq, Raiz, Der), se visita primero D, luego B y por ultimo E. Despus se visita el nodo raz A. Por ultimo se visita el subrbol derecho que consta de los nodos (C, F y G). siguiendo el orden (Izq, Raiz, Der), se visita primero F, luego C y por ultimo G. El recorrido es: D-B-E-A-F-C-G El algoritmo sin usar recursividad: Inorden Inicio Cima = 1 Pila[cima]=nil Ptr = raiz Mientras ptr <> nil hacer Mientras ptr <> nil hacer Cima=cima+1 Pila[cima] = ptr Ptr = ptr^.izq Fin_mientras Ptr = pila[cima] Cima=cima-1 Tieneder =false Mientras Ptr <> nil y no tieneder hacer

Mostrar ptr ^.inf Si ptr^.der <> nil entonces Ptr = ptr^.der Tieneder = true Sino Ptr = pila[cima] Cima=cima-1 Fin_si Fin_mientras Fin_mientras Fin.

Codigo fuente Recorrido en InOrden


#include<stdio.h> #include<conio.h> #include<stdlib.h> struct PNodo{ int inf; struct PNodo *izq,*der; }; PNodo *raiz=NULL; //Recorrido InOrden recursivo void InOrdenr(PNodo *p) { if(p != NULL) { InOrdenr( p->izq); 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(dato<ptr->inf) insertar(ptr->izq,dato); else insertar(ptr->der,dato); } } //programa principal void main(void) { int r; clrscr(); randomize(); for(int i=0;i<=10;i++) { r=random(50); insertar(raiz,r); } printf("\nRecorrido InOrden (NO RECURSIVO):"); InOrden(raiz); printf("\nRecorrido InOrden ( RECURSIVO):"); InOrdenr(raiz); getch(); } RECORRIDO POSTORDEN Si el rbol no esta vaci, el mtodo implica los siguientes pasos: - Recorrer el subrbol izquierdo (Izq) - Recorrer el subrbol derecho (Der) - Visitar el nodo (Raiz) El algoritmo usando recursividad: postorden (var p:pnodo) inicio si p <> nil entonces postorden (p^.izq) postorden (p^.der) escribir p^.inf fin si fin En el rbol, los nodos se han numerado en el orden que son visitados en el recorrido preorden.

Primero se visita el subrbol izquierdo. Este consta de los nodos (B, D y E), por lo que siguiendo con el orden (Izq, Der, Raiz), se visita primero D, luego E y por ultimo B. Por ultimo se visita el subrbol derecho que consta de los nodos (C, F y G). siguiendo el orden (Izq, Der, Raiz), se visita primero F, luego G y por ultimo C. Por ultimo se visita el nodo raz A. El recorrido es: D-E-B-F-G-C-A El algoritmo sin usar recursividad: Postorden Inicio Cima = 1 Pila[cima]=nil Ptr = raiz Mientras ptr <> nil hacer Mientras ptr <> nil hacer Cima=cima+1 Pila[cima] = ptr Si ptr^.der <> nil entonces Cima = cima+1 Pila[cima]= -ptr^.der Fin_si Ptr = ptr^.izq Fin_mientras Ptr = pila[cima] Cima = cima-1 Salir = false Mientras ptr <> nil y no salir hacer Si prt > 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<stdio.h> #include<conio.h> #include<stdlib.h> 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(dato<ptr->inf) insertar(ptr->izq,dato); else insertar(ptr->der,dato); } } //programa principal void main(void) { int r; clrscr(); randomize(); for(int i=0;i<=10;i++) { r=random(50); insertar(raiz,r); } printf("\nRecorrido PostOrden (NO RECURSIVO):"); PostOrden(raiz); printf("\nRecorrido PostOrden ( RECURSIVO):"); PostOrdenr(raiz); getch(); } INSERCIN DE DATOS La insercin de datos en un arbol binario de bsqueda, se realiza de acuerdo al valor que se debe insertar, si el dato es menor que la raz es insertada en el subrbol izquierdo, si el dato es mayor que la raz es insertada en el subrbol derecho. Insertar(Var raiz:pnodo;dato:entero) Inicio Si raiz = nil entonces New(raiz) Raiz^.inf = dato Raiz^.izq = nil Raiz^.der = nil Sino Si dato > raiz^.inf entonces Insertar (raiz^.der,dato) Sino Insertar(raiz^.izq,dato) Fin_si Fin_si Fin. ELIMINAR UN DATO Existen varios casos de eliminacin en un arbol binario de bsqueda: Nodo que no tiene hijos:

Solucin : Ptr = nil Nodo que tiene un hijo:

Solucin : Ptr = ptr^.der

Solucin : 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 subrbol izquierdo que se encuentra mas a la derecha

caso b).- El nodo del subrbol 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 <stdio.h> #include <conio.h> #include <stdlib.h> 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<stdio.h> #include<stdlib.h> #include<conio.h> 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(dato<ptr->inf) 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 <stdlib.h> #include <stdio.h> #include <conio.h> #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 buscndolo */ 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; } BSQUEDA DE DATOS La bsqueda de un nodo comienza en el nodo raz y sigue estos pasos: La clave buscada se compara con la clave del nodo raz. Si las claves son iguales, la bsqueda se detiene, o si el subrbol esta vaci. Si la clave buscada es mayor que la clave raz, la bsqueda se reanuda en el subrbol derecho. Si la clave buscada es menor que la clave raz, la bsqueda se reanuda en el subrbol izquierdo. 4.4 RBOLES EQUILIBRADOS O AVL Es una suavizacin 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 bsqueda en el que para cada nodo, las alturas de sus subrboles izquierdo y derecho no difieren en ms 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 despus de cada insercin o borrado.

DEFINICIN La definicin no slo es simple, sino que adems conduce a un procedimiento de reequilibrado relativamente sencillo, y a una longitud de camino media prcticamente idntica 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 aadir un campo nuevo a la declaracin 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 subrbol derecho - altura subrbol izquierdo; Por definicin, para un rbol AVL, este valor debe ser -1, 0 1. Condiciones para variar equ: o La insercin se hace en las hojas o Cuando creamos un nuevo nodo el campo de equilibrio vale 0, ya que la altura del subrbol 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. INSERCIN EN AVL La insercin se hace siempre en las hojas, y vamos a tener un campo de equilibrio, adems una variable global llamada crecido de tipo BOOLEAN, que en el momento de insertar lo vamos a poner a TRUE para despus 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 quedarn representados en los casos mostrados a continuacin. En las figuras, las cajas rectangulares representan subrboles, y la altura aadida por la insercin 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 aade 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 rotacin 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 rotacin derecha-derecha simple. Es el reflejado del anterior.

La figura muestra la situacin antes y despus de la rotacin simple, donde el elemento X fue insertado en E, y b corresponde al nodo N. Antes de la insercin, la altura de N es la altura de C+1. Idealmente, para recuperar la condicin 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 raz del rbol, N'. Ntese 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 ms arriba en el rbol, por lo que es necesaria una sola rotacin simple para devolver la condicin de balance al rbol. Ntese tambin 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 aade 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 rotacin 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 rotacin derecha-izquierdo compuesto, es el reflejado del anterior.

Para el caso de la figura, la altura de N antes de la insercin 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, obtenindose el resultado de la figura. A este proceso de dos rotaciones simples se le conoce como rotacin doble, y como la altura del nuevo rbol N' es la misma que antes de la insercin del elemento, ningn elemento hacia arriba del rbol queda desbalanceado, por lo que solo es necesaria una rotacin doble para recuperar el balance del rbol despus de la insercin. Ntese que el nuevo rbol cumple con la propiedad de ser ABB despus de la rotacin doble.

EJEMPLO DE INSERCIONES DE NODOS

A continuacin se simulan las inserciones de nodos en un rbol de bsqueda 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 aadir un nodo se representan como superndices de los nodos. Los punteros n, nl y n2 referencia al nodo que viola la condicin de equilibrio y a los descendientes en el camino de bsqueda.

Insertar las claves 68-45-29:

Una vez insertado el nodo con la clave 29, al regresar por el camino de bsqueda 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 rotacin de los nodos II para rehacer el equilibrio. Los movimientos de los punteros para realizar esta rotacin II n^.izqdo = n1^.drcho n1^.drcho = n n = n1

Realizada la rotacin, los factores de equilibrio sern siempre O en las rotaciones simples. El rbol queda de la forma siguiente:

Insercin 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 bsqueda para as calcular los nuevos factores de equilibrio, se observa que dicho factor queda incrementado en 1 pues la insercin ha sido por la derecha. En el nodo con clave 68 queda roto el equilibrio. Para reestructurar se realiza una rotacin DL Los movimientos de los punteros para realizar esta rotacin DD:

n^.Drcho = n1^. Izqdo n1^.Izqdo = n n = n1 Una vez realizada la rotacin, 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 bsqueda. 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 rotacin de los nados para reestablecer el equilibrio es DI. Los movimientos de los punteros para realizar esta rotacin 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 rotacin dependen del valor antes de la insercin del nodo referenciado por n2 segn esta tabla: si n^.fe n1^.fe n2^.fe n2 ^.fe= -1 n2^.fe = 0 0 1 0 0 0 0 n2^. fe =1 -1 0 0

Con esta rotacin el rbol quedara

Insercin 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 bsqueda, 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 rotacin de los nodos para reestablecer el equilibrio es ID. Los movimientos de los punteros para realizar esta rotacin 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 rotacin dependen del valor antes de la insercin del nodo referenciado por n2, segn esta tabla: si n^.fe n1^.fe n2^.fe n2 ^.fe= -1 n2^.fe = 0 1 0 0 0 0 0 n2^. fe =1 0 -1 0

Con esta rotacin el rbol quedara

IMPLEMENTACIN DE LA INSERCIN

Un algoritmo que inserte y reequilibre depender en forma crtica de la forma en que se almacene la informacin relativa al equilibrio del rbol. Una solucin consiste en mantener la informacin sobre el equilibrio, de forma completamente implcita, 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 insercin, 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 explcito. La definicin de nodo se ampla entonces a:
TYPE Nodo=RECORD Clave:Tclave; Info:Tinfo; Izq,Der:^Nodo; Equi:-1..1 END

El proceso de insercin de un nodo consta fundamentalmente de las tres partes consecutivas siguientes: Seguir el camino de bsqueda hasta que se comprueba que la clave an no est en el rbol. Insertar el nuevo nodo y determinar el factor de equilibrio resultante. Volver siguiendo el camino de bsqueda y comprobar el factor de equilibrio de cada nodo. Aunque este mtodo realiza algunas comprobaciones que son redundantes, una vez que se establece el equilibrio, ste no necesita comprobarse en los antecesores del nodo en cuestin. En cada paso se debe mandar informacin sobre si la altura del subrbol (en el cual se ha realizado la insercin) ha aumentado o no. Por lo tanto se extiende la lista de parmetros del procedimiento con el BOOLEAN Crecido, que debe ser un parmetro 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 { rotacin 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 { rotacin 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 simtrico.

EQUILIBRIOS Y DESEQUILIBRIOS

CASO 1. RAIZ.

Caso 1.1: Si alguno de los subrboles que salen de la raz 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 ningn subrbol vaci se sube el nodo de ms a la derecha del subrbol izquierdo, se intercambia los valores de la raz por los de ese nodo, y despus es borra este ltimo.

CASO 2. BORRADO EN EL SUBRBOL DERECHO.


Caso 2.1: Si el campo de equilibrio tiene un cero, los dos subrboles son iguales. Entonces lo borramos y el campo de equilibrio pasa a -1. Caso 2.2: Si tiene un 1, entonces el subrbol derecho tiene una altura ms 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 subrbol 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 rotacin izquierda-derecha compuesta queda:

Hay otros dos casos, que el bloque 2'2 sea el ms pequeo, o que lo sea el 2'1.Tienen altura N-2 y por lo dems se tratan igual.

EJEMPLO DE BORRADO DE NODOS

Una vez eliminado el nodo siguiendo los criterios establecidos anteriormente, se regresa por el camino de bsqueda 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 insercin, una vez que era efectuada una rotacin el proceso terminaba ya que los nodos antecedentes mantenan el mismo factor de equilibrio. En la eliminacin debe de continuar el proceso puesto que se puede producir ms de una rotacin en el retroceso realizado por el camino de bsqueda, pudiendo llegar hasta la raz del rbol. En los procedimientos se utiliza el argumento boolean hh, ser activado cuando la altura del subrbol disminuya debido a que se haya eliminado un nodo, o bien porque al reestructurar haya quedado reducida la altura del subrbol.

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 bsqueda para determinar los Fe, resulta que el Fe del nodo con clave 39 pasara 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 raz 39

El rbol resultante es

Rotacin ID por que


N^.fe (1+1) y n1^.fe < 0 El rbol resultado es :

En estos dos ejemplos se observa que despus de realizar la eliminacin de un nodo, y cuando se regresa por el camino de bsqueda, el factor de equilibrio del nodo visitado disminuye en 1 si la eliminacin se hizo por su rama derecha y se incrementa en 1 si la eliminacin 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 supresin 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:

Rotacin DD por que


N^.fe = 1+1 N1^.fe >= 1 El rbol resultante es:

Al seguir regresando por el camino de bsqueda, el nodo raz debe de incrementar su Fe con lo que pasara a +2, por consiguiente hay que restaurar el rbol, la rotacin es derecha-izquierda ya que

N^.fe 1+1 y n1^.fe <0

El nuevo rbol queda as es

IMPLEMENTACIN DE LA ELIMINACIN

En el algoritmo de supresin se introducen dos procedimientos simtricos de equilibrado: Equilibran se invoca cuando la altura de la rama izquierda ha disminuido y Equilibnar2 se invocar cuando la altura de la rama derecha haya disminuido. En el procedimiento Equilibran al disminuirla altura de la rama izquierda, el factor de equilibrio se incrementa en 1. Por lo que de violarse el factor de equilibrio la rotacin que se produce es del tipo derecha-derecha, o derecha-izquierda.
procedure Rotaciondd (var N: Ptrae; Nl: ptrae); begin N^.Drcho:=N1^.Izqdo; N1^. Izgdo:=n; if N1^. Fe= 1 then begin N^.Fe:=0; N1^.Fe:=0 end else begin N^.Fe:= 1; Nl^.Fe:= -1; end, N :=N1; end; procedure Rotaciondi (var N: Ptrae; Nl: Ptrae); var N2 : Ptrae; begin N2:=Nl^.lzqdo; N^.Drcho:=N2^.Izqdo; N2^.Izqdo:=N; Nl^.lzqdo:=N2^.Drcho; N2^.Drcho:=Nl; if (N2^.Fe=l ) then N^.Fe=-l else N^. Fe: =0 if (N2^.Fe=-l) then N1^.Fe:=1 else Nl^.Fe:=0; N2^.Fe:=0; N :=N2;

end; procedure Rotacionii(var N: Ftrae; N1: Ptrae); begin N^.Izqdo:= N1^.Drcho; N1^.Drcho:=N; if N1^.Fe= -1 then begin N^.Fe :=0; N1^.Fe := 0; end else begin N^.Fe :=-1; N1^.Fe := 1 end; N := N1; end; procedure Rotacionid(Vat N:Ptrae; N1:Ptrae) var n2:ptrae; begin N2= N1^.drcho; N^.Izqdo:= N2^.Drcho; N2^.Drcho:= N; N1^.Drcho:= N2^Izqdo; N2^.Izqdo:= Nl; if (N2^.f2=1) then N1^.Fe:=-1 else N1^.Fe:=O; if (N2^.Fe=-l) then N^.Fe:=l else N^..Fe:=O; N2^.Fe:=0; N:= N2; end; procedure Equilibrarl(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:= O; O: begin N^.Fe:= 1; hh:= false end; 1: begin {Hay que restaurar el equilibrio} Nl:= N^.drcho; {Es determinado el tipo de rotacin} if 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 violacin del criterio de equilibrio, la rotacin 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 rotacin} if N1^.Fe <= 0 then begin if N1^.Fe = 0 then hh;= false; {No disminuye de nuevo la altura} Rotacion_ii(N, N1) end else Rotacion_id(N, Nl) end; end; end;

A continuacin son escritos los procedimientos de borrar_balanceado y el procedimiento anidado bor. El algoritmo que sigue es el mismo que el de borrado en los rboles de bsqueda sin criterio de equilibrio. La principal diferencia est en que en el momento que una rama disminuye en altura es llamado el procedimiento respectivo de equilibrar.
procedure borrar_balanceado(var R:Ptrae;var hh:boolean;X: Tipoinfo) var q: Ptrae; procedure bor(var d: ptrae; var hh: boolean); begin if d^.Drcho<>nil then begin bor(d^.Drcho, hh); if hh then {Ha disminuido rama derecha} Equilibrar2 (d,hh) end else begin q^.info:=d^.info; q: =d; d:=d^.Izqdo; hh:= true end; end; begin

if not ArbolVacio( R ) then if x < R^.info then begin borrar_balanceado(R^.lzqdo,hh , x): if hh then Equilibrarl.(R, hh) end else if x>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 <stdio.h> #include <conio.h> #include <stdlib.h> 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<%d",punt->dato,punt->equi); /* Escribe el dato del nodo */ Escribir(punt->hijoIzq,fila-inc,col+2,inc/2); /* Mira la izqda recursivamente */ Escribir(punt->hijoDer,fila+inc,col+2,inc/2); /* Y luego mira por la derecha */ } else { gotoxy(fila,col); cprintf(""); /* Escribe el NULL del nodo */ } } void Insertar(puntero* punt, TipoDato valor,int *crece) { puntero punt1,punt2; puntero actual= *punt; if (actual == NULL) */ { *punt = (puntero) malloc (sizeof(TipoBase)); /* Reserv. memoria */ actual= *punt; actual->dato = valor; /* Guardamos el dato */ actual->equi = 0; /* Ni derecho */ actual->hijoIzq = NULL; /* No tiene hijo izquierdo */ actual->hijoDer = NULL; /* Ni derecho */ *crece=1; } else /* Si no es hoja */ if (actual->dato > valor) /* Y encuentra un dato mayor */ { Insertar(&actual->hijoIzq, valor,crece); /* Mira por la izquierda */ if(*crece) { switch(actual->equi) { case +1: (*punt)->equi=0; *crece=0; break; case 0: (*punt)->equi=-1; break; case -1: punt1=actual->hijoIzq; if(punt1->equi==-1) { // rotacion sii actual->hijoIzq=punt1->hijoDer; actual->equi=0; punt1->hijoDer=actual; *punt=punt1; } else { // rotacion did punt2=punt1->hijoDer; punt1->hijoDer=punt2->hijoIzq; /* Si hemos llegado a una hoja

punt2->hijoIzq=punt1; actual->hijoIzq=punt2->hijoDer; punt2->hijoDer=actual; if(punt2->equi==-1) actual->equi=1; else actual->equi=0; if(punt2->equi==+1) punt1->equi=-1; else punt1->equi=0; *punt=punt2; } (*punt)->equi=0; *crece=0; break; } } } else /* En caso contrario (menor) */ { Insertar(&actual->hijoDer, valor,crece); /* Mira por la derecha */ if(*crece) { switch(actual->equi) { case -1: (*punt)->equi=0; *crece=0; break; case 0: (*punt)->equi=1; break; case +1: punt1=actual->hijoDer; if(punt1->equi==+1) { // rotacion sii actual->hijoDer=punt1->hijoIzq; punt1->hijoIzq=actual; actual->equi=0; *punt=punt1; } else { // rotacion did punt2=punt1->hijoIzq; punt1->hijoIzq=punt2->hijoDer; punt2->hijoDer=punt1; actual->hijoDer=punt2->hijoIzq; punt2->hijoIzq=actual; if(punt2->equi==+1) actual->equi=-1; else actual->equi=0; if(punt2->equi==-1) punt1->equi=+1; else punt1->equi=0; *punt=punt2; } (*punt)->equi=0; *crece=0; break; } } } } void Rotaciondd(puntero &N, puntero N1)

{ N->hijoDer=N1->hijoIzq; N1->hijoIzq = N; if( N1->equi== 1) { N->equi= 0; N1->equi= 0; } else { N->equi= 1; N1->equi= -1; } N =N1; } void Rotacionii(puntero &N, puntero N1) { N->hijoIzq=N1->hijoDer; N1->hijoDer = N; if( N1->equi== -1) { N->equi= 0; N1->equi= 0; } else { N->equi= -1; N1->equi= 1; } N =N1; } void Rotaciondi (puntero &N, puntero N1) { puntero N2; N2=N1->hijoIzq; N->hijoDer=N2->hijoIzq; N2->hijoIzq=N; N1->hijoIzq=N2->hijoDer; N2->hijoDer=N1; if (N2->equi==1 ) if (N2->equi==-1) N2->equi=0; N =N2; } void Rotacionid (puntero &N, puntero N1) { puntero N2; N2=N1->hijoDer; N->hijoIzq=N2->hijoDer; N2->hijoDer=N; N1->hijoDer=N2->hijoIzq; N2->hijoIzq=N1; if (N2->equi==1 ) if (N2->equi==-1) N2->equi=0; N1->equi=-1; else N1->equi =0; N->equi= 1; else N->equi=0; N->equi=-1; else N->equi =0; N1->equi= 1; else N1->equi=0;

N =N2; } void Equilibrar1(puntero &N, int *hh) //hh: activado cuando ha disminuido en altura la rama izquierda del nodo N { puntero N1; switch( N->equi ) { case -1: N->equi= 0; break; case 0: N->equi= 1; *hh= 0; break; case +1: //Hay que restaurar el equilibrio N1= N->hijoDer; //Es determinado el tipo de rotacin if( N1->equi >= 0) { if(N1->equi == 0) *hh= 0; //No disminuye de nuevo la altura Rotaciondd(N, N1); } else Rotaciondi(N, N1); break; } } void Equilibrar2(puntero &N, int *hh) //hh: activado cuando ha disminuido en altura la rama dercha del nodo N { puntero N1; switch( N->equi ) { case 1: N->equi= 0; break; case 0: N->equi= -1; *hh= 0; break; case -1: //Hay que restaurar el equilibrio N1= N->hijoIzq; //Es determinado el tipo de rotacin if( N1->equi <= 0) { if(N1->equi == 0) *hh= 0; //No disminuye de nuevo la altura Rotacionii(N, N1); } else Rotacionid(N, N1); break; } } //Buscar el nodo puntero aux; void buscarHijo(puntero &ptr1,int *hh) {

if(ptr1->hijoDer!=NULL) { buscarHijo(ptr1->hijoDer,hh); if(*hh==1) Equilibrar2(ptr1,hh); } else { aux->dato=ptr1->dato; aux=ptr1; ptr1=ptr1->hijoIzq; *hh=1; } } // Eliminacion de nodos void eliminar(puntero &ptr,int dato,int *hh) { if(ptr==NULL) printf("El dato no existe"); else { if(dato<ptr->dato) { eliminar(ptr->hijoIzq,dato,hh); if(*hh==1) Equilibrar1(ptr,hh); } else { if(dato>ptr->dato) { eliminar(ptr->hijoDer,dato,hh); if(*hh==1) Equilibrar2(ptr,hh); } else { aux=ptr; if(ptr->hijoDer==NULL) { ptr=ptr->hijoIzq; *hh=1; } else if(ptr->hijoIzq==NULL) { ptr=ptr->hijoDer; *hh=1; } else { buscarHijo(aux->hijoIzq,hh); if(*hh==1) Equilibrar1(ptr,hh); } free(aux); } } }

} /* Cuerpo del programa */ int main() { int crece,opcion,valor; puntero arbol = NULL; clrscr(); do { gotoxy(1,23); cprintf("Menu \\"); cprintf("1- Adicion \\"); cprintf("2- Borrar \\"); cprintf("3- Salir \\"); cprintf("Su Opcion:"); cscanf("%d",&opcion); switch(opcion) { case 1:gotoxy(1,24); cprintf("Ingrese el valor a Insertar:"); cscanf("%d",&valor); crece=0; Insertar(&arbol, valor,&crece); clrscr(); Escribir(arbol,36,5,16); break; case 2:gotoxy(1,24); cprintf("Ingrese el valor a Eliminar:"); cscanf("%d",&valor); crece=0; eliminar(arbol, valor,&crece); clrscr(); Escribir(arbol,36,5,16); break; } } while(opcion!=3); return 0; }

También podría gustarte