Sie sind auf Seite 1von 148

INSTITUTO SUPERIOR TECNOLGICO

NORBERT WIENER

Manual del Alumno


ASIGNATURA: Estructura de la
Informacin

PROGRAMA: S3C

Lima-Per
2
Manual del Alumno

LISTAS

1. INTRODUCCIN.

Dado un dominio D, una lista de elementos de dicho conjunto es una sucesin finita de
elementos del mismo.En lenguaje matemtico, una lista es una aplicacin de un conjunto de la
forma {1,2, ... ,n} en un dominio D:

R:{1,2, ... ,n} ---> D

Una lista se suele representar de la forma:

<a1,a2, ... ,an> con ai = a(i)

A n se le llama longitud de la lista.

A 1,2,...,n se les llama posiciones de la lista. El elemento a(i)=ai, se dice que ocupa la posicion
i. Si la lista tiene n elementos, no existe ningn elemento que ocupe la posicin n+1. Sin
embargo, conviene tener en cuenta dicha posicin, a la que se llama posicin detras de la
ltima, ya que esta posicin indicar el final de la lista. A a 1 se le llama primer elemento de la
lista y a an ltimo elemento de la lista. Si n=0 diremos que la lista est vaca y lo
representaremos como <>. Los elementos de una lista estan ordenados por su posicin. As,
se dice que ai precede a ai+1 y que ai sigue a ai-1. A continuacin vamos a especificar un
ejemplo de posibles operaciones primitivas entre listas. Al conjunto de las listas (es decir, al
tipo lista) lo llamaremos tLista. Al conjunto de los elementos bsicos(es decir, al tipo que se
almacenar en la lista) tElemento. Tambin vamos a considerar el tipo posicin como
tPosicion. Esto lo haremos as, ya que no siempre las posiciones las vamos a representar por
nmeros naturales del lenguaje que se utilice. Lo nico importante de la representacin que se
utilice es que sea un conjunto finito y totalmente ordenado: hay un primer y un ltimo elemento
y dado un elemento se puede determinar el siguiente (si no es ltimo) y el anterior (si no es el
primero).

Hay que tener en cuenta que si una lista tiene longitud n y se elimina el elemento que ocupa
una determinada posicin intermedia i, entonces la longitud pasa a ser n-1 y el elemento que
estaba en la posicin i+1 pasar a ocupar la posicin i, el de la posicin i+2 pasar a ocupar la
posicin i+1 y as sucesivamente.

2. OPERACIONES PRIMITIVAS DE LAS LISTAS.

Dentro del tipo abstracto de listas podemos proponer las siguientes primitivas:

void anula(tLista *l)


tPosicion primero(tLista l)
tPosicion fin(tLista l)
tPosicion siguiente(tPosicion p, tLista l)
tPosicion anterior(tPosicion p, tLista l)
tPosicion posicion(tElemento x, tLista l)
tElemento elemento(tPosicion p, tLista l)
void insertar(tElemento x, tPosicion p, tLista t)
void borrar(tPosicion p, tLista l)
3
Manual del Alumno

ESPECIFICACIN SEMANTICA Y SINTACTICA.

void anula(tLista *l)


PRE: l = <a1,a2, ... ,an>
POST: (*l) = <>
{vaca la lista}

tPosicion primero(tLista l)
PRE: l est inicializada.
POST: RESULTADO = (1)
{devuelve la primera posicin de la lista. Si la lista es <> coincide con fin(l)}

tPosicion fin(tLista l)
PRE: l est inicializada.
POST: RESULTADO = (n + 1)
{posicin detrs de la ltima}

tPosicion siguiente(tPosicion p, tLista l)


PRE: l = <a1,a2, ... ,an>, 1 <= p <= n
POST: RESULTADO = p + 1
{devuelve la posicin siguiente a la posicin p}

tPosicion anterior(tPosicion p, tLista l)


PRE: l = <a1,a2, ... ,an>, 2 <= p <= n+1
POST: RESULTADO = p - 1
{devuelve la posicin anterior a la posicin p}

tPosicion posicion(tElemento x, tLista l)


PRE: l est inicializada.
POST: Si existe j perteneciente a {1,2, ... ,n} tal que aj = x entonces RESULTADO = i,
donde i verifica: ai=x y si aj=x entonces j>=i.
{da la posicin de la primera aparicin de x en la lista l}

tElemento elemento(tPosicion p, tLista l)


PRE: l = <a1,a2, ... ,an>, 1 <= p <= n
POST: RESULTADO = ap
{devuelve el elemento situado en la posicin p}

void insertar(tElemento t, tPosicion p, tLista l)


PRE: l = <a1,a2, ... ,an>, 1 <= p <= n+1
POST: l = <a1, ... ,ap-1, x, ap, ... ,an >
{Resulta una lista de longitud n+1, en la que x ocupa la posicion p. Si p=n entonces la
lista resultante es l=< a1,...,an-1,x,an>}

void borrar(tPosicion p, tLista l)


PRE: l = <a1,a2, ... ,an>, 1 <= p <= n
POST: l = <a1, ... ,ap-1, ap+1, ... ,an >
{elimina el elemento que ocupa la posicin p, de forma que ahora la posicin p la ocupa
el elemento que se encontraba en la posicin p+1}

Respecto al conjunto de primitivas que hemos presentado, no son ms que un ejemplo


representativo de las primitivas ms importantes que nos sirve para ilustrar la forma en que se
debe construir el tipo de dato abstracto Lista. Obviamente, en una implementacion real es
posible optar por un conjunto distinto de primitivas teniendo en cuenta varios puntos:

El conjunto de primitivas tiene que ser completo en el sentido de que tiene que ser
posible construir cualquier algoritmo que use listas utilizando nicamente las primitivas
que se incluyen.
4
Manual del Alumno
Tambin debe ser suficiente pero no obligatoriamente mnimo. Aunque no sea
necesario incluir nuevas primitivas, puede ser conveniente aadir nuevas funciones si
existen motivos como:

a. La funcin va a ser probablemente muy usada. Es el caso de primitivas como la


de posicin o anterior, las cuales pueden ser perfectamente programadas en
base a las dems. Es fcil imaginar nuevas primitivas como pueden ser una
primitiva de copia de una lista en otra, una primitiva de ordenacin eficiente de
los elementos de la lista,etc.

b. La funcin va a ser usada con cierta asiduidad y por otra parte la


implementacin haciendo uso de las dems funciones primitivas empeora
sustancialmente la velocidad de operacin. Es el caso de primitivas como
anterior que pueden ser programadas en base a primero y siguiente pero que
en determinadas implementaciones pasara esta operacin de tener orden
constante a tener orden de la longitud de la lista.

Es posible tener que rehacer el conjunto de primitivas atendiendo a razones referentes


a una eficiente utilizacin de los recursos hardware. Es el caso por ejemplo de la
funcin anula, la cual en ciertas implementaciones es altamente probable se a
transformada en una funcin de creacin que se ve completada con otra nueva de
destruccin:

. CrearVacia: Devuelve una lista que est vaca.


i. CrearCopia: Toma como argumento una lista ya creada y devuelve una lista
distinta que es copia de la primera.

Las cabeceras de las funciones pueden necesitar ser modificadas para hacer viable su
implementacin. Es el caso por ejemplo de que una funcin no pueda devolver un tipo
de dato o que el tipo de dato sea muy complejo y que pasarlo por valor o devolverlo
como salida de una funcin pueda convertirse en algo ineficente dado su tamao. En
muchos casos, por tanto, ser aconsejable no pasar estructuras directamente sino un
puntero a ellas, que una funcin no devuelva un valor sino que se devuelva mediante
un puntero en uno de sus parmetros,etc.

Un tipo de dato abstracto es un producto software y como tal es algo dinmico que est
sujeto a un mantenimiento. De esta forma tendremos que tener en cuenta que el
conjunto de primitivas de un TDA es algo extensible. En este sentido, el conjunto de
funciones que incorporamos a un TDA no debe ser diseado considerando que
debemos aadir todas y cada una de primitvas que creemos que se necesitarn, es
decir, puede ser ms conveniente retrasar la incorporacin de ciertas primitavas en
caso de que dudemos de su utilidad. Tngase en cuenta que, desde el punto de vista
del mantenimiento del software que usa el TDA, es mucho menos costosa la adicon de
nuevas primitivas que la supresion de algunas ya existentes.

EJEMPLOS DE USO.
Es importante aprender a usar las listas basndonos en estas especificaciones, aunque este
tipo no venga en el lenguaje en el que estemos trabajando y no conozcamos la implementacin
que se va a usar.
Por ejemplo, vamos a escribir un porcedimiento que escriba todos los elementos de una lista.
Suponiendo que para tElemento existe un procedimiento, ,escribe(x), que escribe un elemento
de dicho tipo.

void salida (tLista t)

{
5
Manual del Alumno
tposicion p;

telemento x;

for (p = primero(l); p != fin(l); p = siguiente(p, l)){

x = elemento(p, l);

escribe(x);

Veamos otro ejemplo de copia de una lista en otra:

tLista copia (tLista l)

tLista l2;

tPosicion p;

anula(&l2);

for (p=primero(l);p!=fin(l);p=siguiente(p,l))

insertar(elemento(p,l),fin(l2),l2);

return l2;

En el siguiente ejemplo, vamos a ver un porcedimiento para eliminar todos los elementos
repetidos de la lista. Suponemos que el tipo bsico es tElemento y que existe una funcin
lgica, igual(x,y), que nos dice cuando son iguales dos elementos de este tipo. Se podra
pensar en que bastara considerar la igualdad del C(==), pero es posible que no coincidad con
la igualdad de tElemento. Por ejemplo, consideremos los numeros racionales definidos como:

typedef struct {

int num;

int den;

}racional;
6
Manual del Alumno

Entonces si x,y son de tipo racional, entonces pueden representar el mismo racional, ser
iguales, aunque no se verifique x.num==y.num && x.den==y.den .La funcin igual sera en este
caso:

int igual (int x;int y)

return (x.num*y.den == y.num*x.den);

Con estas consideraciones, el procedimiento para eliminar las repeticiones de una lista sera
como sigue:

void elimina (tLista l, int (*es_igual)(tElemento, tElemento))

tposicion p, q;

for (p = primero(l); p != fin(l); p = siguiente(p, l)){

q = siguiente(p ,l);

while (q != fin(l))

if ((*es_igual)(elemento(p, l), elemento(q, l)))

borrar(q, l);

else

q = siguiente(q, l);

Unos comentarios respecto a esto ejemplo:

La forma de usar esta funcin con nuestro ejemplo sobre una lista l es mediante la
llamada elimina(l,igual).

La variable l es un parmetro que se pasa por valor. Ntese que su valor no cambia a
lo largo de la funcin dado que en las especificaciones siempre se pasa este parmetro
de esta forma. La nica funcin de las especificadas que se puede usar para cambiar
el valor de una variable de tipo tLista es anula.
7
Manual del Alumno
Se puede observar que tan solo se pasa a la posicin siguiente cuando no se borra el
elemento que se encuentra en la posicin p ya que en el caso de que sea borrado ese
elemento habr que analizar el elemento que se encuentra en esa posicin p.

3. IMPLEMENTACIN DE LAS LISTAS.


IMPLEMENTACIN DE LISTAS MEDIANTE VECTORES.
Las listas se pueden implementar usando las posiciones consecutivas de un vector.Como las
listas tienen longitud variable y los vectores longitud fija, esto se resuelve considerando
vectores de tamao igual a la longitud maxima de la lista y un entero donde se indica la
posicin donde se encuentra el ltimo elemento de la lista.
As podriamos tener:

#define LMAX = 100; /* Una constante adecuada. */

typedef int tElemento; /* Por ejemplo. */

typedef struct{

tElemento elementos[LMAX];

int n;

} Lista;

typedef Lista *tLista;

typedef int tPosicion;

FUNCIN DE ABSTRACCIN:
Dado el objeto del tipo rep r = {elementos, n}, el objeto abstracto que representa es:
<r.elementos[0], r.elementos[1], ... , r.elementos[n-1]>

INVARIANTE DE LA REPRESENTACIN:
VERDAD.
8
Manual del Alumno
La implementacin de la mayoria de las operaciones es prcticamente inmediata. Por ejemplo,
las mas simples son:

static void error (char *mensaje)

fprintf(stderr, "%s\n", mensaje);

exit(-1);

void anula (tLista *l)

*l = (tLista) malloc (sizeof(Lista));

if (*l==NULL) {

error("No hay memoria.");

(*l)->n=-1;

tPosicion primero (tLista l)

return 0;

tPosicion fin (tLista l)

return(l->n+1);

tPosicion siguiente (tPosicion p, tLista l)

if ((p < 0) || (p > l->n))


9
Manual del Alumno
error("Posicin no vlida.");

return (p + 1);

tPosicion anterior (tPosicion p, tLista l)

if ((p <= 0) || (p > l->n+1))

error("Posicin no vlida.");

return (p - 1);

tElemento elemento (tPosicion p, tLista l)

if ((p < 0) || (p > l->n))

error("Posicin no vlida.");

return (l->elementos[p]);

Las nicas operaciones que pueden presentar un poco de dificultad son las de insertar,borrar y
posicion. La funcin posicin tiene que realizar una bsqueda lineal en un vector. En caso de
que el elemento considerado no est en el vector, esta funcin debe devolver lo mismo que
fin(l).

tPosicion posicion (tElemento x, tLista l)

tPosicion q;

int encontrado;

q = encontrado = 0;

while ((q <= l->n) && (!encontrado)) {

if (l->elementos[q] == x)

encontrado=1;

else q++;

};
10
Manual del Alumno

return q;

Para la operacin de insercin hay que hacer previamente un hueco donde realizar dicha
insercin. Para el borrado, hay que "rellenar" el hueco dejado por el elemento borrado. En la
figura podemos observar en las flechas superiores los movimientos de los elementos que se
han tenido que realizar para insertar en la posicin p (coinciden con los movimientos en sentido
contrario que se deben realizar para borrar el elemento que se encuentra en dicha posicin).

Como consecuencia de ello, habr que mover, en ambos casos, todos los elementos que
ocupen una posicin superior a la considerada para realizar la insercin o borrado. Esto tiene
como consecuencia que la eficiencia de las operaciones no es muy buena, del orden del
tamao de la lista.

void insertar (tElemento x, tPosicion p, tLista l)

tPosicion q;

if ((p > l->n+1) || (p < 0))

error("Error p incorrecta.");

else if (l->n >= LMAX-1)

error("Lista llena");

else{

for (q = l->n; q >= p; q--)

l->elementos[q+1] = l->elementos[q];

l->n++;

l->elementos[p] = x;

}
11
Manual del Alumno

void borrar (tPosicion p,tLista l)

if ((p > l->n) || (p < 0))

error("p incorrecta.");

else {

l->n--;

for (; p <= l->n; p++)

l->elementos[p]=l->elementos[p+1];

Aparte de la mala eficiencia de estas dos operaciones, que veremos como se mejorar en otras
implementaciones, otro inconveniente de esta implementacin es que las listas tienen un
tamao mximo del que no se puede pasar. Es decir, no corresponden exactamente a las
especificaciones consideradas en un principio. Por otra parte, siempre hay una porcin de
espacio reservada para los elementos de la lista, y que no se utiliza al ser el tamao de la lista,
en un momento dado menor que el tamao mximo. Esto se hace ms grave si las distintas
listas que se representen son de un tamao muy distinto.
Otro detalle importante de esta implementacin es, cmo hemos mencionado anteriormente, la
necesidad de una funcin de destruccin ya que ahora mismo la memoria que se requiere cada
vez que se hace una llamada a la funcin anula no es recuperada en ningn momento.Sera
interesante aadir una nueva funcin tal como la siguiente (Ntese que si la constante LMAX
es grande y se hace uso de un nmero alto de listas esta funcin no slo se hace interesante
sino que necesaria:

void destruye (tLista l)

free(l);

Teniendo en cuenta los problemas que presenta la implementacin que hemos presentado
mediante vectores y considerando las posibilidades que nos brinda el lenguaje C, podemos
proponer una versin mas optimizada:

typedef int tElemento /* Por ejemplo. */


12
Manual del Alumno

typedef struct{

tElemento *elementos;

int Lmax;

int n;

}Lista;

typedef Lista *tLista;

typedef int tPosicion;

tLista crear (int tamanoMax)

tLista l;

l = (tLista) malloc(sizeof(Lista));

if (l == NULL)

error("Memoria Insuficiente");

l->Lmax = tamanoMax;

l->n = -1;

l->elementos = (tElemento *)malloc(tamanoMax*sizeof(tElemento));

if (l->elementos == NULL)

error("Memoria Insuficiente.");

void destruir (tLista l)

free(l->elementos);

free(l);

}
13
Manual del Alumno
Donde las dems primitivas quedaran de la misma forma sustituyendo LMAX por l->LMAX
En esta nueva implementacin conseguimos resolver con exito dos cosas:

1. Tamaos variables: Ahora la primitiva anula ha sido sustituida por la primitiva crear a
la que se pasa un parmetro indicando el tamao maximo que tendra la lista.La mejora,
por lo tanto, ha sido sustancial teniendo en cuenta que el tamao mximo que es
necesario para la versin anterior debe ser superior a la ms grande de las listas que
se manejan y por consiguiente para pequeas listas habra una gran cantidad de
memoria desperdiciada.

2. Creacin y Destruccin: Aunque en la versin anterior se solucion el problema al


proponer la funcin destruye, es importante destacar que en esta versin tambin se
ofrece el constructor y destructor del tipo de dato permitiendo de esta forma recuperar
los recursos ocupados por las listas que no se volvern a usar.

Es importante destacar la forma en que se deben usar las funciones de un tipo de dato
abstracto (normalmente en la especificacin junto con algn ejemplo si es necesario). As
destacaremos que que en este nuevo conjunto de primitivas incluyendo crear y destruir el uso
del TDA debe ser:

1. Declaracin de la variable de tipo tLista.


2. Creacin de la lista mediante la primitva crear.
3. Uso de la lista mediante primitivas distintas a la de creacin y destruccin.
4. Destruccin de la lista mediante la primitiva destruir.

Teniendo en cuenta:

1. El uso de la primitiva crear sobre una lista ya creada provocar una prdida de los
recursos de memoria ocupados por esta lista y la actualizacin de su valor a la lista
vaca.
2. El uso de la primitiva destruir a una lista no creada o a una lista que aunque se cre ha
sido destruida es errneo y provocar resultados imprevisibles.
3. Obviamente, despus de la destruccin de una lista, se podr usar de nuevo la misma
variable en la creacin, uso y destruccin de una nueva lista.

Como ejemplo mostramos una funcin que guarda en una lista los nmeros enteros del 0 al 9,
despues la recorre eliminando los impares y por ltimo escribe el resultado dos veces, desde el
primer elemento al ltimo y desde el ltimo al primero:

void EJEMPLO ()

int a;

tLista l;

tPosicion p;

l = crear(10);

for (a=0; a<10; a++)

insertar(a, primero(l), l);


14
Manual del Alumno

for (p=primero(l); p!=fin(l); ) {

a = elemento(p,l);

if (a%2)

borrar(p,l);

else

p = siguiente(p,l);

for (p=primero(l); p!=fin(l); p=siguiente(p,l)) {

a = elemento(p,l);

printf("Elemento: %d \n",a);

printf(" \n \n ");

for (p=fin(l); p!=primero(l); p=anterior(p,l)) {

a = elemento(anterior(p,l), l);

printf("Elemento: %d \n",a);

destruir(l);

IMPLEMENTACIN DE LISTAS MEDIANTE CELDAS ENLAZADAS POR PUNTEROS.

Una implementacin de las listas que evita los problemas anteriormente mencionados para los
vectores, es la que est basada en el uso de punteros. Esta implementacin se basa en
representar cada elemento, ai, de una lista <a1,a2, ...,an> como una celda dividida en dos
partes: un primer campo donde se almacena el elemento en cuestin; y un segundo campo
donde se almacena un puntero, que nos indica donde est el siguiente elemento de la lista, tal
como se muestra en la parte (a) de la figura.
La celda que contiene el ltimo elemento de la lista tiene un puntero donde se almacena NULL.
As, la lista quedaria como se muestra en la parte (b) de la figura.
15
Manual del Alumno
Para realizar ms facilmente las operaciones es conveniente considerar una celda inicial,
llamada de cabecera y donde no se almacena ningn elemento de la lista. De esta forma la
lista propiamente diche vendr representada por un puntero que indique la direccin de la
cabecera y que permite obtener los distintos elementos de la misma como se muestra
finalmente en la parte (c) de la figura.

Para estas listas es conveniente representar la posicin mediante un puntero que acceda al
elemento correspondiente. Sin embargo, no se va a consider un puntero con la direccin de la
celda donde est el elemento considerado, sino la direccin de la celda donde est el elemento
anterior. Con esto se puede acceder a dicho elemento (mediante el puntero correspondiente), y
tambin ser ms til para las operaciones de insercin y borrado. La posicin del primer
elemento, vendr representada entonces por un puntero apuntando a la celda de cabecera, es
decir, idntico a la lista, l. La posicin del elemento ai se representar mediante un puntero,
indicando la celda del elemento ai-1. La posicin detrs del ltimo elemento ser un puntero
apuntando a an.
Debido a que la posicin lgica de un elemento viene determinada por la posicin fsica del
anterior puede dar lugar a un error de programacin si se trabaja con varias posiciones a la vez
y se realizan borrados. Por ejemplo, consideremos una lista con 3 elementos y 2 punteros
indicando la posicin del segundo (puntero p) y tercer (puntero q) elemento (ver figura).

Si no atendemos a la implementacin, el borrar el elemento de la posicin p (elemento a 2)


podemos considerar dos resultados:

Dado que q apunta al tercer elemento y quedan dos, q resulta apuntando a fin(l).
Dado que a3 pasa a ser el segundo elemento y q apuntaba a a3, ahora q apunta al
elemento segundo de la lista.

En general, el primer caso corresponde a la implementacin realizada mediante vectores y el


segundo a la realizada mediante celdas enlazadas teniendo en cuenta:
16
Manual del Alumno
En el caso de las listas mediante celdas enlazadas, el comportamiento es vlido
excepto para el caso de dos posiciones consecutivas. Si en el ejemplo que nos ocupa
borramos el segundo elemento, la zona a la que apunta q es liberada y por tanto es
incorrecto usar su contenido adems de haber quedado fuera de la lista y por tanto es
una posicin no vlida.
En el caso de las matrices,ocurre de forma paralela que el comportamiento es vlido
excepto si una posicin indicaba el final de la lista. Al eliminar un elemento, el final de
la lista se ve modificado y por tanto si una posicin indicaba el final, queda apuntando a
una zona fuera de la lista.

Es por ello que el uso simultneo de varias posiciones conviene que sea manejado con
cuidado. Obviamente, el que el acceso a un elemento se produzca por medio del elemento
anterior conviene que sea indicado en la especificacin del TDA mediante el correspondiente
aviso de que el borrado de un elemento invalidad los valores de posicin del inmediatamente
posterior (por ejemplo, se puede indicar el comportamiento de los valores posicin cuando se
usan las funciones de insercin y de borrado).
En C, la definicin de tipos correspondiente a la implementacin por punteros sera:

typedef struct Celda{

tElemento elemento;

struct Celda *siguiente;

}celda;

typedef celda *tPosicion;

typedef celda *tLista;

FUNCIN DE ABSTRACCIN
Dado el objeto del tipo rep l={elemento, siguiente}, el objeto abstracto que representa es:
(n)
<l->siguiente->elemento, l->siguiente->siguiente->elemento, ... ,l->siguiente-> ->siguiente-
>elemento>
(n+1)
Donde r->siguiente-> ->siguiente == NULL.
INVARIANTE DE REPRESENTACIN
Todas las direcciones de los campos siguiente proceden de llamadas (tposicion)
malloc(sizeof(celda)) o son NULL.

Y las operaciones se pueden implementar como sigue:

tLista crear ()

tLista l;

l = (tLista)malloc(sizeof(celda));

if (l == NULL)
17
Manual del Alumno

error("Memoria Insuficiente.");

l->siguiente = NULL;

return l;

void destruir (tLista l)

tPosicion p;

for (p = l; l != NULL; p = l){

l = l->siguiente;

free(p);

tPosicion fin (tLista l)

tPosicion p;

p=l;

While (p->siguiente != NULL) {

p = p->siguiente;

return p;

Repecto a esta funcin es importante senalar , que siempre tiene que recorrer toda la lista
para devolver el puntero que se muestra en la figura. Por lo que su eficiencia es del orden de la
longitud de la lista. Habra que procurar no utilizarla demasiado si se usa esta implementacin.
18
Manual del Alumno

Por ejemplo, un ciclo while con una condicin p!=fin(l) se debe sustituir por:
q=fin(l);
while (p!=q)...

void insertar (tElemento x, tPosicion p, tLista l)

tPosicion q;

q = (tPosicion)malloc(sizeof(celda));

if (q == NULL)

error("Memoria Insuficiente.");

q->elemento = x;

q->siguiente = p->siguiente;

p->siguiente = q;

La forma en la que se realiza la insercin puede observarse en la figura.


19
Manual del Alumno

Es importante sealar varias cosas de este procedimiento:

Tarda siempre un tiempo constante. No como en la implementacin vectorial en que


tardaba un tiempo proporcional a la longitud de la lista.
No comprueba la precondicin. Se podra hacer, pero entonces se perdera mucho
tiempo en la comprobacin. Es responsabilidad del programador utilizarlo siempre con
posiciones de esta lista. Si no se hace as, puede dar lugar a graves errores.
El procedimiento funciona bien en los casos extremos de la primera posicin y la
posicin fin(l). En las listas sin cabecera estos casos habra que haberlos considerado
aparte.

tPosicion siguiente (tPosicion p, tLista l)

if (p->siguiente==NULL) {

error("No hay siguiente de fin.");

return p->siguiente;

tPosicion primero (tLista l)


20
Manual del Alumno

return l;

tPosicion posicion (tElemento x, tLista l)

tPosicion p;

int encontrado;

p = primero(l);

encontrado = 0;

while ((p->siguiente != NULL) && (!encontrado)) {

if (p->siguiente->elemento == x)

encontrado=1;

else p = p->siguiente;

return p;

Notas referentes a la funcin posicion:

Es importante comprobar que la funcin verifica las postcondiciones en los dos casos
posibles: cuando est y cuando no est el elemento buscado en la lista.
La complejidad es igual al caso de la implementacin mediante vectores. En trmino
medio hay que recorrer la mitad de la lista.
En la condicin del bucle aparece la comparacin (p->siguente != NULL). Esta es
equivalente a (p !=fin(l)), pero entonces aumentaria mucho la complejidad debido a la
poca eficiencia de la funcin fin(l). Se podra pensar en sustituir en cualquier programa,
esta condicin por la que hemos usado aqu. Pero esto lo hemos podido hacer porque
sta es una operacin primitva y se puede hacer referencia a la implementacin. En un
programa que use las listas no se debe hacer.

tElemento elemento (tPosicion p, tLista l)

{
21
Manual del Alumno
if (p->siguiente == NULL) {

error("Error: posicion fin(l).");

return p->siguiente->elemento;

void borrar (tPosicion p, tLista l)

tPosicion q;

if (p->siguiente == NULL)

error("Error: posicion fin(l).");

q = p->siguiente;

p->siguiente = q->siguiente;

free(q);

Respecto a esta implementacin son vlidos los mismos comentarios que para la funcin
insercion.

4. COMPARACIN DE MTODOS.
Resulta de inters saber si es mejor usar una implementacin de listas basada celdas
enlazadas o en matrices en una circunstancia dada. Frecuentemente la contestacin depende
de las operaciones uqe queramos llevar a cabo, o de cuales son llevadas a cabo con mayor
asiduiadad. Otras veces, la decisin es en base a la longitud de la lista.
Los puntos principales a considerar son los siguentes:

1. La implementacin matricial nos obliga a especificar el tamao mximo de una lista en


tiempo de compilacin. Si no podemos poner una cota a la longitud de la lista,
posiblemente deberamos coger una implementacin basada en punteros.
Lgicamente, este problema ha sido parcialmente solucionado con la parametrizacin
del tamao mximo de la lista, pero an as hay que delimitar el tamao mximo para
cada una de las listas.

2. Ciertas operaciones requieren ms tiempo en unas implementaciones que en otras.


Por ejemplo insertar y borrar realizan un nmero constante de pasos para una lista
enlazada, pero necesitan tiempo proporcional al nmero de elementos siguientes
cuando usamos la representacin matricial.Inversamente, ejecutar fin requiere tiempo
constante con la implementacin matricial, pero tiempo proporcional a la lista si usamos
la implementacin por punteros simplemente-enlazadas (aunque recordemos que el
problema es solucionable aadiendo un puntero). Por otro lado, en las listas
22
Manual del Alumno
doblemente-enlazadas se requiere tiempo constante para todas las operaciones
(excepto la de posicin que requiere un tiempo proporcional a la longitud de la lista).

3. La implementacin matricial puede derrochar espacio, ya que usa la cantidad mxima


de espacio independientemente del nmero de elementos presentes en la lista en un
momento dado. La implementacin por punteros usa tanto espacio como necesita para
los elementos que hay en la lista, pero necesita espacio adicional para los punteros de
cada celda.Por ltimo, las listas doblemente-enlazadas aunque son las ms eficientes
requieren dos punteros para cada elemento.

4. En las listas enlazadas la posicin de un elemento se determina con un puntero a la


celda del elemento anterior por lo que hay que tener cuidado con la operacin de
borrado si se trabaja con varias posiciones tal y como vimos anteriormente. En el caso
de la implementacin matricial, si borramos un elemento, todas las posiciones
posteriores a ese elemento apuntarn al siguiente al que apuntaban y si existe una
posicin apuntando al final de la lista, sta queda invalidada. (El comportamiento
tambien es distinto para la insercin). En el caso de las listas doblemente-enlazadas,
su comportamiento es el mas cmodo siempre que la implementacin realizada no
provoque que la posicin usada en el borrado quede invalidada.

PILAS

1. INTRODUCCIN.

Una Pila es una clase especial de lista en la cual todas las inserciones y borrados tienen lugar
en un extremo denominado extremo, cabeza o tope. otro nombre para las pilas son listas FIFO
(ltimo en entrar, primero en salir) o listas pushdown (empujdas hacia abajo). El modelo
intuitivo de una pila es un conjunto de objetos apilados de forma que al aadir un objeto se
coloca encima del ultimo aadido y para quitar un objeto del montn hay que quitar antes los
que estn por encima de l.Un tipo de dato abstracto PILA incluye las siguientes operaciones.

2. OPERACIONES PRIMITIVAS DE LAS PILAS.

Dentro del tipo abstracto de pila podemos proponer las siguientes primitivas:

CREAR()
DESTRUIR(P)
TOPE(P)
POP(P)
PUSH(x,P)
VACIA(P)

ESPECIFICACIN SEMANTICA Y SINTACTICA

pila crear ()
Efecto: Devuelve un valor del tipo pila preparado para ser usado y que contiene un
valor de pila vacia.Esta operacin es la misma que la de las listas generales.

void destruir (pila *P)


Argumentos: Una pila P.
Efecto: Libera los recursos que mantienen la lista P de forma que para volver a usarla
se debe asignar una nueva pila con la operacin de creacin. Esta operacin es la
misma que la de las listas generales.
23
Manual del Alumno
telemento tope (pila P)
Argumentos: Una pila P que debe ser no vaca.
Efecto: Devuelve el elemento en la cabeza de la pila P. Si, como es lgico,
identificamos la cabeza de una pila con la posicin 1, entonces TOPE(P) puede
escribirse en trminos de operaciones de listas como ELEMENTO (PRIMERO(P),P).

void pop (pila P)


Argumentos: Una pila P que debe ser no vaca. Es modificada.
Efecto: Borra el elemento del tope de la pila P, esto es, BORRA (PRIMERO(P),P).
Algunas veces es conveniente implementar POP como una funcin que devuelve el
elemento que acaba de borrar.

void push (telemento x, pila P)


Argumentos:

x: Un elemento que deseamos poner en la pila.


p: Una pila P val donde deseamos poner el elemento x.

Efecto:Inserta el elemento x en el tope de la pila P. El elemento tope antiguo se


convierte en el siguiente al tope y asi sucesivamente. En trminos de primitivas de
listas esta operacin es INSERTA (x,PRIMERO(P),P).

int vacia (pila P)


Argumentos: Una pila P.
Efecto: Devuelve si P es una pila vaca.

EQUIVALENCIA CON LAS LISTAS

3. IMPLEMENTACIN DE LAS PILAS.


Todas las implementaciones de las listas que hemos descrito son validas para las pilas ya que
una pila junto con sus operaciones es un caso especial de una lista con sus operaciones. An
asi conviene destacar que las operaciones de las pilas son ms especficas y que por lo tanto
la implementacin puede ser mejorada especialmente en el caso de la implementacin
matricial.

IMPLEMENTACIN MATRICIAL DE LAS PILAS.


La implementacion basada en matrices para las listas que dimos anteriormente, no es
particularmente buena para las pilas, porque cada PUSH o POP requiere mover la lista entera
hacia arriba o hacia abajo y por tanto, requiere un tiempo proporcional al nmero de elementos
en la pila. Una forma mejor de usar matrices toma en cuenta el hecho de que inserciones y
borrados ocurren solamente en el tope y por lo tanto dichas operaciones slo se efectuarn en
un extremo de la estructura. Obsrvese que la mejora puede ser introducida haciendo las
inserciones y borrados al final de la lista dentro de la implementacin matricial de las listas.
Podemos situar el fondo de la pila en el primer elemento de la matriz y hacer crecer la pila
24
Manual del Alumno
hacia el ultimo elemento de la matriz. Un cursor llamado tope indica la posicin actual del
primer elemento de la pila.

Para esta implementacion basada en matrices de pilas definimos el tipo de dato abstracto Pila
por

typedef int tElemento /* Por ejemplo */

typedef struct {
tElemento *elementos;
int Lmax;
int tope;
} tipoPila;

typedef tipoPila *pila;

FUNCIN DE ABSTRACCIN.
Dado el objeto del tipo rep p, *p = (elemento, Lmax, tope), el objeto abstracto que representa
es:
<p->elemento[p->tope], p->elemento[p->tope - 1],..., p->elemento[0]>.

INVARIANTE DE LA REPRESENTACIN.
Dado el objeto del tipo rep p, *p = (elemento, Lmax, tope) debe cumplir:

a. p tiene valores obtenidos de llamadas (pila) malloc(sizeof(tipopila));


b. p->elemento tiene una direccin vlida de tipo telemento*.
c. p->Lmax > 0.
d. -1 <= p->tope <= p->Lmax - 1.

Las operaciones tipicas sobre las pilas estn implementadas en las siguientes funciones y
procedimientos.

pila CREAR (int tamanoMax)


{
pila P;

P = (pila) malloc(sizeof(tipoPila));
if (P == NULL)
25
Manual del Alumno
error("No hay memoria suficiente");
P->Lmax = tamanoMax;
P->tope = -1;
P->elementos = (tElemento *) malloc(tamanoMax, sizeof(tElemento));
if (P->elementos == NULL)
error("No hay memoria suficiente.");
return P;
}

void DESTRUIR (pila *P)


{
free((*P)->elementos);
free(*P);
*P = NULL;
}

int VACIA (pila P)


{
return(P->tope == -1);
}

tElemento TOPE (pila P)


{
if (VACIA(P)) {
error("No hay elementos en la pila.");
return(P->elementos[P->tope]);
}

void POP (pila P)


{
if (VACIA(P)) {
error("No hay elementos en la pila.");
P->tope--;
}

void PUSH (tElemento x, pila P)


{
if (P->tope==P->Lmax-1) {
error("Pila llena");
p->tope++;
p->elementos[p->tope] = x;
}

Como puede observar el lector, esta implementacin es justamente la realizada sobre las listas
mediante vectores pero simplificada de una forma considerable.

IMPLEMENTACIN DE LAS PILAS MEDIANTE CELDAS ENLAZADAS.


La representacin por celdas enlazadas de una pila es facil, porque PUSH y POP operan
slamente sobre la celda de cabecera. De hecho, las cabeceras pueden ser punteros mejor
que celdas completas, ya que no hay nocin de posicin para las pilas y por tanto no
necesitamos representar la posicin 1 en una forma anloga a otras posiciones tal y como
muestra figura.
26
Manual del Alumno

Obviamente, el que las funciones sobre pilas sean ms especificas que sobre listas implica que
en general se simplificar la implementacin (que responde a la estructura de la figura
anterior).

FUNCIN DE ABSTRACCIN.
Dado el objeto del tipo rep p, el objeto abstracto que representa es:
(n)
<(*p)->elemento, (*p)->siguiente->elemento, ... , (*p)->siguiente-> ->siguiente->elemento>.
(n+1)
con (*p)->siguiente-> ->siguiente = NULL.
INVARIANTE DE LA REPRESENTACIN.
Dado un objeto del tipo rep p, debe cumplir:

a. p tiene valores obtenidos de llamadas (tiponodo **)


malloc(sizeof(tiponodo *));
b. Los campos siguiente de los nodos tienen direcciones vlidas, obtenidas de
llamadas a (tiponodo *) malloc(sizeof(tiponodo)). Slo es NULL el ltimo.

typedef struct pnodo {


tElemento elemento;
struct pnodo *siguiente;
} tipopnodo;

typedef tipopnodo **pila;

pila CREAR ()
{
pila P;

P = (tipopnodo **) malloc(sizeof(tipopnodo *));


if (P == NULL) {
error("Memoria insuficiente.");
*P = NULL;
return P;
}

void DESTRUIR (pila P)


{
while (!VACIA(P))
POP(P);
free(P);
}

tElemento TOPE (pila P)


{
if (VACIA(P))
error("No existe tope.");
return((*P)->elemento);
27
Manual del Alumno
}

void POP (pila P)


{
tipopnodo *q;

if (VACIA(P))
error("No existe tope.");
q = (*P);
(*P) = q->siguiente;
free(q);
}

void PUSH (tElemento x,pila P)


{
tipopnodo *q;

q = (tipopnodo *) malloc(sizeof(tipopnodo));
if (q == NULL) {
error("No hay memoria.");
q->elemento = x;
q->siguiente = (*P);
(*P) = q;
}

int VACIA (Pila P)


{
return (*P == NULL);
}

4. EJEMPLO DE APLICACIN.
Editor de lneas.

#: carcter de borrado
@: carcter de cancelacin de lnea

IDEA: procesar una lnea de texto usando una pila.

Leer un carcter
Si el carcter no es '#' ni '@' meterlo en la pila
Si el carcter es '#' sacar de la pila
Si el carcter es '@' vacia la pila

El cdigo podra ser:

editar (void) {
pila p, q;
char c;
p = crear();
while ((c = (char)getchar()) != EOF) {
if (c == '#')
quitar(p);
else
if (c == '@') {
destruir(&p);
p = crear();
} else
28
Manual del Alumno
poner(c, p);
};
q = crear();
while (!vacia(p)) {
poner(tope(p), q);
quitar(p);
};
while (!vacia(q)) {
printf("%c", tope(q));
quitar(q);
};
destruir(&q);
destruir(&p);
}

COLAS

1. INTRODUCCIN.

Una Cola es otro tipo especial de lista en el cual los elementos se insertan por un extremo (el
posterior) y se suprimen por el otro (el anterior o frente). Las colas se conocen tambien como
listas FIFO (primero en entrar,primero en salir). Las operaciones para las colas son anlogas a
las de las pilas. Las diferencias sustanciales consisten en que las inserciones se hacen al final
de la lista, y no al principio, y en que la terminologa tradicional para colas y listas no es la
misma. Las primitivas que vamos a considerar para las colas son las siguientes.

2. OPERACIONES PRIMITIVAS DE LAS COLAS.

Dentro del tipo abstracto de cola podemos proponer las siguientes primitivas:

CREAR()
DESTRUIR(C)
FRENTE(C)
PONER_EN_COLA(x,C)
QUITAR_DE_COLA(C)
VACIA(C)

ESPECIFICACIN SEMANTICA Y SINTACTICA.

cola crear ()
Argumentos: Ninguno.
Efecto: Devuelve una cola vacia preparada para ser usada.

void destruir(cola C)
Argumentos: Una cola C.
Efecto: Destruye el objeto C liberando los recursos que mantiene que empleaba.Para
volver a usarlo habr que crearlo de nuevo.
29
Manual del Alumno
tElemento frente (cola C)
Argumentos: Recibe una cola C no vaca.
Efecto: Devuelve el valor del primer elemento de la cloa C. Se puede escribir en
funcin de las operaciones primitivas de las listas como: ELEMENTO(PRIMERO(C),C).

void poner_en_cola (tElemento x, cola C)


Argumentos:

x: Elemento que queremos insertar en la cola.


C: Cola en la que insertamos el elemento x.

Efecto: Inserta el elemento x al final de la cola C. En funcin de las operaciones de las


listas seria: INSERTA(x,FIN(C),C).

void quitar_de_cola (cola C)


Argumentos: Una cola C que debe ser no vaca.
Efecto: Suprime el primer elemento de la cola C. En funcin de las operaciones de
listas seria: BORRA(PRIMERO(C),C).

int vacia (cola C)


Argumentos: Una cola C.
Efecto: Devuelve si la cola C es una cola vaca.

EQUIVALENCIA CON LAS LISTAS

3. IMPLEMENTACIN DE LAS COLAS. IMPLEMENTACIN DE COLAS BASADA EN


CELDAS ENLAZADAS.
Igual que en el caso de las pilas, cualquier implementacin de listas es vlida para las colas.
No obstante, para aumentar la eficiencia de PONER_EN_COLA es posible aprovechar el
hecho de que las inserciones se efectan slo en el extremo posterior de forma que en lugar de
recorrer la lista de principio a fin cada vez que desea hacer una insercin se puede mantener
un apuntador al ltimo elemento. Como en las listas de cualquier clase, tambien se mantiene
un puntero al frente de la lista. En las colas ese puntero es til para ejecutar mandatos del tipo
FRENTE o QUITA_DE_COLA. Utilizaremos al igual que para las listas, una celda cabecera
con el puntero frontal apuntndola con lo que nos permitir un manejo ms cmodo.
Grficamente, la estructura de la cola es tal y como muestra la figura:

Una cola es pues un puntero a una estructura compuesta por dos punteros, uno al extremo
anterior de la cola y otro al extremo posterior. La primera celda es una celda cabecera cuyo
campo elemento se ignora.
La definicin de tipos es la siguiente:

typedef struct Celda{


30
Manual del Alumno
tElemento elemento;
struct Celda *siguiente;
} celda;

typedef struct {
celda *ant,*post;
} tcola;

typedef tcola *cola;

FUNCIN DE ABSTRACCIN.
Dado el objeto del tipo rep c, *c = (ant, post), el objeto abstracto que representa es:

<c->ant->siguiente->elemento, c->ant->siguiente->siguiente->elemento, ..., c-


(n)
>ant->siguiente-> ->siguiente->elemento>, tal que c->siguiente->siguiente->
(n)
->siguiente = c->post.

INVARIANTE DE LA REPRESENTACIN.
Dado un objeto del tipo rep c, *c = (ant, post), debe cumplir:

a. c tiene valores obtenidos de llamadas (tcola **) malloc(sizeof(tcola));


b. Los campos siguiente de los nodos, c->ant y c->post tienen direcciones vlidas,
obtenidas de llamadas a (celda) malloc(sizeof(celda)). Slo es NULL el ltimode los
campos siguiente.

Con estas definiciones, la implementacin de las primitivas es la siguiente:

cola CREAR ()
{
cola C;

C = (tcola *) malloc(sizeof(tcola));
if (C == NULL)
error("Memoria insuficiente.");
C->ant = C->post = (celda *)malloc(sizeof(celda));
if (C->ant == NULL)
error("Memoria insuficiente.");
C->ant->siguiente = NULL;
return C;
}

void DESTRUIR (cola C)


{
while (!VACIA(C))
QUITAR_DE_COLA(C);
free(C->ant);
free(C);
}

int VACIA (cola C)


{
return(C->ant == C->post);
}

tElemento FRENTE (cola C)


{
31
Manual del Alumno
if (VACIA(C)) {
error("Error: Cola Vacia.");
}
return(C->ant->siguiente->elemento);
}

void PONER_EN_COLA (tElemento x,cola C)


{
C->post->siguiente = (celda *) malloc(sizeof(celda));
if (C->post->siguiente == NULL)
error("Memoria insuficiente.");
C->post = C->post->siguiente;
C->post->elemento = x;
C->post->siguiente = NULL;
}

void QUITAR_DE_COLA (cola C)


{
celda *aux;

if (VACIA(C))
error("Cola vacia.");
aux = C->ant;
C->ant = C->ant->siguiente;
free(aux);
}

Este procedimiento QUITAR_DE_COLA suprime el primer elemento de C desconectando el


encabezado antiguo de la cola,de forma que el primer elemento de la cola se convierte en la
nueva cabecera.
En la figura siguiente puede verse esquematicamente el resultado de hacer consecutivamente
las siguientes operaciones:

C=CREAR(C);
PONER_EN_COLA(x,C);PONER_EN_COLA(y,C);
QUITAR_DE_COLA(C);
DESTRUIR(C);
32
Manual del Alumno

Se puede observar que en el primer caso, la memoria que se obtiene del sistema es la de la
estructura de tipo celda que hace de cabecera y la memoria para ubicar los dos punteros
anterior y posterior. En los dos ltimos casos, la lnea punteada indica la memoria que es
liberada.

IMPLEMENTACIN DE LAS COLAS USANDO MATRICES CIRCULARES.


La implementacin matrical de las listas no es muy eficiente para las colas, puesto que si bien
con el uso de un apuntador al ltimo elemento es posible ejecutar PONER_EN_COLA en un
tiempo constante, QUITAR_DE_COLA, que suprime le primer elemento, requiere que la cola
completa ascienda una posicin en la matriz con lo que tiene un orden de eficiencia lineal
proporcional al tamao de la cola. Para evitarlo se puede adoptar un criterio diferente.
Imaginemos a la matriz como un circulo en el que la primera posicin sigue a la ltima, en la
forma en la que se ve en la figura siguiente. La cola se encuentra en alguna parte de ese
crculo ocupando posiciones consecutivas. Para insertar un elemento en la cola se mueve el
apuntador post una posicin en el sentido de las agujas del reloj y se escribe el elemento en
esa posicin. Para suprimir un elemento simplemente se mueve ant una posicin en el sentido
de las agujas del reloj. De esta forma, la cola se mueve en ese sentido conforme se insertan y
suprimen elementos. Obsrvese que utilizando este modelo los procedimientos
PONER_EN_COLA y QUITAR_DE_COLA se pueden implementar de manera que su
ejecucin se realice en tiempo constante.
33
Manual del Alumno

Existe un probelma que aparece en la representacin de la figura anterior y en cualquier


variacin menor de esta estrategia (p.e. que post apunte a la ltima posicin en el sentido de
las agujas del reloj). El problema es que no hay forma de distinguir una cola vacia de una que
llene el crculo completo salvo que mantengamos un bit que sea verdad si y solo si la cola est
vacia. Si no deseamos mantener este bit debemos prevenir que la cola llene alguna vez la
matriz. Para ver por qu puede pasar esto, supongamos que la cola de la figura anterior tuviera
MAX_LONG elementos. Entonces, post apuntara a la posicin anterior en el sentido de las
agujas del reloj de ant. Qu pasaria si la cola estuviese vacia?. Para ver como se representa
una cola vacia, consideramos primero una cola de un elemento. Entonces post y ant apuntarian
a la misma posicin. Si extraemos un elemento, ant se mueve una posicin en el sentido de las
agujas del reloj, formando una cola vacia. Por tanto una cola vacia tiene post a una posicin de
ant en el sentido de las agujas del reloj, que es exactamente la misma posicin relativa que
cuando la cola tenia MAX_LONG elementos. Por tanto vemos que an cuando la matriz tenga
MAX_LONG casillas, no podemos hacer crecer la cola ms all de MAX_LONG-1 casillas, a
menos que introduzcamos un mecanismo para distinguir si la cola est vaca o llena.
Ahora escribimos las primitivas de las colas usando esta representacin para una cola:

typedef struct {
tElemento *elementos;
int Lmax;
int ant,post;
} tipocola;

typedef tipocola *cola;

cola CREAR (int tamanoMax)


{
cola C;

C = (cola) malloc(sizeof(tipocola));
if (C == NULL)
error("No hay memoria.");
C->Lmax = tamanoMax+1;
C->ant = 0;
C->post = C->Lmax-1;
C->elementos = (tElemento *) calloc((tamanoMax+1), sizeof(tElemento));
34
Manual del Alumno
if (C->elementos == NULL)
error("No hay memoria.");
return C;
}

void DESTRUIR (cola *C)


{
free(*C->elementos);
free(*C);
*C == NULL;
}

int VACIA (cola C)


{
return((C->post+1)%(C->Lmax) == C->ant)
}

tElemento FRENTE (cola C)


{
if (VACIA(C))
error("Cola vacia.");
return(C->elementos[C->ant]);
}

void PONER_EN_COLA (tElemento x,cola C)


{
if ((C->post+2) % (C->Lmax) == C->ant)
error("Cola llena.");
C->post = (C->post+1) % (C->Lmax);
C->elementos[C->post] = x;
}
35
Manual del Alumno

void QUITAR_DE_COLA (cola C)


{
if (VACIA(C))
error("Cola vacia.");
C->ant = (C->ant+1) % (C->Lmax);
}

En esta implementacin podemos observar que se reserva una posicn ms que la


especificada en el parametro de la funcin CREAR. La razn de hacerlo es que no se podrn
ocupar todos los elementos de la matriz ya que debemos distinguir la cola llena de la cola
vaca. Estas dos situaciones por lo tanto vienen representadas tal y como se muestra en la
figura siguiente.

Se puede observar en el caso de la cola llena en la figura como la posicin siguiente a post no
es usada y por lo tanto es necesario crear una matriz de un tamao N+1 para tener una
capacidad para almacenar N elementos en cola.
36
Manual del Alumno

LISTAS DOBLEMENTE ENLAZADAS

1. INTRODUCCIN.

En algunas aplicaciones podemos desear recorrer la lista hacia adelante y hacia atrs, o dado
un elemento, podemos desear conocer rpidamente los elementos anterior y siguiente. En
tales situaciones podramos desear darle a cada celda sobre una lista un puntero a las celdas
siguiente y anterior en la lista tal y como se muestra en la figura.

Otra ventaja de las listas doblemente enlazadas es que podemos usar un puntero a la celda
que contiene el i-simo elemento de una lista para representar la posicin i, mejor que usar el
puntero a la celda anterior aunque lgicamente, tambin es posible la implementacin similar a
la expuesta en las listas simples haciendo uso de la cabecera. El nico precio que pagamos
por estas caractersticas es la presencia de un puntero adicional en cada celda y
consecuentemente procedimientos algo ms largos para algunas de las operaciones bsicas
de listas. Si usamos punteros (mejor que cursores) podemos declarar celdas que consisten en
un elemento y dos punteros a travs de:

typedef struct celda{


tipoelemento elemento;
struct celda *siguiente,*anterior;
}tipocelda;

typedef tipocelda *posicion;

Un procedimiento para borrar un elemento en la posicin p en una lista doblemente enlazada


es:

void borrar (posicion p)


37
Manual del Alumno
{
if (p->anterior != NULL)
p->anterior->siguiente = p->siguiente;
if (p->siguiente != NULL)
p->siguiente->anterior = p->anterior;
free(p);
}

El procedimiento anterior se expresa de forma grfica en como muestra la figura:

Donde los trazos contnuos denotan la situacin inicial y los punteados la final. El ejemplo visto
se ajusta a la supresin de un elemento o celda de una lista situada en medio de la misma.
Para obviar los problemas derivados de los elementos extremos (primero y ltimo) es prctica
comn hacer que la cabecera de la lista doblemente enlazada sea una celda que efectivamente
complete el crculo, es decir, el anterior a la celda de cabecera sea la ltima celda de la lista y
la siguiente la primera. De esta manera no necesitamos chequear para NULL en el anterior
procedimiento borrar.
Por consiguiente, podemos realizar una implementacin de listas doblemente enlazadas con
cabecera tal que tenga una estructura circular en el sentido de que dado un nodo y por medio
de los punteros siguiente podemos volver hasta l como se puede observar en la figura (de
forma analoga para anterior).

Es importante notar que aunque la estructura fsica de la lista puede hacer pensar que
mediante la operacin siguiente podemos alcanzar de nuevo un nodo de la lista, la estructura
lgica es la de una lista y por lo tanto habr una posicin primero y una posicin fin de forma
que al aplicar una operacin anterior o siguiente respectivamente sobre estas posiciones el
resultado ser un error.
Respecto a la forma en que trabajarn las funciones de la implementacin que proponemos
hay que hacer constar los siguientes puntos:

La funcin de creacin debe alojar memoria para la cabecera y hacer que los punteros
siguiente y anterior apunten a ella, devolviendo un puntero a dicha cabecera.
38
Manual del Alumno
La funcin primero(l) devolver un puntero al nodo siguiente a la cabecera.

La funcin fin(l) devolvera un puntero al nodo cabecera.

Trabajar con varias posiciones simultneamente tendr un comportamiento idntico al


de las listas enlazadas excepto respecto al problema referente al borrado cuando se
utilizan posiciones consecutivas. Es posible implementar la funcin de borrado de tal
forma que borrar un elemento de una posicin p invalida el valor de dicha posicin p y
no afecta a ninguna otra posicin. Nosotros en nuestra implementacin final optaremos
por pasar un puntero a la posicin para el borrado de forma que la posicin usada
quede apuntando al elemento siguente que se va a borrar al igual que ocurra en el
caso de las listas simples. Otra posible solucin puede ser que la funcin devuelva la
posicin del elemento siguiente a ser borrado.

La insercin se debe hacer a la izquierda del nodo apuntado por la posicin ofrecida a
la funcin insertar. Esto implica que al contrario que en las listas simples, al insertar un
nodo, el puntero utilizado sigue apuntando al mismo elemento al que apuntaba y no al
nuevo elemento insertado. Si se desea, es posible modificar la funcin de forma que se
pase un puntero a la posicin de insercin para poder modificarla y hacer que apunte al
nuevo elemento insertado. En cualquier caso, el comportamiento final de la funcin
deber quedar reflejado en el conjunto de especificaciones del TDA.

2. OPERACIONES PRIMITIVAS DE LISTAS DOBLES.


Dentro del tipo abstracto de listas doblemente enlazadas podemos proponer las siguientes
primitivas:

tLista crear ()
void destruir (tLista l)
tPosicion primero (tLista l)
tPosicion fin (tLista l)
void insertar (tElemento x, tPosicion p, tLista l)
void borrar (tPosicion *p, tLista l)
tElemento elemento(tPosicion p, tLista l)
tPosicion siguiente (tPosicion p, tLista l)
tPosicion anterior (tPosicion p, tLista l)
tPosicion posicion (tElemento x, tLista l)

ESPECIFICACIN SEMANTICA Y SINTACTICA.

tLista crear ()
Argumentos: Ninguno.
Efecto: (Constructor primitivo). Crea un objeto del tipo tLista.

void destruir (tLista l)


Argumentos: Una lista.
Efecto: Destruye el objeto l liberando los recursos que empleaba. Para volver a usarlo
habr que crearlo de nuevo.

tPosicion primero (tLista l)


Argumentos: Una lista.
Efecto: Devuelve la posicin del primer elemento de la lista.

tPosicion fin (tLista l)


Argumentos: Una lista.
Efecto: Devuelve la posicin posterior al ltimo elemento de la lista.
39
Manual del Alumno
void insertar (tElemento x, tPosicion p, tLista l)
Argumentos:

l: Es modificada.
p: Es una posicin vlida para la lista l.
x: Direccin vlida de un elemento del tipo T con que se instancia la
lista, distinta de NULL.

Efecto: Inserta elemento x en la posicin p de la lista l desplazando todos los dems


elementos en una posicin.

void borrar (tPosicion *p, tLista l)


Argumentos:

l: Es modificada.
p: Es una posicin vlida para la lista l.

Efecto: Elimina el elemento de la posicin p de la lista l desplazando todos los dems


elementos un una posicin.

tElemento elemento(tPosicion p, tLista l)


Argumentos:

l: Una lista.
p: Es una poscin vlida de la lista l.

Efecto: Devuelve el elemento que se encuentra en la posicin p de la lista l.

tPosicion siguiente (tPosicion p, tLista l)


Argumentos:

l: Una lista.
p: Es una posicin vlida para la lista l, distinta de fin(l).

Efecto: Devuelve la posicin siguiente a p en l.

tPosicion anterior (tPosicion p, tLista l)


Argumentos:

l: Una lista.
p: Es una posicin vlida para la lista l, distinta de primero(l).

Efecto: Devuelve la posicin que precede a p en l.

tPosicion posicion (tElemento x, tLista l)


Argumentos:

l: Una lista.
x: Direccin vlida de un elemento del tipo T con que se instancia la
lista, distinta de NULL.

Efecto: Si x se encuentra entre los elementos de la lista l, devuelve la posicin de su


primera ocurrencia. En otro caso, devuelve la posicin fin(l).
40
Manual del Alumno

3. EFICIENCIA.
Comparacin de la eficiencia para las distintas implementaciones de las listas:

4. IMPLEMENTACIN DE LISTAS DOB. ENLAZADAS.


Una vez aclaradas las posibles ambigedades y dudas que se pueden plantear, la
implementacin de las listas doblemente enlazadas quedara como sigue:

typedef struct celda {


tElemento elemento;
struct celda *siguiente,*anterior;
} tipocelda;

typedef tipocelda *tPosicion;


typedef tipocelda *tLista;

static void error(char *cad)


{
fprintf(stderr, "ERROR: %s\n", cad);
exit(1);
}

tLista Crear()
{
tLista l;

l = (tLista)malloc(sizeof(tipocelda));
if (l == NULL)
Error("Memoria insuficiente.");
l->siguiente = l->anterior = l;
return l;
}
41
Manual del Alumno
void Destruir (tLista l)
{
tPosicion p;

for (p=l, l->anterior->siguiente=NULL; l!=NULL; p=l) {


l = l->siguiente;
free(p);
}
}

tPosicion Primero (tLista l)


{

return l->siguiente;
}

tPosicion Fin (tLista l)


{

return l;
}

void Insertar (tElemento x, tPosicion p, tLista l)


{
tPosicion nuevo;

nuevo = (tPosicion)malloc(sizeof(tipocelda));
if (nuevo == NULL)
Error("Memoria insuficiente.");
nuevo->elemento = x;
nuevo->siguiente = p;
nuevo->anterior = p->anterior;
p->anterior->siguiente = nuevo;
p->anterior = nuevo;
}

void Borrar (tPosicion *p, tLista l)


{
tPosicion q;

if (*p == l){
Error("Posicion fin(l)");
}
q = (*p)->siguiente;
(*p)->anterior->siguiente = q;
q->anterior = (*p)->anterior;
free(*p);
(*p) = q;
}

tElemento elemento(tPosicion p, tLista l)


{

if (p == l){
Error("Posicion fin(l)");
}
return p->elemento;
}
42
Manual del Alumno

tPosicion siguiente (tPosicion p, tLista l)


{

if (p == l){
Error("Posicion fin(l)");
}
return p->siguiente;
}

tPosicion anterior( tPosicion p, tLista l)


{

if (p == l->siguiente){
Error("Posicion primero(l)");
}
return p->anterior;
}

tPosicion posicion (tElemento x, tLista l)


{
tPosicion p;
int encontrado;

p = primero(l);
encontrado = 0;
while ((p != fin(l)) && (!encontrado))
if (p->elemento == x)
encontrado = 1;
else
p = p->siguiente;
return p;
}

MULTILISTAS

1. TDA FRENTE A ESTRUCTURA DE DATOS.

Tipo de Dato Abstracto (TDA): Modelo formal de un ente junto con un conjunto de
operaciones definidas sobre el modelo que nos permite procesarlo.

Estructuras de Datos: Organizacin lgica de la informacin con que representamos


los Datos.
43
Manual del Alumno

2. ENTIDADES Y RELACIONES.
Tipos de Relacin:

Uno a uno (Ejemplo: Nombre <--> D.N.I.).


Uno a muchos (Ejemplo: Equipo <-->> Jugador).
Muchos a muchos (Ejemplo: Alumno <<-->> Asignatura).

Representacin de relaciones muchos a muchos.

Matriz.
Listas.
Multilistas.

3. ESTRUCTURA DE DATOS MULTILISTA

Conjunto de nodos en que algunos tienen ms de un puntero y pueden estar en ms


de una lista simultneamente.
Para cada tipo de nodo es importante distinguir los distintos campos puntero para
realizar los recorridos adecuados y evitar confusiones.
Estructura bsica para Sistemas de Bases de Datos en Red.

4. IMPLEMENTACIN DE MULTILISTAS
Dados dos tipos de entidades, TipoA y TipoB, se necesitan:

Dos nuevos tipos correspondientes a los nodos para cada clase de


entidad, que junto con la informacin propia de la entidad incluye los
punteros necesarios para mantener la estructura.

typedef struct NodoTipoA {


TipoA Info;
NodoRelacion *PrimerB;
} NodoTipoA;

typedef struct NodoTipoB{


TipoB Info;
NodoRelacion *PrimerA;
} NodoTipoB;

Una estructura para agrupar los objetos de cada tipo de entidad (Array,
Lista,rbol, Tabla Hash, ...).
Un TDA Nodo Relacion que incluye un puntero por cada lista as como
informacin propia de la relacin.

typedef struct NodoRelacion {


NodoTipoA *SiguienteA;
NodoTipoB *SiguienteB;
<tipo1> campo1;
........
<tipon> campo_n;
44
Manual del Alumno
} NodoRelacion;

Un nodo Multilista que engloba los distintos tipos de nodos (entidad A,


entidad B y relacin). El tipo de dato para construir esto es el registro
variante:

typedef enum {NODO_A, NODO_B, NODO_ML} TipoNodo;

typedef struct NodoMultilista {


TipoNodo tipo;
union {
NodoTipoA a;
NodoTipoB b;
NodoRelacion nr;
} cont;
} NodoMultilista;

5. CONSULTA SOBRE UNA ESTRUCTURA MULTILISTA.


Localizar todas las entidades de TipoA relacionadas con la entidad B de TipoB.

void BuscarEntidadesA (EntidadB B){

NodoMultilista a, b, r;

b = Direccion(B);
/* Depende de como se agrupen los NodoTipoB. */
r = b.cont.b.PrimerA;
/* Mediante r se recorre el conjunto de entidades TipoA para B. */
while (r.tipo == NODO_ML) {
a = r;
do
a = a.cont.nr.SiguienteB;
while (a.tipo == NODO_ML)
Escribe(a.cont.a.Info);
r = r.cont.nr.SiguienteA;
};
};
45
Manual del Alumno

6. TDA RELACIN.
TDA Relacion: crear, aadeAlum, aadeAsig, borrarAlum, borrarAsig, aadir, borrar, existe,
escribeAsig, escribeAlum, destruir.
Definicin: Dados los TDAs Alumno y Asignatura, los objetos Relacion representan las
relaciones (matrcula, calificacin) entre un conjunto de Alumnos y un conjunto de Asignaturas.

Son objetos mutables.


Residen en memoria dinmica.

OPERACIONES:

Relacion crear(int NumAlum, int NumAsig)


Argumentos:

NumAlum: Nmero mximo de alumnos.


NumAsig: Nmero mximo de asignaturas.

Efecto: (Constructor Primitivo): Crea un objeto del tipo Relacion, que representa las
matrculas de hasta un mximo de NumAlum alumnos en un mximo de NumAsig
asignaturas.Devuelve un objeto vaco, sin alumnos, asignaturas, ni vnculos entre
stos.

void aadeAlum(Alumno al, Relacion r)


Argumentos:

al: Un alumno.
r: El nmero de alumnos debe ser menor del mximo. Es modificada.

Efecto: Aade el alumno al a la relacin r, sin establecer ningn vnculo con las
asignaturas.

void aadeAsig(Asignatura as, Relacion r)


Argumentos:
46
Manual del Alumno
as: Una asignatura.
r: El nmero de asignaturas debe ser menor del mximo.Es
modificada.

Efecto: Aade la asignatura as a la relacin r, sin establecer ningn vnculo con los
alumnos.

void borrarAlum(Alumno al, Relacion r)


Argumentos:

al: Alumno que debe existir en r.


r: Es modificada.

Efecto: Elimina todos los vnculos del alumno al con asignaturas en r. Despus elimina
el alumno de r.

void borrarAsig(Asignatura as, Relacion r)


Argumentos:

as: Asignatura que debe existir en r.


r: Es modificada.

Efecto: Elimina todos los vnculos de la asignatura as con alumnos en r. Despus


elimina la asignatura de r.

void aadir(Alumno al, Asignatura as, Relacion r)


Argumentos:

al: Alumno que debe existir en r.


as: Asignatura que debe existir en r.
r: Es modificada.

Efecto: Establece un vnculo entre el alumno al y la asignatura as.

logico existe(Alumno al, Asignatura as, Relacion r)


Argumentos:

al: Alumno que debe existir en r.


as: Asignatura que debe existir en r.
r: Una relacin.

Efecto: Si en r existe un vnculo entre el alumno al y la asignatura as, devuelve


VERDAD.En otro caso, devuelve FALSO.

void borrar(Alumno al, Asignatura as, Relacion r)


Argumentos:

al: Alumno que debe existir en r.


as: Asignatura que debe existir en r.
r: Es modificada.

Efecto: Si existe un vnculo entre el alumno al y la asignatura as, es eliminado.

void escribeAsignaturas(Alumno al, relacion r)


Argumentos:
47
Manual del Alumno
al: Alumno que debe existir en r.
r: Una relacin.

Efecto:Escribe en la salida estndar una lista de las asignaturas relacionadas con el


alumno al en r.

void destruir(Relacion r)
Argumentos: r: Es modificada.
Efecto Destruye el objeto r liberando los recursos que empleaba.Para volver a usarlo
habr que crearlo de nuevo.

typedef void *pnodo;

typedef struct NodoTipoA {


TipoA info;
pnodo *PrimerB;
} NodoTipoA;

typedef struct NodoTipoB {


TipoB info;
pnodo *PrimerA;
} NodoTipoB;

typedef struct NodoRel {


pnodo SiguienteB;
pnodo SiguienteA;
} NodoRel;

typedef enum {NODO_A, NODO_B, NODO_REL} TipoNodo;

typedef struct nodo {


TipoNodo tipo;
union {
NodoRel nr;
NodoTipoA na;
NodoTipoB nb;
} cont;
} nodo;

TABLAS HASH

1. INTRODUCCIN.

Una aproximacin a la bsqueda radicalmente diferente a las anteriores consiste en proceder,


no por comparaciones entre valores clave, sino encontrando alguna funcin h(k) que nos d
directamente la localizacin de la clave k en la tabla.
48
Manual del Alumno
La primera pregunta que podemos hacernos es si es fcil encontrar tales funciones h. La
respuesta es, en principio, bastante pesimista, puesto que si tomamos como situacion ideal el
que tal funcin d siempre localizaciones distintas a claves distintas y pensamos p.ej. en una
tabla de tamao 40 en donde queremos direccionar 30 claves, nos encontramos con que hay
30 48
40 = 1.15 * 10 posibles funciones del conjunto de claves en la tabla, y slo 40*39*11 =
41
40!/10! = 2.25 * 10 de ellas no generan localizaciones duplicadas. En otras palabras, slo 2
de cada 10 millones de tales funciones serian 'perfectas' para nuestros propsitos. Esa tarea es
factible slo en el caso de que los valores que vayan a pertenecer a la tabla hash sean
conocidas a priori. Existen algoritmos para construir funciones hash perfectas que son
utilizadas para organizar las palabras clave en un compilador de forma que la bsqueda de
cualquiera de esas palabras clave se realice en tiempo constante.

Las funciones que evitan valores duplicados son sorprendentemente dificiles de encontrar,
incluso para tablas pequeas. Por ejemplo, la famosa "paradoja del cumpleaos" asegura que
si en una reunin estn presentes 23 ms presonas, hay bastante probabilidad de que dos de
ellas hayan nacido el mismo dia del mismo mes. En otras palabras, si seleccionamos una
funcin aleatoria que aplique 23 claves a una tabla de tamao 365 la probabilidad de que dos
claves no caigan en la misma localizacin es de slo 0.4927.

En consecuencia, las aplicaciones h(k), a las que desde ahora llamaremos funciones hash,
tienen la particularidad de que podemos esperar que h( ki ) = h( kj ) para bastantes pares
distintos ( ki,kj ). El objetivo ser pues encontrar una funcin hash que provoque el menor
nmero posible de colisiones (ocurrencias de sinnimos), aunque esto es solo un aspecto del
problema, el otro ser el de disear mtodos de resolucin de colisiones cuando stas se
produzcan.

2. FUNCIONES HASH.

El primer problema que hemos de abordar es el clculo de la funcin hash que transforma
claves en localizaciones de la tabla. Ms concretamente, necesitamos una funcin que
transforme claves(normalmente enteros o cadenas de caracteres) en enteros en un rango
[0..M-1], donde M es el nmero de registros que podemos manejar con la memoria de que
dispongamos.como factores a tener en cuenta para la eleccin de la funcin h(k) estn que
minimice las colisiones y que sea relativamente rpida y fcil de calcular, aunque la situacin
ideal sera encontrar una funcin h que generara valores aleatorios uniformemente sobre el
intervalo [0..M-1]. Las dos aproximaciones que veremos estn encaminadas hacia este objetivo
y ambas estn basadas en generadores de nmeros aleatorios.

Hasing Multiplicativo.

Esta tcnica trabaja multiplicando la clave k por s misma o por una constante, usando despus
alguna porcin de los bits del producto como una localizacin de la tabla hash.

Cuando la eleccin es multiplicar k por s misma y quedarse con alguno de los bits centrales, el
mtodo se denomina el cuadrado medio. Este metodo an siendo simple y pudiendo cumplir el
criterio de que los bits elegidos para marcar la localizacin son funcin de todos los bits
originales de k, tiene como principales inconvenientes el que las claves con muchos ceros se
reflejarn en valores hash tambin con muchos ceros, y el que el tamao de la tabla est
restringido a ser una potencia de 2.

Otro mtodo multiplicativo, que evita las restricciones anteriores consiste en calcular h(k) =
Int[M * Frac(C*k)] donde M es el tamao de la tabla y 0 <= C <= 1, siendo importante elegir C
con cuidado para evitar efectos negativos como que una clave alfabtica K sea sinnima a
otras claves obtenidas permutando los caracteres de k. Knuth (ver bibliografa) prueba que un
valor recomendable es:
49
Manual del Alumno

Hasing por Divisin.

En este caso la funcin se calcula simplemente como h(k) = k mod M usando el 0 como el
primer ndice de la tabla hash de tamao M.

Aunque la frmula es aplicable a tablas de cualquier tamao es importante elegir el valor de M


con cuidado. Por ejemplo si M fuera par, todas las claves pares (resp. impares) seran
aplicadas a localizaciones pares (resp. impares), lo que constituira un sesgo muy fuerte. Una
regla simple para elegir M es tomarlo como un nmero primo. En cualquier caso existen reglas
mas sofisticadas para la eleccin de M (ver Knuth), basadas todas en estudios toricos de
funcionamiento de los mtodos congruenciales de generacin de nmeros aleatorios.

3. RESOLUCIN DE COLISIONES.

El segundo aspecto importante a estudiar en el hasing es la resolucin de colisiones entre


sinnimos. Estudiaremos tres mtodos basicos de resolucin de colisiones, uno de ellos
depende de la idea de mantener listas enlazadas de sinnimos, y los otros dos del clculo de
una secuencia de localizaciones en la tabla hash hasta que se encuentre que se encuentre una
vaca. El anlisis comparativo de los mtodos se har en base al estudio del nmero de
localizaciones que han de examinarse hasta determinar donde situar cada nueva clave en la
tabla.

Para todos los ejemplos el tamao de la tabla ser M=13 y la funcin hash h1(k) que
utilizaremos ser:

HASH = Clave Mod M

y los valores de la clave k que consideraremos son los expuestos en la siguiente tabla:

Suponiendo que k=0 no ocurre de forma natural, podemos marcar todas las localizaciones de
la tabla, inicialmente vacas, dndoles el valor 0. Finalmente y puesto que las operaciones de
bsqueda e insercin estn muy relacionadas, se presentaran algoritmos para buscar un item
insertndolo si es necesario (salvo que esta operacin provoque un desbordamiento de la
tabla) devolviendo la localizacin del item o un -1 (NULL) en caso de desbordamiento.

Encadenamiento separado o Hasing Abierto.

La manera ms simple de resolver una colisin es construir, para cada localizacin de la tabla,
una lista enlazada de registros cuyas claves caigan en esa direccin. Este mtodo se conoce
normalmente con el nombre de encadenamiento separado y obviamente la cantidad de tiempo
requerido para una bsqueda depender de la longitud de las listas y de las posiciones
relativas de las claves en ellas. Existen variantes dependiendo del mantenimiento que
50
Manual del Alumno
hagamos de las listas de sinnimos (FIFO, LIFO, por valor Clave, etc), aunque en la mayora
de los casos, y dado que las listas individuales no han de tener un tamao excesivo, se suele
optar por la alternativa ms simple, la LIFO.

En cualquier caso, si las listas se mantienen en orden esto puede verse como una
generalizacin del mtodo de bsqueda secuencial en listas. La diferencia es que en lugar de
mantener una sola lista con un solo nodo cabecera se mantienen M listas con M nodos
cabecera de forma que se reduce el nmero de comparaciones de la bsqueda secuencial en
un factor de M (en media) usando espacio extra para M punteros. Para nuestro ejemplo y con
la alternativa LIFO, la tabla quedara como se muestra en la siguiente figura:

A veces y cuando el nmero de entradas a la tabla es relativamente moderado, no es


conveniente dar a las entradas de la tabla hash el papel de cabeceras de listas, lo que nos
conducira a otro mtodo de encadenamiento, conocido como encadenamiento interno. En este
caso, la unin entre sinnimos est dentro de la propia tabla hash, mediante campos cursores
(punteros) que son inicializados a -1 (NULL) y que irn apuntando hacia sus respectivos
sinnimos.

Direccionamiento abierto o Hasing Cerrado.

Otra posibilidad consiste en utilizar un vector en el que se pone una clave en cada una de sus
casillas. En este caso nos encontramos con el problema de que en el caso de que se produzca
una colisin no se pueden tener ambos elementos formando parte de una lista paraesa casilla.
Para solucionar ese problema se usa lo que se llama rehashing. El rehashing consiste en que
una vez producida una colisin al insertar un elemento se utiliza una funcin adicional para
determinar cual ser la casilla que le corresponde dentro de la tabla, aesta funcin la
llamaremos funcin de rehashing,rehi(k).

A la hora de definir una funcin de rehashing existen mltiples posibilidades, la ms simple


consiste en utilizar una funcin que dependa del nmero de intentos realizados para encontrar
una casilla libre en la que realizar la insercin, a este tipo de rehashing se le conoce como
hashing lineal. De esta forma la funcin de rehashing quedaria de la siguiente forma:

rehi(k) = (h(k)+(i-1)) mod M i=2,3,...


51
Manual del Alumno
En nuestro ejemplo, despus de insertar las 7 primeras claves nos aparece la tabla A, (ver la
tabla siguiente). Cuando vamos a insertar la clave 147, esta queda situada en la casilla 6,
(tabla B) una vez que no se han encontrado vacas las casillas 4 y 5. Se puede observar que
antes de la insercin del 147 haba agrupaciones de claves en las localizaciones 4,5 y 7,8, y
despus de la insercin, esos dos grupos se han unido formando una agrupacin primaria
mayor, esto conlleva que si se trata de insertar un elemento al que le corresponde algunas de
las casillas que estn al principio de esa agrupacin el proceso de rehashing tendr de recorrer
todas esas casillas con lo que se degradar la eficiencia de la insercin. Para solucionar este
problema habr que buscar un mtodo de rehashing que distribuya de la forma ms aleatoria
posible las casillas vacas.

Despues de llevar a cabo la insercin de las claves consideradas en nuestro ejemplo, el estado
de la tabla hash ser el que se puede observar en la tabla (C) en la que admas aparece el
nmero de intentos que han sido necesarios para insertar cada una de las claves.

Para intentar evitar el problema de las agrupaciones que acabamos de ver podramos utilizar la
siguiente funcin de rehashing:

rehi(k) = (h(k)+(i-1)*C) mod M C>1 y primo relativo con M

pero aunque esto evitara la formacin de agrupaciones primarias, no solventara el problema


de la formacin de agrupaciones secundarias (agrupaciones separadas por una distancia C). El
problema bsico de rehashing lineal es que para dos claves distintas que tengan el mismo
valor para la funcin hash se irn obteniendo exactamente la misma secuencia de valores al
aplicar la funcin de rehashing, cunado lo interenante seria que la secuencia de valores
obtenida por el proceso de rehashing fuera distinta. As, habr que buscar una funcin de
rehashing que cumpla las siguientes condiciones:

Sea fcilmente calculable (con un orden de eficiencia constante),


que evite la formacin de agrupaciones,
que genere una secuencia de valores distinta para dos claves distintas aunque tenga el
mismo valor de funcin hash, y por ltimo
que garantice que todas las casillas de la tabla son visitadas.
52
Manual del Alumno
si no cumpliera esto ltimo se podra dar el caso de que an quedaran casillas libres pero no
podemos insertar un determinado elemento porque los valores correspondientes a esas
casillas no son obtenidos durante el rehashing.
Una funcin de rehashing que cumple las condiciones anteriores es la funcin de rehashing
doble. Esta funcin se define de la siguiente forma:
hi(k) = (hi-1(k)+h0(k)) mod M i=2,3,...
con h0(k) = 1+k mod (M-2) y h1(k) = h(k).
Existe la posibilidad de hacer otras elecciones de la funcin h0(k) siempre que la funcin
escogida no sea constante.
Esta forma de rehashing doble es particularmente buena cuando M y M-2 son primos relativos.
Hay que tener en cuenta que si M es primo entonces es seguro que M-2 es primo relativo suyo
(exceptuando el caso trivial de que M=3).
El resultado de aplicar este mtodo a nuestro ejemplo puede verse en las tablas siguientes. En
la primera se incluyen los valores de h para cada clave y en la segunda pueden verse las
localizaciones finales de las claves en la tabla as como las pruebas requeridas para su
insercin.
53
Manual del Alumno

4. BORRADOS Y REHASING.
Cuando intentamos borrar un valor k de una tabla que ha sido generada por direccionamiento
abierto, nos encontramos con un problema. Si k precede a cualquier otro valor k en una
secuencia de pruebas, no podemos eliminarlo sin ms, ya que si lo hiciramos, las pruebas
siguientes para k se encontrarian el "agujero" dejado por k por lo que podramos concluir que k
no est en la tabla, hecho que puede ser falso.Podemos comprobarlo en nuestro ejemplo en
cualquiera de las tablas. La solucin es que necesitamos mirar cada localizacin de la tabla
hash como inmersa en uno de los tres posibles estados: vacia, ocupada o borrada, de forma
que en lo que concierne a la busqueda, una celda borrada se trata exectamente igual que una
ocupada.En caso de inserciones, podemos usar la primera localizacin vacia o borrada que se
encuentre en la secuencia de pruebas para realizar la operacin. Observemos que este
problema no afecta a los borrado de las listas en el encadenamiento separado. Para la
implementacin de la idea anterior podria pensarse en la introduccin en los algortmos de un
valor etiqueta para marcar las casillas borradas, pero esto sera solo una solucin parcial ya
que quedara el problema de que si los borrados son frecuentes, las bsquedas sin
xitopodran requerir O(M) pruebas para detectar que un valor no est presente.
Cuando una tabla llega a un desbordamiento o cuando su eficiencia baja demasiado debido a
los borrados, el nico recurso es llevarla a otra tabla de un tamao ms apropiado, no
necesariamente mayor, puesto que como las localizaciones borradas no tienen que
reasignarse, la nueva tabla podra ser mayor, menor o incluso del mismo tamao que la
original. Este proceso se suele denominar rehashing y es muy simple de implementar si el arca
de la nueva tabla es distinta al de la primitiva, pero puede complicarse bastante si deseamos
hacer un rehashing en la propia tabla.

5. EVALUACIN DE LOS MTODOS DE RESOLUCIN.


El aspecto ms significativo de la bsqueda por hashing es que si eficiencia depende del
denominado factor de almacenamiento = n/M con n el nmero de items y M el tamao de la
tabla.
Discuteremos el nmero medio de pruebas para cada uno de los mtodos que hemos visto de
resolucin de colisiones, en trminos de BE (bsqueda con xito) y BF (bsqueda sin xito).
Las demostraciones de las frmulas resultantes pueden encontrarse en Knuth (ver bibliografa).

Encadenamiento separado.
54
Manual del Alumno
Aunque puede resultar engaoso comparar este mtodo con los otros dos, puesto que en este
caso puede ocurrir que >1, las frmulas paroximadas son:

Estas expresiones se aplican incluso cuando >>1, por lo que para n>>M, la longitud media de
cada lista ser , y deberia esperarse en media rastrear la mitad de la lista, antes de encontrar
un determinado elemento.

Hasing Lineal.
Las frmulas aproximadas son:

Como puede verse, este mtodo, aun siendo satisfactorio para pequeos, es muy pobre
cuando -> 1, ya que el lmite de los valores medios de BE y BF son respectivamente:

En cualquier caso, el tamao de la tabla en el hash lineal es mayor que en el encadenamiento


separado, pero la cantidad de memoria total utilizada es menor al no usarse punteros.

Hasing Doble.
Las frmulas son ahora:
BE=-(1/1-) * ln(1-)

BF=1/(1-)
con valores medios cuando -> 1 de M y M/2, respectivamente.
Para facilitar la comprensin de las frmulas podemos construir una tabla en la que las
evaluemos para distintos valores de :

La eleccin del mejor mtodo hash para una aplicacin particular puede no ser fcil. Los
distintos mtodos dan unas caractersticas de eficiencia similares. Generalmente, lo mejor es
usar el encadenamiento separado para reducir los tiempos de bsqueda cuando el nmero de
registros a procesar no se conoce de antemano y el hash doble para buscar claves cuyo
nmero pueda, de alguna manera, predecirse de antemano.
En comparacin con otras tcnicas de bsqueda, el hashing tiene ventajas y desventajas. En
general, para valores grandes de n (y razonables valores de ) un buen esquema de hashing
requiere normalmente menos pruebas (del orden 1.5 - 2) que cualquier otro mtodo de
bsqueda, incluyendo la bsqueda en rboles binarios. Por otra parte, en el caso peor, puede
comportarse muy mal al requerir O(n) pruebas. Tambin puede considerarse como una ventaja
el hecho de que debemos tener alguna estimacin a priori de nmero mximo de items que
vamos a colocar en la tabla aunque si no disponemos de tal estimacin siempre nos quedara
la opcin de usar el metodo de encadenamiento separado en donde el desbordamiento de la
tabla no constituye ningn problema.
Otro problema relativo es que en una tabla hash no tenemos ninguna de las ventajas que
tenemos cuando manejamos relaciones ordenadas, y as p.e. no podemos procesar los items
en la tabla secuencialmente, ni concluir tras una bsqueda sin xito nada sobre los items que
55
Manual del Alumno
tienen un valor cercano al que buscamos, pero en cualquier caso el mayor problema que tener
el hashing cerrado es el de los borrados dentro de la tabla.

6. IMPLEMENTACIN DE LAS TABLAS HASH.

Implementacin de Hasing Abierto.


En este apartado vamos a realizar una implementacin simple del hasing abierto que nos
servir como ejemplo ilustrativo de su funcionamiento. Para ello supondremos un tipo de dato
char * para el cual disearemos una funcin hash simple consistente en la suma de los codigos
ASCII que componen dicha cadena.
Una posible implementacin utilizando el tipo de dato abstracto lista sera la siguiente:

#define NCASILLAS 100 /*Ejemplo de nmero de entradas en la tabla.*/

typedef tLista *TablaHash;

Para la cual podemos disear las siguientes funciones de creacin y destrucin:

TablaHash CrearTablaHash ()
{
tLista *t;
register i;

t=(tLista *)malloc(NCASILLAS*sizeof(tLista));
if (t==NULL)
error("Memoria insuficiente.");

for (i=0;i<NCASILLAS;i++)
t[i]=crear();

return t;
}

void DestruirTablaHash (TablaHash t)


{
register i;

for (i=0;i<NCASILLAS;i++)
destruir(t[i]);

free(t);
}

Como fue mencionado anteriormente la funcin hash que ser usada es:

int Hash (char *cad)


{
int valor;
unsigned char *c;

for (c=cad,valor=O;*c;c++)
56
Manual del Alumno
valor+=(int)(*c);

return(valor%NCASILLAS);
}

Y funciones del tipo MiembroHash, InsertarHash, BorrarHash pueden ser programadas:

int MiembroHash (char *cad,TablaHash t)


{
tPosicion p;
int enc;
int pos=Hash(cad);

p=primero(t[pos]);
enc=O;
while (p!=fin(t[pos]) && !enc) {
if (strcmp(cad,elemento(p,t[pos]))==O)
enc=1;
else
p=siguiente(p,t[pos]);
}

return enc;
}

void InsertarHash (char *cad,TablaHash t)


{
int pos;

if (MiembroHash(cad,t))
return;

pos=Hash(cad);
insertar(cad,primero(t[pos]),t[pos]);
}

void BorrarHash (char *cad,TablaHash t)


{
tPosicion p;
int pos=Hash(cad);

p=primero(t[pos]);
while (p!=fin(t[pos]) && !strcmp(cad,elemento(p,t[pos])))
p=siguiente(p,t[pos]));

if (p!=fin(t[pos]))
borrar(p,t[pos]);
}

Como se puede observar esta implementacin es bastante simple de forma que puede sufrir
bastantes mejoras. Se propone como ejercicio el realizar esta labor dotando al tipo de dato de
posibilidades como:

Determinacin del tamao de la tabla en el momento de creacin.


Modificacin de la funcin hash utilizada, mediante el uso de un puntero a funcin.
Construccin de una funcin que pasa una tabla hash de un tamao determinado a
otra tabla con un tamao superior o inferior.
57
Manual del Alumno
Construccin de un iterador a travs de todos los elementos de la tabla.
etc...

Implementacin de Hasing Cerrado.


En este apartado vamos a realizar una implementacin simple del hashing cerrado. Para ello
supondremos un tipo de datochar * al igual que en el apartado anterior, para el cual
disearemos la misma funcin hash.
Una posible implementacin de la estructura a conseguir es la siguiente:

#define NCASILLAS 100


#define VACIO NULL
static char * BORRADO='''';

typedef char **TablaHash;

Para la cual podemos disear las siguientes funciones de creacin y destrucin:

TablaHash CrearTablaHash ()
{
TablaHash t;
register i;

t=(TablaHash)malloc(NCASILLAS*sizeof(char *));
if (t==NULL)
error("Memoria Insuficiente.");

for (i=0;i<NCASILLAS;i++)
t[i]=VACIO;

return t;
}

void DestruirTablaHash (TablaHash t)


{
register i;

for (i=O;i<NCASILLAS;i++)
if (t[i]!=VACIO && t[i]!=BORRADO)
free(t[i]);

free t;
}

La funcin hash que ser usada es igual a la que ya hemos usado para la implementacin del
Hasing Abierto. Y funciones del tipo MiembroHash, InsertarHash, BorrarHash pueden ser
programadas tal como sigue, teniendo en cuenta que en esta implementacin haremos uso de
un rehashing lineal.

int Hash (char *cad)


{
int valor;
unsigned char *c;

for (c=cad, valor=0; *c; c++)


58
Manual del Alumno
valor += (int)*c;

return (valor%NCASILLAS);
}

int Localizar (char *x,TablaHash t)

/* Devuelve el sitio donde esta x o donde deberia de estar. */


/* No tiene en cuenta los borrados. */

{
int ini,i,aux;

ini=Hash(x);

for (i=O;i<NCASILLAS;i++) {
aux=(ini+i)%NCASILLAS;
if (t[aux]==VACIO)
return aux;
if (!strcmp(t[aux],x))
return aux;
}
return ini;
}

int Localizar1 (char *x,TablaHash t)

/* Devuelve el sitio donde podriamos poner x */

{
int ini,i,aux;

ini=Hash(x);

for (i=O;i<NCASILLAS;i++) {
aux=(ini+i)%NCASILLAS;
if (t[aux]==VACIO || t[aux]==BORRADO)
return aux;
if (!strcmp(t[aux],x))
return aux;
}

return ini;
}

int MiembroHash (char *cad,TablaHash t)


{
int pos=Localizar(cad,t);

if (t[pos]==VACIO)
return 0;
else
return(!strcomp(t[pos],cad));
}

void InsertarHash (char *cad,TablaHash t)


{
int pos;
59
Manual del Alumno
if (!cad)
error("Cadena inexistente.");

if (!MiembroHash(cad,t)) {
pos=Localizar1(cad,t);
if (t[pos]==VACIO || t[pos]==BORRADO) {
t[pos]=(char *)malloc((strlen(cad)+1)*sizeof(char));
strcpy(t[pos],cad);
} else {
error("Tabla Llena. \n");
}
}
}

void BorrarHash (char *cad,TablaHash t)


{
int pos = Localizar(cad,t);

if (t[pos]!=VACIO && t[pos]!=BORRADO) {


if (!strcmp(t[pos],cad)) {
free(t[pos]);
t[pos]=BORRADO;
}
}
}

Obviamente, esta implementacin al igual que la del hasing abierto es tambin mejorable de
forma que se propone el ejercicio de disear e implementar una versin mejorada con
posibilidades similares a las enumeradas en el apartado anterior.

ARBOLES GENERALES

1. INTRODUCCIN.

Hasta ahora las estructuras de datos que hemos estudiado eran de tipo lineal, o sea,exista una
relacin de anterior y siguiente entre los elementos que la componan(cada elemento tendr
uno anterior y otro posterior , salvo los casos de primero y ltimo).Pues bien, aqu se va a
estudiar una estructuracin de los datos ms compleja: los rboles.

Este tipo de estructura es usual incluso fuera del campo de la informtica.El lector seguramente
conoce casos como los rboles gramaticales para analizar oraciones,los rboles genealgicos
,representacin de jerarquas,etc...La estructuracin en rbol de los elementos es fundamental
dentro del campo de la informtica aplicndose en una amplia variedad de problemas como
veremos ms adelante.

En principio podemos considerar la estructura de rbol de manera intuitiva como una estructura
jerrquica.Por tanto,para estructurar un conjunto de elementos ei en rbol, deberemos escoger
uno de ellos e1 al que llamaremos raz del rbol.Del resto de los elementos se selecciona un
subconjunto e2,...,ek estableciendo una relacin padre-hijo entre la raz y cada uno de dichos
60
Manual del Alumno
elementos de manera que e1 es llamado el padre de e2,de e3,...ek y cada uno de ellos es
llamado un hijo de e1.Iterativamente podemos realizar la misma operacin para cada uno de
estos elementos asignando a cada uno de ellos un nmero de 0 o ms hijos hasta que no
tengamos ms elementos que insertar.El nico elemento que no tiene padre es e1,la raz del
rbol.Por otro lado hay un conjunto de elementos que no tienen hijos aunque s padre que son
llamados hojas.Como hemos visto la relacin de paternidad es una relacin uno a muchos.

Para tratar esta estructura cambiaremos la notacin:

Las listas tienen posiciones.Los rboles tienen nodos.


Las listas tienen un elemento en cada posicin.Los rboles tienen una etiqueta en cada
nodo (algunos autores distinguen entre rboles con y sin etiquetas.Un rbol sin
etiquetas tiene sentido aunque en la inmensa mayora de los problemas necesitaremos
etiquetar los nodos. Es por ello por lo que a partir de ahora slo haremos referencia a
rboles etiquetados).

Usando esta notacin,un rbol tiene uno y slo un nodo raz y uno o ms nodos hoja.
Desde un punto de vista formal la estructura de datos rbol es un caso particular de grafo, ms
concretamente,en la teora de grafos se denota de forma similar como rbol dirigido. A pesar
de ello,la definicin formal ms usual de rbol en ciencias de la computacin es la recursiva:

El caso bsico es un rbol con un nico nodo.Lgicamente este nodo es a la vez raz y
hoja del rbol.
Para construir un nuevo rbol a partir de un nodo nr y k rboles A1 ,A2,...,Ak de races
n1,n2,...,nk con N1,N2,...,Nk elementos cada uno establecemos una relacin padre-hijo
entre nr y cada una de las races de los k rboles.El rbol resultante de N=1 + N1 + ... +
Nk nodos tiene como raz el nodo nr, los nodos n1,n2,...,nk son los hijos de nr y el
conjunto de nodos hoja est formado por la unin de los k conjuntos hojas iniciales.
Adems a cada uno de los Ai se les denota subrboles de la raz.

Ejemplo: Consideremos el ejemplo de la siguiente figura.

Podemos observar que cada uno de los identificadores representa un nodo y la relacin padre-
hijo se seala con una lnea.Los rboles normalmente se presentan en forma descendente y se
interpretan de la siguiente forma:

E es la raz del rbol.


S1,S2,S3 son los hijos de E.
S1,D1 componen un subrbol de la raz.
D1,T1,T2,T3,D3,S3 son las hojas del rbol.
61
Manual del Alumno
etc...

Adems de los trminos introducidos consideraremos la siguiente terminologa:


1. Grado de salida o simplemente grado.Se denomina grado de un nodo al
nmero de hijos que tiene.As el grado de un nodo hoja es cero.En la figura
anterior el nodo con etiqueta E tiene grado 3.

2. Caminos.Si n1,n2,...,nk es una sucesin de nodos en un rbol tal que ni es el


padre de ni+1 para 1<=i<=k-1 ,entonces esta sucesin se llama un camino del
nodo ni al nodo nk.La longitud de un camino es el nmero de nodos menos
uno, que haya en el mismo.Existe un camino de longitud cero de cada nodo a
s mismo.Ejemplos sobre la figura anterior:
E,S2,D2,T3 es un camino de E a T3 ya que E es padre de S2,ste es
padre de D2,etc.
S1,E,S2 no es un camino de S1 a S2 ya que S1 no es padre de E.

3. Ancestros y descendientes.Si existe un camino,del nodo a al nodo b


,entonces a es un ancestro de b y b es un descendiente de a.En el ejemplo
anterior los ancestros de D2 son D2,S2 y E y sus descendientes D2,T1,T2 y
T3(cualquier nodo es a la vez ancestro y descendiente de s mismo). Un
ancestro o descendiente de un nodo,distinto de s mismo,se llama un ancestro
propio o descendiente propio respectivamente.Podemos definir en trminos de
ancestros y descendientes los conceptos de raz,hoja y subrbol:

En un rbol,la raz es el nico nodo que no tiene ancestros propios.


Una hoja es un nodo sin descendientes propios.
Un subrbol de un rbol es un nodo,junto con todos sus
descendientes.

Algunos autores prescinden de las definiciones de ancestro propio y


descendiente propio asumiendo que un nodo no es ancestro ni descendiente
de s mismo.

4. Altura.La altura de un nodo en un rbol es la longitud del mayor de los


caminos del nodo a cada hoja.La altura de un rbol es la altura de la
raz.Ejemplo: en la figura anterior la altura de S2 es 2 y la del rbol es 3.

5. Profundidad.La profundidad de un nodo es la longitud del nico camino de la


raz a ese nodo.Ejemplo: en la figura anterior la profundidad de S2 es 1.

6. Niveles.Dado un rbol de altura h se definen los niveles 0...h de manera que el


nivel i est compuesto por todos los nodos de profundidad i.

7. Orden de los nodos.Los hijos de un nodo usualmente estn ordenados de


izquierda a derecha.Si deseamos explcitamente ignorar el orden de los dos
hijos, nos referiremos a un rbol como un rbol no-ordenado.

La ordenacin izquierda-derecha de hermanos puede ser extendida para


comparar cualesquiera dos nodos que no estn relacionados por la relacin
ancestro-descendiente.La regla a usar es que si n1 y n2 son hermanos y n1 est
a la izquierda de n2, entonces todos los descendientes de n1 estn a la
izquierda de todos los descendientes de n2.
62
Manual del Alumno

RECORRIDOS DE UN RBOL.

En una estructura lineal resulta trivial establecer un criterio de movimiento por la misma
para acceder a los elementos, pero en un rbol esa tarea no resulta tan simple.No
obstante, existen distintos mtodos tiles en que podemos sistemticamente recorrer
todos los nodos de un rbol.Los tres recorridos ms importantes se denominan
preorden,inorden y postorden aunque hay otros recorridos como es el recorrido por
niveles.

Si consideramos el esquema general de un rbol tal como muestra la figura


siguiente,los recorridos se definen como sigue:

8. El listado en preorden es:


Si el rbol tiene un nico elemento, dicho elemento es el listado en
preorden.
Si el rbol tiene ms de un elemento,es decir,una estructura como
muestra la figura 2,el listado en preorden es listar el nodo raz seguido
del listado en preorden de cada uno de los subrboles hijos de
izquierda a derecha.

9. El listado en inorden es:


Si el rbol tiene un nico elemento,dicho elemento es el listado en
inorden.
Si el rbol tiene una estructura como muestra la figura 2,el listado en
inorden es listar el subrbol A1 en inorden,y listar el nodo raz seguido
del listado en inorden de cada uno de los subrboles hijos de izquierda
a derecha restantes.

10. El listado en postorden es:


Si el rbol tiene un nico elemento,dicho elemento es el listado en
postorden.
63
Manual del Alumno
Si el rbol tiene una estructura como muestra la figura 2,el listado en
postorden es listar en postorden cada uno de los subrboles hijos de
izquierda a derecha seguidos por el nodo raz.

11. El listado por niveles es: desde i=0 hasta la altura h del rbol,listar de izquierda
a derecha los elementos de profundidad i.Como podemos observar,un nodo n1
aparece antes que n2 en el listado por niveles si la profundidad de n1 es menor
que la profundidad de n2 usando el orden de los nodos definido anteriormente
para el caso en que tengan la misma profundidad.

Como ejemplo de listados veamos el resultado que se obtendra sobre el rbol A de la


figura 3.

Los resultados de los listados de preorden,postorden e inorden son los siguientes:

12. Listado preorden.


A=Ar=rAvAs=rvAuAwAs= rvuAwAs=rvuwAxAyAzAs=
rvuwxAyAzAs=rvuwxyAzAs=rvuwxyzAs
=rvuwxyzsApAq=rvuwxyzspAq=rvuwxyzspq.
13. Listado postorden.
A=Ar=AvAsr=AuAwvAsr= uAwvAsr=uAxAyAzwvAsr=
uxAyAzwvAsr=uxyAzwvAsr=uxyzwvAsr=
uxyzwvApAqsr=uxyzwvpAqsr=uxyzwvpqsr.
14. Listado inorden.
A=Ar=AvrAs=AuvAwrAs= uvAwrAs=uvAxwAyAzrAs=uvxw
AyAzrAs=uvxwyAzrAs=uvxwyzrAs= uvxwyzrApsAq=uvxwyzrpsAq=uvxwyzrpsq.

Por ltimo,el listado por niveles de este rbol es el siguiente:r,v,s,u,w,p,q,x,y,z.

Finalmente es interesante conocer que un rbol no puede,en general,recuperarse con


uno solo de sus recorridos.Por ejemplo:Dada la lista en inorden:vwyxzrtupsq,los
rboles de la figura 4 tienen ese mismo recorrido en inorden.
64
Manual del Alumno

2. UNA APLICACIN: ARBOLES DE EXPRESIN.


Una importante aplicacin de los rboles en la informtica es la representacin de rboles
sintcticos,es decir,rboles que contienen las derivaciones de una gramtica necesarias para
obtener una determinada frase de un lenguaje.
Podemos etiquetar los nodos de un rbol con operandos y operadores de manera que un rbol
represente una expresin.Por ejemplo. en la figura 5 se representa un rbol con la expresin
aritmtica (x-y)*(z/t).

Para que un rbol represente una expresin,hay que tener en cuenta que:

Cualquier hoja est etiquetada con uno y slo un operando.


Cualquier nodo interior n est etiquetado por un operador.

En los rboles de expresin,la sucesin del preorden de etiquetas nos da lo que se conoce
como la forma prefijo de una expresin, en la que el operador precede a su operando izquierdo
y su operando derecho.En el ejemplo de la figura 5,el preorden de etiquetas del rbol es *-xy/zt
.
Anlogamente,la sucesin postorden de las etiquetas de un rbol expresin nos da lo que se
conoce como la representacin postfijo de una expresin.As en el ejemplo,la expresin postfijo
del rbol es xy-zt/*.
Finalmente,el inorden de una expresin en un rbol de expresin da la expresin infijo en s
misma,pero sin parntesis.En el ejemplo,la sucesin inorden del rbol anterior es x-y*z/t.

3. EL TIPO DE DATO ABSTRACTO "ARBOL".


La estructura de rbol puede ser tratada como un tipo de dato abstracto.A continuacin
presentaremos varias operaciones sobre rboles y veremos como los algoritmos de rboles
pueden disearse en trminos de estas operaciones.Al igual que con otros TDA,existe una
gran variedad de operaciones que pueden llevarse a cabo sobre rboles.
65
Manual del Alumno
Como podremos observar,cuando se construye una instancia de este tipo,tiene al menos un
elemento, es decir,hasta ahora no hemos hablado de la existencia de un rbol vaco
.Realmente, segn la definicin que vimos,efectivamente el nmero mnimo de nodos de un
rbol es 1.En las implementaciones usaremos un valor especial ARBOL_VACIO para el caso
en que el rbol no contenga nodos,al igual que en listas existe el concepto de lista vaca.
De igual forma es necesario expresar en algunos casos que un nodo no existe para lo cual
tambin usaremos otro valor especial NODO_NULO.Un ejemplo de su uso puede ser cuando
intentemos extraer el nodo hijo a la izquierda de un nodo hoja.
A continuacin mostramos el conjunto de primitivas que nosotros consideraremos:

1. CREAR_RAIZ(u).Construye un nuevo nodo r con etiqueta u y sin hijos.Se devuelve el


rbol con raz r,es decir,un rbol con un nico nodo.
2. DESTRUIR(T).Libera los recursos que mantienen el rbol T de forma que para volver a
usarlo se debe de asignar un nuevo valor con la operacin de creacin.
3. PADRE(n,T).Esta funcin devuelve el padre del nodo n en el rbol T .Si n es la raz
,que no tiene padre,devuelve NODO_NULO(un valor que ser usado para indicar que
hemos intentado salirnos del rbol).Como precondicin n no es NODO_NULO (por
tanto T no es vaco).
.
4. HIJO_IZQDA(n,T).Devuelve el descendente ms a la izquierda en el siguiente nivel del
nodo n en el rbol T, y devuelve NODO_NULO si n no tiene hijo a la izquierda.Como
precondicin n no es NODO_NULO.
5. HERMANO_DRCHA(n,T).Devuelve el descendiente a la derecha del nodo n en el rbol
T ,definido para ser aquel nodo m con el mismo padre que n ,es decir, padre p,de tal
manera que m cae inmediatamente a la derecha de n en la ordenacin de los hijos de p
(Por ejemplo,vase el rbol de la figura 6). Devuelve NODO_NULO si n no tiene
hermano a la derecha.Como precondicin n no es NODO_NULO.

6. ETIQUETA(n,T).Devuelve la etiqueta del nodo n en el rbol T (manejaremos rboles


etiquetados,sin embargo no es obligatorio definir etiquetas para cada rbol).Como
precondicin n no es NODO_NULO.
7. REETIQUETA(e,n,T).Asigna una nueva etiqueta e al nodo n en el rbol T.Como
precondicin n no es NODO_NULO.
8. RAIZ(T).Devuelve el nodo que est en la raz del rbol T o NODO_NULO si T es el
rbol vaco.
9. INSERTAR_HIJO_IZQDA(n,Ti,T).Inserta el rbol Ti como hijo a la izquierda del nodo n
que pertenece al rbol T.Como precondicin n no es NODO_NULO y Ti no es el rbol
vaco.
66
Manual del Alumno
10. INSERTAR_HERMANO_DRCHA(n,Td,T).Inserta el rbol Td como hermano a la
derecha del nodo n que pertenece al rbol T.Como precondicin n no es NODO_NULO
y Td no es el rbol vaco.
11. PODAR_HIJO_IZQDA(n,T).Devuelve el subrbol con raz hijo a la izquierda de n del
rbol T el cual se ve privado de estos nodos.Como precondicin n no es NODO_NULO.
12. PODAR_HERMANO_DRCHA(n,T).Devuelve el subrbol con raz hermano a la
derecha de n del rbol T el cual se ve privado de estos nodos.Como precondicin n no
es NODO_NULO.

A continuacin veremos cmo implementar el TDA rbol y posteriormente implementaremos los


algoritmos de recorrido:PREORDEN,POSTORDEN,INORDEN.

IMPLEMENTACIN DE RBOLES.
UNA IMPLEMENTACIN MATRICIAL
Sea A un rbol en el cual los nodos se etiquetan 0,1,2,...,n-1,es decir,cada nodo contiene un
campo de informacin que contendr estos valores.La representacin ms simple de A que
soporta la operacin PADRE es una matriz lineal P en la cual el valor de P[i] es un valor o un
cursor al padre del nodo i.La raz de A puede distinguirse dndole un valor nulo o un valor a l
mismo como padre.Por ejemplo.,podemos usar un esquema de cursores donde P[i]=j si el nodo
j es el padre del nodo i,y P[i]=-1 (suponemos que NODO_NULO=-1) si el nodo i es la raz.La
definicin del tipo sera:

#define MAXNODOS 100 /*Por ejemplo*/


#define NODO_NULO -1

typedef int nodo; /*Indica una casilla de la matriz*/


typedef int *ARBOL;

Esta representacin usa la propiedad de los rboles de que cada nodo tiene un nico
padre.Con esta representacin el padre de un nodo puede encontrarse en tiempo constante.Un
camino hacia arriba en el rbol puede seguirse atravesando el rbol en tiempo proporcional al
nmero de nodos en el camino.Podemos soportar tambin el operador ETIQUETA aadiendo
otra matriz L ,tal que L[i] es la etiqueta del nodo i ,o haciendo que los elementos de la matriz A
sean registros consistiendo en un entero(cursor)y una etiqueta.EJEMPLO:Vase el rbol de la
figura 7:
67
Manual del Alumno

La representacin de padre por cursores no facilita las operaciones que requieren informacin
de hijos.Dado un nodo n ,es costoso determinar los hijos de n o la altura de n.Adems,la
representacin por cursores del padre no especifica el orden de los hijos de un nodo.Por
tanto,operaciones como HIJO_IZQDA y HERMANO_DRCHA no estn bien
definidas.Podramos imponer un orden artificial,por ejemplo,numerando los hijos de cada nodo
despus de numerar el padre,y numerar los hijos en orden creciente de izquierda a derecha.
Nota:Tngase en cuenta que aunque esta implementacin no parece muy adecuada, es
posible ampliarla con la utilizacin de nuevos campos de cursores.Por ejemplo:Podemos aadir
dos matrices adicionales para almacenar para cada nodo tanto el hijo a la izquierda como el
hermano a la derecha.
IMPLEMENTACIN DE RBOLES POR LISTAS DE HIJOS
Una forma til e importante de representar rboles es formar para cada nodo una lista de sus
hijos.Las listas pueden representarse por cualquier mtodo,pero como el nmero de hijos que
cada nodo puede tener puede ser variable,las representaciones por listas enlazadas son las
ms apropiadas.La figura 8 sugiere como puede representarse el rbol del ejemplo de la figura
7:
68
Manual del Alumno

Hay una matriz de celdas de cabecera indexadas por nodos ,que suponemos numerados
0,1,2,...,n-1. Cada punto de cabecera apunta a una lista enlazada de elementos que son
nodos.Los elementos sobre una lista encabezada por cabecera[i] son los hijos de i(por ejemplo,
9 y 4 son los hijos de 8).Si desarrollamos la estructura de datos que necesitamos en trminos
de un tipo de dato abstracto tLista (de nodos) y damos una implementacin particular de
listas,puede verse como las abstracciones encajan.

#include /*Definidas apropiadamente*/


#define MAXNODOS 100 /*Por ejemplo*/
#define NODO_NULO -1

typedef int nodo;


typedef struct {
tLista cabecera[MAXNODOS];
tEtiqueta etiquetas[MAXNODOS];
nodo raiz;
}ARBOL;

Suponemos que la raz de cada rbol est almacenada explcitamente en el campo raz.El -1
en el campo raz se usa para representar el rbol nulo o vaco.La siguiente funcin muestra el
cdigo para la operacin HIJO_IZQDA:

nodo HIJO_IZQDA(nodo n,ARBOL T)


{
tLista L;

L=T.cabecera[n];
if(PRIMERO(L)==FIN(L))
return NODO_NULO; /*No tiene hijos*/
else
return RECUPERA(PRIMERO(L),L); /*Recupera el primero(izqda)*/
69
Manual del Alumno
}

Las dems operaciones son tambin fciles de implementar utilizando la anterior estructura
para el tipo de dato y usando las primitivas del TDA Lista.
Nota:Las funciones PRIMERO,FIN y RECUPERA usadas en el ejemplo anterior pertenecen al
TDA Lista anteriormente estudiado.
IMPLEMENTACIN DE RBOLES BASADA EN CELDAS ENLAZADAS
Al igual que ocurre en los TDA estudiados (Listas,Pilas o Colas), un nodo puede ser declarado
de forma que la estructura del rbol pueda ir en aumento mediante la obtencin de memoria de
forma dinmica,haciendo una peticin de memoria adicional cada vez que se quiere crear un
nuevo nodo.

#define ARBOL_VACIO NULL


#define NODO_NULO NULL

typedef int tEtiqueta /*Algn tipo adecuado*/


typedef struct tipocelda{
struct tipocelda *padre,*hizqda,*herdrchaAr;
tEtiqueta etiqueta;
}*nodo;
typedef nodo tArbol;

Observemos que bajo esta implementacin cada nodo de un rbol contiene 3 punteros: padre
que apunta al padre,hizqda que apunta al hijo izquierdo y herdrcha que apunta al hermano a la
derecha del nodo.Para esta implementacin de rbol vamos a presentar las funciones
primitivas de las que hablbamos al principio.Suponemos que para referenciar el nodo i la
variable puntero apuntar a ese nodo.Suponemos tambin unas variables de tipo nodo y que la
variable T de tipo rbol apunta a la raz del rbol.

nodo PadreAr(nodo n,tArbol T)


{
return n->padre;
}

nodo HizqdaAr(nodo n,tArbol T)


{
return n->hizqda;
}

nodo HerdrchaAr(nodo n,tArbol T)


{
return n->herdrchaAr;
}

tEtiqueta EtiquetaAr(nodo n,tArbol T)


{
return n->etiqueta;
}

void ReEtiquetaAr(tEtiqueta e,nodo n,tArbol T)


{
n->etiqueta=e;
}

nodo RaizAr(tArbol T)
70
Manual del Alumno
{
return T;
}

tArbol Crea0(tEtiqueta et)


{
tArbol raiz;

raiz=(tArbol)malloc (sizeof(struct tipocelda));


if (!raiz){
error("Memoria Insuficiente.");
}
raiz->padre=NULL;
raiz->hizqda=NULL;
raiz->etiqueta=et;

return raiz;
}

void Destruir(tArbol T)
{

if(T){
destruir(T->hizqda);
destruir(T->herdrcha);
free(T);
}
}

void Insertar_hijo_izqda(nodo n,tArbol Ti,tArbol T)


{

Ti->herdrcha=n->hizqda;
Ti->padre=n;
n->hizqda=Ti;
}

void Insertar_hermano_drcha(nodo n,tArbol Td,tArbol T)


{

if(n==raizAr(T)){
error("Memoria Insuficiente.");
}
Td->herdrcha=n->herdrcha;
Td->padre=n->padre;
n->herdrcha=Td;
}

tArbol Podar_hijo_izqda(nodo n,tArbol T)


{
tArbol Taux;

Taux=n->hizqda;
if(Taux!=ARBOL_VACIO){
n->hizqda=Taux->herdrcha;
Taux->padre=NODO_NULO;
Taux->herdrcha=NODO_NULO;
}
71
Manual del Alumno
return Taux;
}

tArbol Podar_hermano_drcha(nodo n,tArbol T)


{
tArbol Taux;

Taux=n->herdrcha;
if(Taux!=ARBOL_VACIO){
n->herdrcha=Taux->herdrcha;
Taux->padre=NODO_NULO;
Taux->herdrcha=NODO_NULO;
}

return Taux;
}

Como vemos hemos implementado creaRaiz de manera que el rbol devuelto es un nico
nodo.Es posible construir en C un procedimiento con un nmero variable de parmetros:

El primero de los parmetros una etiqueta para el nodo raz.


Los restantes parmetros de tipo tArbol que se insertarn como subrboles(hijos) del
nodo raz.

Los podemos realizar mediante la implementacin de un nmero de parmetros indeterminado


y haciendo uso del tipo va_list que podemos encontrar en el fichero cabecera stdarg.h.El
procedimiento podra ser el siguiente:

tArbol CreaRaiz(tEtiqueta et,tArbol T1,...,tArbol Tn,NULL)


{
va_list ap;
nodo n,aux,raiz;

/*Reservamos memoria para el nodo raiz*/


raiz=(nodo)malloc(sizeof(struct tipocelda));
if(!raiz){
error("Memoria Insuficiente.");
}
/*Inicializamos el nodo raiz*/
raiz->padre=NULL;
raiz->hizqda=NULL;
raiz->herdrcha=NULL;
raiz->etiqueta=et;
/*Un bucle para insertar los subarboles*/
va_start(ap,et); /*Inicio de argumentos*/
for(;;){
n=(nodo)va_arg(ap,nodo);
if(n==NULL)break; /*No quedan mas hijos*/
if(raiz->hizqda)aux->herdrcha=n;
else raiz->hizqda=n;
aux=n;
aux->herdrcha=NULL;
aux->padre=raiz;
}
va_end(ap); /*Final de argumentos*/
return(tArbol)raiz;
}
72
Manual del Alumno
La llamada a la funcin tendra como parmetros una etiqueta para el nodo raz del rbol
resultante y una lista de nodos que podra ser vaca en cuyo caso el rbol que resulta tiene un
nico nodo:su raz con etiqueta et. Por ltimo,despus de dicha lista,es necesario un parmetro
adicional(NULL) que indica el final de la lista tras cuya lectura el procedimiento dejara de
aadir ms hijos al nodo raz que se est construyendo.
IMPLEMENTACIN DE LOS RECORRIDOS DE UN RBOL
Recordemos que los recorridos de un rbol pueden ser de una forma directa en Preorden,
Inorden y Postorden.A continuacin veremos la implementacin de estos tres recorridos. As
mismo,veremos un procedimiento de lectura de un rbol en preorden.
PREORDEN

1. Visitar la raz.
2. Recorrer el subrbol ms a la izquierda en preorden.
3. Recorrer el subrbol de la derecha en preorden.

Vamos a escribir dos procedimientos uno recursivo y otro no recursivo que toman un rbol y
listan las etiquetas de sus nodos en preorden.Supongamos que existen los tipos nodo y tArbol
con etiquetas del tipo tEtiqueta definidos anteriormente en la implementacin por punteros.El
siguiente procedimiento muestra un procedimiento recursivo que , dado el nodo n,lista las
etiquetas en preorden del subrbol con raz en n.

void PreordenArbol(nodo n,tArbol T)


{
Escribir(etiquetaAr(n,T));
for(n=hizqdaAr(n,T);n!=NODO_NULO;n=herdrchaAr(n,T))
PreordenArbol(n,T);
}

En esta funcin hemos supuesto que existe una rutina Escribir que tiene como parmetro de
entrada un valor de tipo tEtiqueta que se encarga de imprimir en la salida estndar.Por
ejemplo,si hemos realizado typedef int tEtiqueta la funcin podra ser la siguiente:

void Escribir(tEtiqueta et)


{
fprintf(stdout,"%d",(int)et);
}

Por otro lado,en los programas C hemos usado el operador de desigualdad entre un dato de
tipo nodo y la constante ARBOL_VACIO.Para hacerlo ms independiente de la impementacin
sera conveniente programar una funcin que podramos llamar Arbol_Vacio que se aadira
como una nueva primitiva que nos devuelve si el subrbol que cuelga del nodo es un rbol
vaco.
Para el procedimiento no recursivo,usaremos una pila para encontrar el camino alrededor del
rbol.El tipo PILA es realmente pila de nodos,es decir,pila de posiciones de nodos. La idea
bsica subyacente al algoritmo es que cuando estamos en la posicin p,la pila alojar el
camino desde la raz a p,con la raz en el fondo de la pila y el nodo p a la cabeza.El programa
tiene dos modos de operar.En el primer modo desciende por el camino ms a la izquierda en el
rbol,escribiendo y apilando los nodos a lo largo del camino,hasta que encuentra una hoja.A
continuacin el programa entra en el segundo modo de operacin en el cual vuelve hacia atrs
por el camino apilado en la pila,extrayendo los nodos de la pila hasta que se encuentra un nodo
en el camino con un hermano a la derecha.Entonces el programa vuelve al primer modo de
operacin,comenzando el descenso desde el inexplorado hermano de la derecha.El programa
comienza en modo uno en la raz y termina cuando la pila est vaca.

void PreordenArbol(tArbol T)
{
pila P; /*Pila de posiciones:tElemento de la pila es el tipo nodo*/
73
Manual del Alumno
nodo m;
P=CREAR(); /*Funcion de creacion del TDA PILA*/

m=raizAr(T);
do{
if(m!=NODO_NULO){
Escribir(etiquetaAr(n,T));
PUSH(m,P);
m=hizqdaAr(m,T);
}
else if(!VACIA(P)){
m=herdrchaAr(TOPE(P),T);
POP(P);
}
}while(!VACIA(P));

DESTRUIR(P); /*Funcion del TDA PILA*/


}

INORDEN

1. Recorrer el subrbol ms a la izquierda en inorden.


2. Visitar la raz.
3. Recorrer el subrbol del siguiente hijo a la derecha en inorden.

Vamos a escribir un procedimiento recursivo para listar las etiquetas de sus nodos en inorden.

void InordenArbol(nodo n,tArbol T)


{
nodo c;

c=hizqdaAr(n,T);
if(c!=NODO_NULO){
InordenArbol(c,T);
Escribir(etiquetaAr(n,T));
for(c=herdrchaAr(c,T);c!=NODO_NULO;c=herdrchaAr(c,T))
InordenArbol(c,T);
}
else Escribir(etiquetaAr(n,T));
}

POSTORDEN

1. Recorrer el subrbol ms a la izquierda en postorden.


2. Recorrer el subrbol de la derecha en postorden.
3. Visitar la raz.

El procedimiento recursivo para listar las etiquetas de sus nodos en postorden es el siguiente:

void PostordenArbol(nodo n,tArbol T)


{
nodo c;

for(c=hizqdaAr(n,T);c!=NODO_NULO;c=herdrchaAr(c,T))
74
Manual del Alumno
PostordenArbol(c,T);

Escribir(etiquetaAr(n,T));
}

LECTURA
A continuacin veremos un procedimiento que nos realizar la lectura de los nodos de un rbol
introducindolos en preorden.La funcin implementada se llama Lectura aunque se listan dos
funciones(la rutina Lectura2 es una funcin auxiliar que es usada por la primera).

void Lectura2(nodo n,tArbol T)


{
tEtiqueta etHijo,etHermano;
tArbol Hijo,Hermano;

fprintf(stdout,"Introduce hijo_izqda de: ");


Escribir(etiquetaAr(n,T));
Leer(&etHijo);

if(comparar(etHijo,FINAL)){
Hijo=creaRaiz(etHijo);
insertar_hijo_izqda(n,Hijo,T);
Lectura2(hizqdaAr(n,T),T);
}

fprintf(stdout,"Introduce her_drcha de: ");


Escribir(etiquetaAr(n,T));
Leer(&etHermano);

if(comparar(etHermano,FINAL)){
Hermano=creaRaiz(etHermano);
insertar_hermano_drcha(n,Hermano,T);
Lectura2(herdrchaAr(n,T),T);
}
}

tArbol Lectura()
{
tArbol T;
tEtiqueta et;

fprintf(stdout,"En caso de que no exista el hijo_izqdo o el"


"hermano_derecho introducir el valor: ");
Escribir(FINAL); /*FINAL actua de centinela*/
fprintf(stdout,"\nIntroduce la raiz del arbol: ");
Leer(&et);
T=creaRaiz(et);
Lectura2(raizAr(T),T);
}

Es interesante observar 5 puntos en esta rutina:

Hemos supuesto que existe una funcin Leer que tiene como parmetro de entrada un
puntero a una zona de memoria que almacena un valor de tipo tEtiqueta,y que sirve
para leer de la entrada estndar un dato de ese tipo y almacenarlo en dicha zona de
memoria.
75
Manual del Alumno
Existe una variable FINAL que contiene un valor para la etiqueta que "no es legal" para
indicar la inexistencia de un hijo a la izquierda y/o de un hermano a la derecha.
Suponemos que existe una funcin comparar que tiene como parmetros de entrada
dos variables de tipo tEtiqueta y que devuelve un valor entero distinto de 0 en caso de
que las variables sean distintas segn el criterio implementado en la funcin.
Las sentencias insertar_hijo_izqda(...);Lectura2(...);no son intercambiables,es decir,si
hubieramos programado esas sentencias en otro orden
(Lectura2(...);insertar_hijo_izqda(...);) la funcin de lectura no funcionara
correctamente.La comprobacin de que esta afirmacin es correcta se deja como
ejercicio al lector.
En la segunda sentencia if ocurre una situacin similar al punto anterior.
Se puede completar la rutina de lectura para que prescinda de la lectura de un posible
hermano a la derecha de la raz simplemente preguntndonos si n es la raz del rbol
T.

ARBOLES BINARIOS

1. INTRODUCCIN.

Un rbol binario puede definirse como un rbol que en cada nodo puede tener como mucho
grado 2,es decir,a lo ms 2 hijos.Los hijos suelen denominarse hijo a la izquierda e hijo a la
derecha,establecindose de esta forma un orden en el posicionamiento de los mismos.

Todas las definiciones bsicas que se dieron para rboles generales permanecen inalteradas
sin ms que hacer las particularizaciones correspondientes.En los rboles binarios hay que
tener en cuenta el orden izqda-drcha de los hijos.Por ejemplo:los rboles binarios a) y b) de la
figura 1(adoptamos el convenio de que los hijos a la izquierda son extrados extendindonos
hacia la izquierda y los hijos a la derecha a la derecha) son diferentes,puesto que difieren en el
nodo 5.El rbol c por convenio se supone igual al b) y no al a).
76
Manual del Alumno

2. EL TIPO DE DATO ABSTRACTO ARBOL BINARIO.

Para construir el TDA Arbol Binario bastara con utilizar las primitivas de los rboles generales
pero dado la gran importancia y peculiaridades que tienen este tipo de rboles, construiremos
una serie de operaciones especficas.Consideraremos las siguientes:

1. CREAR(e,Ti,Td).Devuelve un rbol cuya raz contiene la etiqueta e asignando como


hijo a la izquierda Ti y como hijo a la derecha Td.
2. DESTRUIR(T).Libera los recursos que mantienen el rbol T de forma que para volver a
usarlo se debe asignar un nuevo valor con la operacin de creacin.
3. PADRE(n,T).Esta funcin devuelve el padre del nodo n en el rbol T.En caso de no
existir,devuelve NODO_NULO.Como precondicin n no es NODO_NULO.
4. HIJO_IZQDA(n,T).Devuelve el hijo a la izquierda del nodo n en el rbol T,y devuelve
NODO_NULO si n no tiene hijo a la izquierda.Como precondicin, n no es
NODO_NULO.
5. HIJO_DRCHA(n,T).Devuelve el hijo a la derecha del nodo n en el rbol T,y devuelve
NODO_NULO si n no tiene hijo a la derecha.Como precondicin, n no es
NODO_NULO.
6. ETIQUETA(n,T).Devuelve la etiqueta del nodo n en el rbol T. Como precondicin, n
no es NODO_NULO.
7. REETIQUETA(e,n,T).Asigna una nueva etiqueta e al nodo n en el rbol T.Como
precondicin n no es NODO_NULO.
8. RAIZ(T).Devuelve el nodo que est en la raz del rbol T o NODO_NULO si T es el
rbol vaco.
9. INSERTAR_HIJO_IZQDA(n,Ti,T).Inserta el rbol Ti como hijo a la izquierda del nodo n
que pertenece al rbol T.En el caso de que existiese ya el hijo a la izquierda,la primitiva
se encarga de que sea destrudo junto con sus descendientes. Como precondiciones,Ti
no es ARBOL_VACIO y n no es NODO_NULO.
10. INSERTAR_HIJO_DRCHA(n,Td,T).Inserta el rbol Td como hijo a la derecha del nodo
n que pertenece al rbol T.En el caso de que existiese ya el hijo a la derecha,la
primitiva se encarga de que sea destrudo junto con sus descendientes.Como
precondiciones,Td no es ARBOL_VACIO y n no es NODO_NULO.
77
Manual del Alumno
11. PODAR_HIJO_IZQDA(n,T).Devuelve el subrbol con raz hijo a la izquierda de n del
rbol T el cual se ve privado de estos nodos.Como precondicin, n no es
NODO_NULO.
12. PODAR_HIJO_DRCHA(n,T).Devuelve el subrbol con raz hijo a la derecha de n del
rbol T el cual se ve privado de estos nodos.Como precondicin, n no es
NODO_NULO.

3. IMPLEMENTACIN DEL TDA ARBOL BINARIO Y DE LOS RECORRIDOS.


Vamos a realizar una implementacin mediante punteros,para la cual hay que realizar la
siguiente declaracin de tipos:

#define BINARIO_VACIO NULL


#define NODO_NULO NULL

typedef int tEtiqueta /*Algun tipo adecuado*/


typedef struct tipoceldaB{
struct tipoceldaB *padre,*hizqda,*hdrcha;
tEtiqueta etiqueta;
}*nodoB;
typedef nodoB tArbolB;

Una posible implementacin para las primitivas de rboles binarios es la siguiente:

tArbolBin Crear0(tEtiqueta et)


{
tArbolBin raiz;

raiz = (tArbolBin)malloc(sizeof(struct tipoceldaBin));


if (raiz==NULL)
error(\"Memoria Insuficiente.\");
raiz->padre = NODO_NULO;
raiz->hizda = NODO_NULO;
raiz->hdcha = NODO_NULO;
raiz->etiqueta = et;
return(raiz);
}

tArbolBin Crear2(tEtiqueta et,tArbolBin ti,tArbolBin td)


{
tArbolBin raiz;

raiz=(tarbolBin)malloc(sizeof(struct tipoceldaBin));
if(!raiz){
error("Memoria Insuficiente.");
}
raiz->padre=NULL;
raiz->hizqda=ti;
raiz->hdrcha=td;
raiz->etiqueta=et;
if(ti!=NULL)
td->padre=raiz;
78
Manual del Alumno
return raiz;
}

void Destruir(tArbolBin A)
{

if(A){
Destruir(A->hizqda);
Destruir(A->hdrcha);
free(A);
}
}

nodoBin Padre(nodoBin n,tArbolBin A)


{
return(n->padre);
}

nodoBin Hderecha(nodoBin n,tArbolBin A)


{
return(n->hdrcha);
}

nodoBin Hizquierda(nodoBin n,tArbolBin A)


{
return(n->hizqda);
}

tEtiqueta Etiqueta(nodoBin n,tArbolBin A)


{
return(n->etiqueta);
}

void ReEtiqueta(tEtiqueta e,nodoBin n,tArbolBin A)


{
n->etiqueta=e;
}

nodoBin Raiz(tArbolBin A)
{
return A;
}

void InsertarHijoIzda(nodoBin n,tArbolBin ah,tArbolBin A)


{
Destruir(n->hizqda);
n->hizqda=ah;
ah->padre=n;
}
79
Manual del Alumno
void InsertarHijoDrchaB(nodoBin n,tArbolBin ah,tArbolBin A)
{
Destruir(n->hdrcha);
n->hdrcha=ah;
ah->padre=n;
}

tArbolBin PodarHijoIzqda(nodoBin n,tArbolBin A)


{
tArbolBin Aaux;

Aaux=n->hizqda;
n->hizqda=BINARIO_VACIO;
if(Aaux)
Aaux->padre=BINARIO_VACIO;

return Aaux;
}

tArbolBin PodarHijoDrcha(nodoBin n,tArbolBin A)


{
tArbolBin Aaux;

Aaux=n->hdrcha;
n->hdrcha=BINARIO_VACIO;
if(Aaux)
Aaux->padre=BINARIO_VACIO;

return Aaux;
}

Con las cuales podemos hacer la siguiente implementacin de los recorridos en preorden,
postorden e inorden:

void PreordenArbol(nodoBin n,tArbolBin A)


{

if(n!=NODO_NULO){
Escribir(Etiqueta(n,A));
PreordenArbol(HizqdaB(n,A),A);
PreordenArbol(HdrchaB(n,A),A);
}
}

void PostordenArbol(nodoBin n,tArbolBin A)


{

if(n!=NODO_NULO){
PostordenArbol(Hizquierda(n,A),A);
PostordenArbol(Hderecha(n,A),A);
Escribir(etiquetaB(n,A));
}
}
80
Manual del Alumno
void InordenArbol(nodoBin n,tArbolBin A)
{

if(n!=NODO_NULO){
InordenArbol(Hizquierda(n,A),A);
Escribir(Etiqueta(n,A));
InordenArbol(HderechaB(n,A),A);
}
}

ARBOLES BINARIOS DE BUSQUEDA

1. INTRODUCCIN.

La bsqueda en rboles binarios es un mtodo de bsqueda simple, dinmico y eficiente


considerado como uno de los fundamentales en Ciencia de la Computacin. De toda la
terminologa sobre rboles,tan slo recordar que la propiedad que define un rbol binario es
que cada nodo tiene a lo ms un hijo a la izquierda y uno a la derecha.Para construir los
algoritmos consideraremos que cada nodo contiene un registro con un valor clave a travs del
cual efectuaremos las bsquedas.En las implementaciones que presentaremos slo se
considerar en cada nodo del rbol un valor del tipo tElemento aunque en un caso general ese
tipo estar compuesto por dos:una clave indicando el campo por el cual se realiza la
ordenacin y una informacin asociada a dicha clave o visto de otra forma,una informacin que
puede ser compuesta en la cual existe definido un orden.

Un rbol binario de bsqueda(ABB) es un rbol binario con la propiedad de que todos los
elementos almacenados en el subrbol izquierdo de cualquier nodo x son menores que el
elemento almacenado en x ,y todos los elementos almacenados en el subrbol derecho de x
son mayores que el elemento almacenado en x.

La figura 1 muestra dos ABB construidos en base al mismo conjunto de enteros:


81
Manual del Alumno

Obsrvese la interesante propiedad de que si se listan los nodos del ABB en inorden nos da la
lista de nodos ordenada.Esta propiedad define un mtodo de ordenacin similar al
Quicksort,con el nodo raz jugando un papel similar al del elemento de particin del Quicksort
aunque con los ABB hay un gasto extra de memoria mayor debido a los punteros.La propiedad
de ABB hace que sea muy simple disear un procedimiento para realizar la bsqueda. Para
determinar si k est presente en el rbol la comparamos con la clave situada en la raz, r.Si
coinciden la bsqueda finaliza con xito, si k<r es evidente que k,de estar presente,ha de ser
un descendiente del hijo izquierdo de la raz,y si es mayor ser un descendiente del hijo
derecho.La funcin puede ser codificada fcilmente de la siguiente forma:

#define ABB_VACIO NULL


#define TRUE 1
#define FALSE 0

typedef int tEtiqueta /*Algun tipo adecuado*/


typedef struct tipoceldaABB{
struct tipoceldaABB *hizqda,*hdrcha;
tEtiqueta etiqueta;
}*nodoABB;
typedef nodoABB ABB;

ABB Crear(tEtiqueta et)


{
ABB raiz;

raiz = (ABB)malloc(sizeof(struct tceldaABB));


if (raiz == NULL)
error("Memoria Insuficiente.");
raiz->hizda = NODO_NULO;
raiz->hdcha = NODO_NULO;
raiz->etiqueta = et;
return(raiz);
}

int Pertenece(tElemento x,ABB t)


{

if(!t)
return FALSE
else if(t->etiqueta==x)
return TRUE;
else if(t->etiqueta>x)
return pertenece(x,t->hizqda);
else return pertenece(x,t->hdrcha);
}

Es conveniente hacer notar la diferencia entre este procedimiento y el de bsqueda binaria.En


ste podra pensarse en que se usa un rbol binario para describir la secuencia de
comparaciones hecha por una funcin de bsqueda sobre el vector.En cambio en los ABB se
construye una estructura de datos con registros conectados por punteros y se usa esta
estructura para la bsqueda.El procedimiento de construccin de un ABB puede basarse en un
82
Manual del Alumno
procedimiento de insercin que vaya aadiendo elementos al rbol. Tal procedimiento
comenzara mirando si el rbol es vaco y de ser as se creara un nuevo nodo para el elemento
insertado devolviendo como rbol resultado un puntero a ese nodo.Si el rbol no est vacio se
busca el elemento a insertar como lo hace el procedimiento pertenece slo que al encontrar un
puntero NULL durante la bsqueda,se reemplaza por un puntero a un nodo nuevo que
contenga el elemento a insertar.El cdigo podra ser el siguiente:

void Inserta(tElemento x,ABB *t)


{

if(!(*t)){
*t=(nodoABB)malloc(sizeof(struct tipoceldaABB));
if(!(*t)){
error("Memoria Insuficiente.");
}
(*t)->etiqueta=x;
(*t)->hizqda=NULL;
(*t)->hdrcha=NULL;
} else if(x<(*t)->etiqueta)
inserta(x,&((*t)->hizqda));
else
inserta(x,&((*t)->hdrcha));
}

Por ejemplo supongamos que queremos construir un ABB a partir del conjunto de enteros
{10,5,14,7,12} aplicando reiteradamente el proceso de insercin.El resultado es el que muestra
la figura 2.

2. ANLISIS DE LA EFICIENCIA DE LAS OPERACIONES.


83
Manual del Alumno
Puede probarse que una bsqueda o una insercin en un ABB requiere O(log2n) operaciones
en el caso medio,en un rbol construido a partir de n claves aleatorias, y en el peor caso una
bsqueda en un ABB con n claves puede implicar revisar las n claves,o sea,es O(n).
Es fcil ver que si un rbol binario con n nodos est completo (todos los nodos no hojas tienen
dos hijos) y as ningn camino tendr ms de 1+log2n nodos.Por otro lado,las operaciones
pertenece e inserta toman una cantidad de tiempo constante en un nodo.Por tanto,en estos
rboles, el camino que forman desde la raz,la secuencia de nodos que determinan la
bsqueda o la insercin, es de longitud O(log2n),y el tiempo total consumido para seguir el
camino es tambin O(log2n).
Sin embargo,al insertar n elementos en un orden aleatorio no es seguro que se siten en forma
de rbol binario completo.Por ejemplo,si sucede que el primer elemento(de n situados en
orden) insertado es el ms pequeo el rbol resultante ser una cadena de n nodos donde
cada nodo,excepto el ms bajo en el rbol,tendr un hijo derecho pero no un hijo izquierdo.En
este caso,es fcil demostrar que como lleva i pasos insertar el i-simo elemento dicho proceso

de n inserciones necesita pasos o equivalentemente O(n) pasos por


operacin.
Es necesario pues determinar si el ABB promedio con n nodos se acerca en estructura al rbol
completo o a la cadena,es decir,si el tiempo medio por operacin es O(log2n),O(n) o una
cantidad intermedia.Como es difcil saber la verdadera frecuencia de inserciones slo se puede
analizar la longitud del camino promedio de rboles "aleatorios" adoptando algunas
suposiciones como que los rboles se forman slo a partir de inserciones y que todas las
magnitudes de los n elementos insertados tienen igual probabilidad.Con esas suposiciones se
puede calcular P(n),el nmero promedio de nodos del camino que va de la raz hacia algn
nodo(no necesariamente una hoja).Se supone que el rbol se form con la insercin aleatoria
de n nodos en un rbol que se encontraba inicialmente vaco,es evidente que P(0)=0 y
P(1)=1.Supongamos que tenemos una lista de n>=2 elementos para insertar en un rbol
vaco,el primer elemento de la lista,x,es igual de probable que sea el primero,el segundo o el n-
simo en la lista ordenada.Consideremos que i elementos de la lista son menores que x de
modo que n-i-1 son mayores. Al construir el rbol,x aparecer en la raz,los i elementos ms
pequeos sern descendientes izquierdos de la raz y los restantes n-i-1 sern descendientes
derechos.Esquemticamente quedara como muestra la figura 3.

Al tener tanto en un lado como en otro todos los elementos igual probabilidad se espera que los
subrboles izqdo y drcho de la raz tengan longitudes de camino medias P(i) y P(n-i-1)
respectivamente.Como es posible acceder a esos elementos desde la raz del rbol completo
es necesario agregar 1 al nmero de nodos de cada camino de forma que para todo i entre 0 y
n-1,P(n) puede calcularse obteniendo el promedio de la suma:

El primer trmino es la longitud del camino promedio en el subrbol izquierdo ponderando su


tamao.El segundo trmino es la cantidad anloga del subrbol derecho y el trmino 1/n
84
Manual del Alumno
representa la contribucin de la raz.Al promediar la suma anterior para todo i entre 1 y n se
obtiene la recurrencia:

y con unas transformaciones simples podemos ponerla en la forma:

y el resto es demostrar por induccin sobre n que P(n)<=1+4log2n.


En consecuencia el tiempo promedio para seguir un camino de la raz a un nodo aleatorio de
un ABB construido mediante inserciones aleatorias es O(log2n).Un anlisis ms detallado
demuestra que la constante 4 es en realidad una constante cercana a 1.4.
De lo anterior podemos concluir que la prueba de pertenencia de una clave aleatoria lleva un
tiempo O(log2n).Un anlisis similar muestra que si se incluyen en la longitud del camino
promedio slo aquellos nodos que carecen de ambos hijos o solo aquellos que no tienen hijo
izqdo o drcho tambin la longitud es O(log2n).
Terminaremos este apartado con algunos comentarios sobre los borrados en los ABB.Es
evidente que si el elemento a borrar est en una hoja bastara eliminarla,pero si el elemento
est en un nodo interior,eliminndolo,podramos desconectar el rbol.Para evitar que esto
suceda se sigue el siguiente procedimiento:si el nodo a borrar u tiene slo un hijo se sustituye u
por ese hijo y el ABB quedar&aacue; construido.Si u tiene dos hijos,se encuentra el menor
elemento de los descendientes del hijo derecho(o el mayor de los descendientes del hijo
izquierdo) y se coloca en lugar de u,de forma que se contine manteniendo la propiedad de
ABB.

B-RBOLES

1. INTRODUCCIN.

Los B-rboles sugieron en 1972 creados por R.Bayer y E.McCreight.El problema original
comienza con la necesidad de mantener ndices en almacenamiento externo para acceso a
bases de datos,es decir,con el grave problema de la lentitud de estos dispositivos se pretende
aprovechar la gran capacidad de almacenamiento para mantener una cantidad de informacin
muy alta organizada de forma que el acceso a una clave sea lo ms rpido posible.

Como se ha visto anteriormente existen mtodos y estructuras de datos que permiten realizar
una bsqueda dentro de un conjunto alto de datos en un tiempo de orden O(log2n). As
tenemos el caso de los rboles binarios AVL.Por qu no usarlos para organizar a travs de
ellos el ndice de una base de datos?la respuesta aparece si estudiamos ms de cerca el
problema de acceso a memoria externa.Mientras que en memoria interna el tiempo de acceso
a n datos situados en distintas partes de la memoria es independiente de las direcciones que
estos ocupen(n*cte donde cte es el tiempo de acceso a 1 dato),en memoria externa es
85
Manual del Alumno
fundamental el acceder a datos situados en el mismo bloque para hacer que el tiempo de
ejecucin disminuya debido a que el tiempo depende fuertemente del tiempo de acceso del
dispositivo externo,si disminuimos el nmero de accesos a disco lgicamente el tiempo
resultante de ejecucin de nuestra bsqueda se ve fuertemente recortado.Por consiguiente,si
tratamos de construir una estructura de datos sobre disco es fundamental tener en cuenta que
uno de los factores determinantes en el tiempo de ejecucin es el nmero total de accesos,de
forma que aunque dicho n&uacite;mero pueda ser acotado por un orden de eficiencia es muy
importante tener en cuenta el nmero real ya que el tiempo para realizar un acceso es
suficientemente alto como para que dos algoritmos pese a tener un mismo orden,puedan tener
en un caso un tiempo real de ejecucin aceptable y en otro inadmisible.

De esta forma,si construimos un rbol binario de bsqueda equilibrado en disco,los accesos a


disco sern para cargar en memoria uno de los nodos,es decir,para poder llevar a memoria una
cantidad de informacin suficiente como para poder decidir entre dos ramas.Los rboles de
mltiples ramas tienen una altura menor que los rboles binarios pues pueden contener ms de
dos hijos por nodo,adems de que puede hacerse corresponder los nodos con las pginas en
disco de forma que al realizar un nico acceso se leen un nmero alto de datos que permiten
elegir un camino de bsqueda no entre dos ramas,sino en un nmero considerablemente
mayor de ellas.Adems,este tipo de rboles hace ms fcil y menos costoso conseguir
equilibrar el rbol.

En resumen,los rboles con mltiples hijos hacen que el mantenimiento de ndices en memoria
externa sea mucho ms eficiente y es justamente ste el motivo por el que este tipo de rboles
han sido los que tradicionalmente se han usado para el mantenimiento de ndices en sistemas
de bases de datos.Lgicamente,aunque este tipo de estructuras sean ms idneas para
mantener grandes cantidades de datos en almacenamiento externo es posible construirlas de
igual forma en memoria principal,y por consiguiente pueden ser mantenidas en memoria
(mediante el uso de punteros por ejemplo)al igual que las que hemos estudiado hasta ahora.

2. B-RBOLES.
DEFINICIN.

Los B-rboles son rboles cuyos nodos pueden tener un nmero mltiple de hijos tal como
muestra el esquema de uno de ellos en la figura 1.

Como se puede observar en la figura 1,un B-rbol se dice que es de orden m si sus nodos
pueden contener hasta un mximo de m hijos.En la literatura tambin aparece que si un rbol
es de orden m significa que el mnimo nmero de hijos que puede tener es m+1(m
claves).Nosotros no la usaremos para diferenciar el caso de un nmero mximo par e impar de
claves en un nodo.

El conjunto de claves que se sitan en un nodo cumplen la condicin:


86
Manual del Alumno

de forma que los elementos que cuelgan del primer hijo tienen una clave con valor menor que
K1,los que cuelgan del segundo tienen una clave con valor mayor que K1 y menor que
K2,etc...Obviamente,los que cuelgan del ltimo hijo tienen una clave con valor mayor que la
ltima clave(hay que tener en cuenta que el nodo puede tener menos de m hijos y por
consiguiente menos de m-1 claves).

Para que un rbol sea B-rbol adems deber cumplir lo siguiente:

Todos los nodos excepto la raz tienen al menos E((m-1)/2) claves.Lgicamente para
los nodos interiores eso implica que tienen al menos E((m+1)/2) hijos.
Todas las hojas estn en el mismo nivel.

El hecho de que la raz pueda tener menos descendientes se debe a que si el crecimiento del
rbol hace que la raz se divida en dos hay que permitir dicha situacin para que los nuevos
nodos mantengan esa propiedad.En el caso de que eso ocurra en un nodo interior distinto a la
raz se soluciona propagando hacia arriba;lgicamente esta operacin no se puede realizar en
el caso de raz.
Por otro lado,con el hecho de que los nodos interiores tengan un nmero mnimo de
descendientes aseguramos que en el nivel n(nivel 1 corresponde a la raz)haya un mnimo de
n-1
2E ((m+1)/2)(el 2 es el mnimo de hijos de la raz y E((m+1)/2) el mnimo para los dems)y
teniendo en cuenta que un rbol con N claves tiene N+1 descendientes en el nivel de las
hojas,podemos establecer la siguiente desigualdad:

Resolviendo:

que nos da una cota superior del nmero de nodos a recorrer para localizar un elemento en el
rbol.

BSQUEDA EN UN B-RBOL.
Localizar una clave en un B-rbol es una operacin simple pues consiste en situarse en el nodo
raz del rbol,si la clave se encuentra ah hemos terminado y si no es as seleccionamos de
entre los hijos el que se encuentra entre dos valores de clave que son menor y mayor que la
buscada respectivamente y repetimos el proceso hasta que la encontremos.En caso de que se
llegue a una hoja y no podamos proseguir la bsqueda la clave no se encuentra en el rbol.En
definitiva,los pasos a seguir son los siguientes:

1. Seleccionar como nodo actual la raz del rbol.


2. Comprobar si la clave se encuentra en el nodo actual:
1. Si la clave est, fin.
2. Si la clave no est:
Si estamos en una hoja,no se encuentra la clave.Fin.
Si no estamos en una hoja,hacer nodo actual igual al hijo que
corresponde segn el valor de la clave a buscar y los valores de las
claves del nodo actual(i buscamos la clave K en un nodo con n
claves:el hijo izquierdo si K<K1,el hijo derecho si K>Kn y el hijo i-simo
si Ki<K<Ki+1)y volver al segundo paso.
87
Manual del Alumno

INSERCIN EN UN B-RBOL.
Para insertar una nueva clave usaremos un algoritmo que consiste en dos pasos recursivos:

1. Buscamos la hoja donde debieramos encontrar el valor de la clave de una forma


totalmente paralela a la bsqueda de sta tal como comentabamos en la seccin
anterior(si en esta bsqueda encontramos en algun lugar del rbol la clave a insertar,el
algoritmo no debe hacer nada ms).Si la clave no se encuentra en el rbol habremos
llegado a una hoja que es justamente el lugar donde debemos realizar esa insercin.
2. Situados en un nodo donde realizar la insercin si no est completo,es decir,si el
nmero de claves que existen es menor que el orden menos 1 del rbol,el elemento
puede ser insertado y el algoritmo termina.En caso de que el nodo est completo
insertamos la clave en su posicin y puesto que no caben en un nico nodo dividimos
en dos nuevos nodos conteniendo cada uno de ellos la mitad de las claves y tomando
una de stas para insertarla en el padre(se usar la mediana).Si el padre est tambin
completo,habr que repetir el proceso hasta llegar a la raz.En caso de que la raz est
completa,la altura del rbol aumenta en uno creando un nuevo nodo raz con una nica
clave.

En la figura 2 podemos observar el efecto de insertar una nueva clave en un nodo que est
lleno.

Podemos realizar una modificacin al algoritmo de forma que se retrase al mximo el momento
de romper un nodo en dos.Con ello podramos vernos beneficiados por dos razones
fundamentalmente:
88
Manual del Alumno
1. La razn ms importante para modificar as el algoritmo es que los nodos en el rbol
estn ms llenos con lo cual el gasto en memoria para mantener la estructura es
mucho menor.
2. Retrasamos el momento en que la raz llega a dividirse y por consiguiente retrasamos
el momento en que la altura del rbol aumenta.

La forma ms sencilla de realizar esta modificacin es que en el caso de que tengamos que
realizar esa divisin,antes de llevarla a cabo,comprobemos si los hermanos adyacentes tienen
espacio libre de forma que si alguno de ellos lo tiene se redistribuyen las claves que se
encuentran en el nodo actual ms las de ese hermano m&as la clave que los separa(que se
encuentra en el padre)ms la clave a insertar de forma que en el padre se queda la mediana y
las dems quedan distribuidas entre los dos nodos.
En la figura 3 podemos observar el efecto de insertar una nueva clave en un nodo que est
lleno pero con redistribucin.

BORRADO EN UN B-RBOL.
La idea para realizar el borrado de una clave es similar a la insercin teniendo en cuenta que
ahora,en lugar de divisiones,realizamos uniones.Existe un problema aadido,las claves a
borrar pueden aparecer en cualquier lugar del rbol y por consiguiente no coincide con el caso
de la insercin en la que siempre comenzamos desde una hoja y propagamos hacia arriba.La
solucin a esto es inmediata pues cuando borramos una clave que est en un nodo interior,lo
primero que realizamos es un intercambio de este valor con el inmediato sucesor en el rbol,es
decir,el hijo ms a la izquierda del hijo derecho de esa clave.
Las operaciones a realizar para poder llevar a cabo el borrado son por tanto:

1. Redistribucin:la utilizaremos en el caso en que al borrar una clave el nodo se queda


con un nmero menor que el mnimo y uno de los hermanos adyacentes tiene al menos
uno ms que ese mnimo,es decir,redistribuyendo podemos solucionar el problema.
2. Unin:la utilizaremos en el caso de que no sea posible la redistribucin y por tanto slo
ser posible unir los nodos junto con la clave que los separa y se encuentra en el
padre.

En definitiva,el algoritmo nos queda como sigue:


89
Manual del Alumno
1. Localizar el nodo donde se encuentra la clave. .
2. Si el nodo localizado no es una hoja,intercambiar el valor de la clave localizada con el
valor de la clave ms a la izquierda del hijo a la derecha.En definitiva colocar la clave a
borrar en una hoja.Hacemos nodo actual igual a esa hoja.
3. Borrar la clave.
4. Si el nodo actual contiene al menos el mnimo de claves como para seguir siendo un B-
rbol,fin.
5. Si el nodo actual tiene un nmero menor que el mnimo:
1. Si un hermano tiene ms del mnimo de claves,redistribucin y fin.
2. Si ninguno de los hermanos tiene ms del mnimo,unin de dos nodos junto
con la clave del padre y vuelta al paso 4 para propagar el borrado de dicha
clave(ahora en el padre).

3. PRIMITIVAS DE UN B-RBOL.

AB Crear0(int ne)
{
AB raiz;

raiz = (AB)malloc(sizeof(struct AB));


if (raiz == NULL)
error("Memoria Insuficiente.");
raiz->n_etiquetas = ne;
for (int i=1; i<=(ne+1); i++) {
raiz->hijos[i] = NULO;
}
return(raiz);
}

AB Crear(int ne, int eti[])


{
AB raiz;

raiz = (AB)malloc(sizeof(struct AB));


if (raiz == NULL)
error("Memoria Insuficiente.");
raiz->n_etiquetas = ne;
for (int i=1; i<=(ne+1); i++) {
raiz->hijos[i] = NULO;
}
for (int i=1; i<=lenght(eti[]); i++) {
raiz->etiquetas[i] = eti[i];
}
return(raiz);
}

int Buscar(int eti, int *nod, int *pos)


{
int i,l;

l = lenght(nod->etiquetas[]);
for(i=0;inod->etiquetas[i];i++)
;
*pos = i;
if(*posetiquetas[*pos])
return 1;
else;
90
Manual del Alumno
return 0;
}

int BuscarNodo(int eti, int *nod, int *pos)


{
int i=0, enc;

enc = Buscar(eti,&nod,&pos);
if (enc == 1)
return 1;
do {
if (etietiquetas[i] && nod->hijos[i]!=NULO)
enc = BuscarNodo(eti,&nod->hijos[i],&pos);
else
if ((etietiquetas[i+1]||nod->etiquetas[i+1]==-1)&&nod->hijos[i+1]!=-1)
enc = BuscarNodo(eti,&nod->hijos[i+1],&pos);
i++;
} while (i<lenght(nod->etiquetas[]) && enc==0);
return (enc);
}

RBOLES B*

1. INTRODUCCIN.

En 1973,Knuth propone nuevas reglas para realizar el mantenimiento de los B-rboles de


forma que no se realiza una divisin de un nodo en dos ya que eso hace que los nodos
resultantes tengan la mitad de claves,sino que se realizan divisiones de dos nodos completos a
tres de forma que los nodos resultantes tienen dos tercios del total.Por consiguiente,este tipo
de rboles son muy similares a los anteriores pero teniendo en cuenta:

1. Cada nodo tiene un mximo de m descendientes.


2. Cada nodo excepto el raz tiene al menos (2m-1)/3 hijos.
3. La raz tiene al menos dos descendientes (a menos de que sea hoja).
4. Una hoja contiene al menos E[(2m-1)/3] claves.
5. Todas las hojas aparecen en el mismo nivel.
6. Un nodo que no sea hoja con k descendientes contiene k-1 llaves.
7. Un nodo hoja contiene por lo menos E[(2m-1)/3] llaves, y no ms de m-1.

Existe un problema aadido a este tipo de rboles pues cuando la divisin de nodos se
propaga hasta la raz dividirla segn hemos visto implicara que junto a uno de sus hermanos
que estuvieran completos se crearn tres nuevos nodos llenos en dos terceras partes.Esto no
es posible pues la raz no tiene hermanos.La solucin al problema puede ser permitir que la
raz tenga un nmero superior de claves de forma que si se divide se puedan producir 3 nodos
*
cumpliendo las caractersticas de los rboles B .
Los cambios crticos entre el anterior conjunto de propiedades y el conjunto que se define para
*
un rbol B convencional estn en las reglas 2 y 6: un rbol B tiene nodos que contienen un
91
Manual del Alumno
mnimo de (2m-1)/3 llaves. Por supuesto, esta nueva propiedad afecta los procedimientos de
eliminacin y redistribucin.
*
Para realizar los procedimientos de rboles B tambin se debe abordar la cuestin de dividir la
raz, la cual, por definicin, nunca tiene hermanos. Si no existen hermanos, no es posible la
divisin de dos a tres. Knuth sugiere permitir que la raz crezca hasta un tamao mayor que los
dems nodos, de tal forma que, cuando se divida, pueda producir dos nodos cada uno lleno
casi a las dos terceras partes. Esta sugerencia tiene la ventaja de asegurar que todos los
*
nodos por debajo del nivel de la raz se adhieren a las caractersticas de los rboles B . Sin
embargo, tiene la desventaja de requerir que los procedimientos sean capaces de manejar un
nodo que sea de mayor tamao que todos los dems. Otra solucin es realizar la divisin de la
raz como una divisin convencional de uno a dos. Esta segunda solucin evita cualquier lgica
especial de manejo de nodos. Por otro lado, complica la eliminacin, la redistribucin y otros
procedimientos que deben ser sensibles al nmero mnimo de llaves permitidas en un nodo.
Tales procedimientos tendran que ser capaces de reconocer que los nodos descendientes de
la raz legalmente pueden estar solo llenos.

RBOLES B+

1. INTRODUCCIN

Los rboles B+ constituyen otra mejora sobre los rboles B,pues conservan la propiedad de
acceso aleatorio rpido y permiten adems un recorrido secuencial rpido.En un rbol B+ todas
las claves se encuentran en hojas,duplicndose en la raz y nodos interiores aquellas que
resulten necesarias para definir los caminos de bsqueda.Para facilitar el recorrido secuencial
rpido las hojas se pueden vincular,obtenindose ,de esta forma,una trayectoria secuencial
para recorrer las claves del rbol.

Su principal caracterstica es que todas las claves se encuentran en las hojas.Los rboles B+
ocupan algo ms de espacio que los rboles B,pues existe duplicidad en algunas claves.En los
rboles B+ las claves de las pginas raz e interiores se utilizan nicamente como ndices.

El orden de insercin de los diversos elementos fue: p v d e b c s a r f t q

2. BUSQUEDA EN UN RBOL B+
92
Manual del Alumno
En este caso,la bsqueda no debe detenerse cuando se encuentre la clave en la pgina raz o
en una pgina interior,si no que debe proseguir en la pgina apuntada por la rama derecha de
dicha clave.

3. INSERCIN EN UN RBOL B+

Su diferencia con el proceso de insercin en rboles B consiste en que cuando se inserta una
nueva clave en una pgina llena,sta se divide tambin en otras dos,pero ahora la primera
contendr con m/2 claves y la segunda 1+m/2, y lo que subir a la pgina antecesora ser una
copia de la clave central.

4. BORRADO EN UN RBOL B+

La operacin de borrado debe considerar:

Si al eliminar la clave(siempre en una hoja)el nmero de claves es mayor o igual a m/2


el proceso ha terminado. Las claves de las pginas raz o internas no se modifican
aunque sean una copia de la eliminada,pues siguen constituyendo un separador vlido
entre las claves de las pginas descendientes.
Si al eliminar la clave el nmero de ellas en la pgina es menor que m/2 ser necesaria
una fusin y redistribucin de las mismas tanto en las pginas hojas como en el ndice.

Arboles AVL.

1. MOTIVACIN.

Comencemos con un ejemplo: Supongamos que deseamos construir un ABB para la siguiente
tabla de datos:

El resultado se muestra en la figura siguiente:


93
Manual del Alumno
Como se ve ha resultado un rbol muy poco balanceado y con caractersticas muy pobres para
la bsqueda. Los ABB trabajan muy bien para una amplia variedad de aplicaciones, pero tienen
el problema de que la eficiencia en el peor caso es O(n). Los rboles que estudiaremos a
continuacin nos darn una idea de cmo podria resolverse el problema garantizando en el
peor caso un tiempo O(log2 n).

2. ARBOLES EQUILIBRADOS AVL.

Diremos que un rbol binario est equilibrado (en el sentido de Addelson-Velskii y Landis) si,
para cada uno de sus nodos ocurre que las alturas de sus dos subrboles difieren como mucho
en 1. Los rboles que cumplen esta condicin son denominados a menudo rboles AVL.

En la primera figura se muestra un rbol que es AVL, mientras que el de la segunda no lo es al


no cumplirse la condicin en el nodo k.
94
Manual del Alumno

A travs de los rboles AVL llegaremos a un procedimiento de bsqueda anlogo al de los ABB
pero con la ventaja de garantizaremos un caso peor de O(log2 n), manteniendo el rbol en todo
momento equilibrado. Para llegar a este resultado , podramos preguntarnos cual podra ser el
peor AVL que podramos construir con n nodos, o dicho de otra forma cuanto podramos
permitir que un rbol binario se desequilibrara manteniendo la propiedad de AVL. Para
responder a la pregunta podemos construir para una altura h el AVL Th, con mnimo nmero de
nodos. Cada uno de estos rboles mnimos debe constar de una raiz, un subrbol AVL minimo
de altura h-1 y otro subrbol AVL tambin minimo de altura h-2. Los primeros Ti pueden verse
en la siguiente figura:
95
Manual del Alumno

Es fcil ver que el nmero de nodos n(T h) est dado por la relacin de recurencia [1]:

n(Th) = 1 + n(Th-1) + n(Th-2)

Relacin similar a la que aparece en los nmeros de Fibonacci (F n = Fn-1 + Fn-2) , de forma que
la ss, de valores para n(Th) est relacionada con los valores de la ss. de Fibonacci:

AVL -> -, -, 1, 2, 4, 7, 12, ...


FIB -> 1, 1, 2, 3, 5, 8, 13, ...

es decir [2],
n(Th) = Fh+2 - 1

Resolviendo [1] y utilizando [2] llegamos tras algunos clculos a:

log2(n+1) <= h < 1.44 log2(n+2)-0.33

o dicho de otra forma, la longitud de los caminos de bsqueda (o la altura) para un AVL de n
nodos, nunca excede al 44% de la longitud de los caminos (o la altura) de un rbol
completamente equilibrado con esos n nodos. En consecuencia, an en el peor de los casos
llevara un tiempo O(log2 n) al encontrar un nodo con una clave dada.
Parece, pues, que el nico problema es el mantener siempre tras cada insercin la condicin
de equilibrio, pero esto puede hacerse muy fcilmente sin ms que hacer algunos reajustes
locales, cambiando punteros.
Antes de estudiar mas detalladamente este tipo de rboles realizamos la declaracin de tipos
siguiente:

typedef int tElemento;

typedef struct NODO_AVL {


tElemento elemento;
struct AVL_NODO *izqda;
struct AVL_NODO *drcha;
int altura;
} nodo_avl;

typedef nodo_avl *arbol_avl;


96
Manual del Alumno
#define AVL_VACIO NULL

#define maximo(a,b) ((a>b)?(a):(b))


En muchas implementaciones, para cada nodo no se almacena la altura real de dicho nodo en
el campo que hemos llamada altura, en su lugar se almacena un valor del conjunto {-1,0,1}
indicando la relacin entre las alturas de sus dos hijos. En nuestro caso almacenamos la altura
real por simplicidad. Por consiguiente podemos definir la siguiente macro:

#define altura(n) (n?n->altura:-1)


La cual nos devuelve la altura de un nodo_avl.
Con estas declaraciones la funciones de creacin y destruccin para los rboles AVLpueden
ser como sigue:

arbolAVL Crear_AVL()
{
return AVL_VACIO;
}

void Destruir_AVL (arbolAVL A)


{
if (A) {
Destruir_AVL(A->izqda);
Destruir_AVL(A->drcha);
free(A);
}
}
Es sencillo realizar la implementacin de una funcin que podemos llamar miembro que nos
devuelve si un elemento pertenece al rbol AVL. Podra ser la siguiente:

int miembro_AVL(tElemento e,arbolAVL A)


{
if (A == NULL)
return 0;

if (e == A->elemento)
return 1;
else
if (e < A->elemento)
return miembro_AVL(e,A->izqda);
else
return miembro_AVL(e,A->drcha);
}
Veamos ahora la forma en que puede afectar una insercin en un rbol AVL y la forma en que
deberiamos reorganizar los nodos de manera que siga equilibrado. Consideremos el esquema
general de la siguiente figura, supongamos que la insercin ha provocado que el subrbol que
cuelga de Ai pasa a tener una altura 2 unidades mayor que el subrbol que cuelga de Ad .
Qu operaciones son necesarias para que el nodo r tenga 2 subrboles que cumplan la
propiedad de rboles AVL?.
97
Manual del Alumno

Para responder a esto estudiaremos dos situaciones distintas que requieren 2 secuencias de
operaciones distintas:

La insercin se ha realizado en el rbol A. La operacin a realizar es la de una rotacin


simple a la derecha sobre el nodo r resultando el rbol mostrado en la siguiente figura.

La insercin se ha realizado en el rbol B. (supongamos tiene raiz b, subrbol izquierdo


B1 y subrbol derecho B2). La operacin a realizar es la rotacin doble izquierda-
derecha la cual es equivalente a realizar una rotacin simple a la izquierda sobre el
nodo Ai y despues una rotacin simple a la derecha sobre el nodo r (por tanto, el rbol
B queda dividido). El resultado se muestra en la figura siguiente:
98
Manual del Alumno

En el caso de que la insercin se realice en el subrbol Ad la situacin es la simtrica y para


las posibles violaciones de equilibrio se aplicar la misma tcnica mediante la rotacin simple a
la izquierda o la rotacin doble izquierda-derecha. Se puede comprobar que si los subrboles
Ad y Ai son rboles AVL, estas operaciones hacen que el rbol resultante tambin sea AVL.
Por ltimo, destacaremos que para realizar la implementacin definitiva en base a la
declaracin de tipos que hemos propuesto tendremos que realizar un ajuste de la altura de los
nodos involucrados en la rotacin adems del ya mencionado ajuste de punteros. Por ejemplo:
En la rotacin simple que se ha realizado en la primera de las situaciones, el campo de altura
de los nodos r y Ai puede verse modificado.
Estas operaciones bsicas de simple y doble rotacin se pueden implementar de la siguiente
forma:

void Simple_derecha(arbolAVL *A)


{
nodoAVL *p;

p = (*A)->izqda;
(*A)->izqda = p->drcha;
p->drcha = (*A);
(*A) = p;
/* Ajustamos las alturas */
p = (*A)->drcha;
p->altura = maximo(altura(p->izqda),altura(p->drcha))+1;
(*A)->altura = maximo(a1tura((*T)->izqda),altura((*T)->drcha))+1;
}

void Simple_izquierda(arbolAVL *A)


{
nodoAVL *p;

p = (*A)->drcha;
(*A)->drcha = p->izqda;
p->izqda = (*A);
(*A) = p;
/*Ajustamos las alturas */
p = (*A)->izqda;
p->altura = maximo(altura(p->izqda),altura(p->drcha))+1;
99
Manual del Alumno
(*A)->altura = maximo(altura((*A)->izqda),altura((*A)->drcha))+1;
}

void Doble_izquierda_derecha (arbolAVL *AT)


{
simple_izquierda(&((*A)->izqda));
simple_derecha(A);
}

void Doble_derecha_izquierda (arbolAVL *A)


{
simple_derecha(&((*A)->drcha));
simple_izquierda(A);
}
Obviamente, el reajuste en los nodos es necesario tanto para la operacin de insercin como
para la de borrado. Por consiguiente, se puede programar la insercin de forma que
descendamos en el rbol hasta llegar a una hoja donde insertar y despus recorrer el mismo
camino hacia arriba realizando los ajustes necesarios (igualmente en el borrado se realizara
algo similar). Para hacer ms fcil la implementacin, construiremos la funcin ajusta_avl(e,&T)
cuya misin consiste en ajustar los nodos que existen desde el nodo conteniendo la etiqueta e
hasta el nodo raiz en el rbol T. La usaremos como funcin auxiliar para implementar las
funciones de insercin y de borrado. El cdigo es el siguiente:

void ajusta_AVL (tElemento e, arbolAVL *A)


{
if (!(*A))
return;
if (e > (*A)->elemento)
ajusta_AVL(e,&((*A)->drcha));
else if (e < (*A)->elemento)
ajusta_avl(e,&((*A)->izqda));

switch (altura((*A)->izqda)-altura((*A)->drcha)) {
case 2:
if (altura((*A)->izqda->izqda) > altura((*A)->izqda->drcha))
simple_derecha(A);
else doble_izquierda_derecha(A);
break;
case -2:
if (altura((*A)->drcha->drcha) > altura((*A)->drcha->izqda))
simple_izquierda(A);
else doble_derecha_izquierda(A);
break;
default:
(*A)->altura = maximo(altura((*A)->izqda),altura((*A)->drcha))+1;
}
}
Para la operacin de insercin se deber profundizar en el rbol hasta llegar a un nodo hoja o
un nodo con un solo hijo de forma que se aade un nuevo hijo con el elemento insertado. Una
vez aadido slo resta ajustar los nodos que existen en el camino de la raz al nodo insertado.
El cdigo es el siguiente:

void insertarAVL (tElemento e, arbolAVL *A)


{
nodoAVL **p;

p=T;
while (*p!=NULL)
if ((*p)->elemento > e)
100
Manual del Alumno
p = &((*p)->izqda);
else p = &((*p)->drcha);

(*p)=(nodo_avl *)malloc(sizeof(nodoAVL));
if (!(*p))
error("Error: Memoria insuficiente.");

(*p)->elemento = e;
(*p)->altura = 0;
(*p)->izqda = NULL;
(*p)->drcha = NULL;
ajustaAVL(e,A);
}
En el caso de la operacin de borrado es un poco ms complejo pues hay que determinar el
elemento que se usar para la llamada a la funcin de ajuste. Por lo dems es muy similar al
borrado en los rboles binarios de bsqueda. En la implementacin que sigue usaremos la
variable elem para controlar el elemento involucrado en la funcin de ajuste.

void borrarAVL (tElemento e, arbolAVL *A)


{
nodoAVL **p,**aux,*dest;
tElemento elem;

p=A;
elem=e;
while ((*p)->elemento!=e) {
elem=(*p)->elemento;
if ((*p)->elemento > e)
p=&((*p)->izqda);
else p=&((*p)->drcha);
}

if ((*p)->izqda!=NULL && (*p)->drcha!=NULL) {


aux=&((*p)->drcha);
elem=(*p)->elemento;
while ((*aux)->izqda) {
elem=(*aux)->elemento;
aux=&((*aux)->izqda);
}
(*p)->elemento = (*aux)->elemento;
p=aux;
}
if ((*p)->izqda==NULL && (*p)->drcha==NULL) {
free(*p);
(*p) = NULL;
} else if ((*p)->izqda == NULL) {
dest = (*p);
(*p) = (*p)->drcha;
free(dest);
} else {
dest = (*p);
(*p) = (*p)->izqda;
free(dest);
}
ajustaAVL(elem,A);
}
101
Manual del Alumno

Arboles Binarios Parcialmente Ordenados.

1. INTRODUCCIN.

Un rbol A se dice parcialmente ordenado (APO) si cumple la condicin de que la etiqueta de


cada nodo es menor (de igual forma mayor) o igual que las etiquetas de los hijos (se supone
que el tipo_elemento base admite un orden) mantenindose adems tan balanceado como sea
posible, en el caso ptimo equilibrado.

Las operaciones bsicas en este tipo de rboles son la de insercin de un elemento y la de


borrado del elemento de menor etiqueta (la raiz) ,con la consiguiente problemtica que se
plantea al tener que dejar el rbol tras cualquier operacin tanto equilibrado como cumpliendo
la condicin de orden parcial. Un ejemplo de este tipo de rboles muestra en la siguiente figura:

2. BORRADO EN LOS APO.

Para ejecutar el borrado (y eventual almacenamiento del valor) de la raz,no se puede quitar el
nodo sin ms ya que se desconectaria la estructura. Por otro lado si se quiere mantener la
propiedad de orden parcial y el mayor balanceo posible con las hojas en el nivel ms bajo
alojadas de izquierda a derecha lo que podra hacerse es poner provisionalmente la hoja ms a
la derecha del nivel ms bajo como raz provisional. Empujaremos entonces esta raz hacia
abajo intercambindola con el hijo de etiqueta menor hasta que no podamos hacerlo ms
(porque sea ya una hoja o porque la etiqueta sea ya menor que la de cualquiera de sus hijos).

El anterior proceso aplicado a un rbol con n nodos toma un tiempo O(log2 n) puesto que en el
rbol ningn camino tiene ms de 1+log2 n nodos y el proceso de empujar hacia abajo
intercambiando con los hijos toma un tiempo constante por nodo. En la siguiente figura
podemos observar este proceso sobre un ejemplo de borrado en un APO en el cual se elimina
el nodo a para seguidamente subir a la raz el nodo g que es empujado hacia abajo.
102
Manual del Alumno
103
Manual del Alumno

3. INSERCIN EN LOS APO.

Para implementar la insercin habra que hacer unas consideraciones similares a las
anteriores. El nuevo elemento que se inserta, lo podriamos situar provisionalmente en el nivel
ms bajo tan a la izquierda como sea posible (se comienza en un nuevo nivel si el ltimo nivel
est completo). A continuacin se intercambia con su padre repitindose este proceso hasta
que se cumpla la condicin de orden parcial (bien porque ya est en la raz o porque tenga ya
una etiqueta mayor que la de su padre).

Al igual que en el borrado puede verse fcilmente que este proceso no lleva ms de O(log 2 n)
pasos. En la siguiente figura podemos ver un ejemplo de insercin en un APO.
104
Manual del Alumno
105
Manual del Alumno

4. IMPLEMENTACIN MATRICIAL DE ARBOLES APO.

El hecho de que los rboles que hemos estado considerando sean binarios, tan balanceados
como sea posible y tengan las hojas en el nivel inferior empujadas hacia la izquierda hace que
podamos usar una representacin muy usual para estos rboles llamada MONTON que,
bsicamente es un vector en el que guardamos los nodos del rbol por niveles. Si existen n
nodos, se usan las n primeras posiciones de un vector M (M[0] aloja la raz). El hijo izquierdo
del nodo en M[k], si existe, est en M[2k+1], y el hijo derecho, si existe, est en M[2k+2] ,en
consecuencia el padre de M[k] sea M[(k-1)/2], para i>0.
106
Manual del Alumno

Podemos declarar un APO de elementos de algn tipo, digamos tipoelemento, que consistira
en un vector de tipoelemento y un entero 'ltimo' indicando el ltimo elemento actual (en uso)
del vector. Asi podriamos declarar:

typedef int tElemento;

typedef struct nodoAPO {


int ultimo;
int maximo;
tElemento *apo;
} *APO;
Y la implementacin de las operaciones sera como sigue:

APO CrearAPO (int max)


{
APO A;

if (max < 1) {
error("El rbol debe tener al menos un nodo.");
}

A = (APO)malloc(sizeof(struct nodoAPO));
if (A == NULL)
error("No hay memoria suficiente.");

A->apo = (tElemento*)malloc(max*sizeof(tElemento));
if (A->apo == NULL)
error("No hay memoria suficiente.");

A->ultimo = -1;
A->maximo = max;

return A;
}
107
Manual del Alumno
void DestruirAPO (APO A)
{
free(A->apo);
free(A);
}

void InsertaAPO (tElemento el, APO A)


{
int pos;
tElemento aux;

if (A->ultimo == A->maximo-1) {

error("No caben mas elementos.");


}

A->ultimo++;
pos=A->ultimo;
A->apo[pos]=el;
/* Bucle para subir el elemento hasta su posicin. */
while ((pos>0) && (A->apo[pos] < A->apo[(pos-1)/2])) {
aux = A->apo[pos];
A->apo[pos] = A->apo[(pos-1)/2];
A->apo[(pos-1)/2] = aux;
pos = (pos-1)/2;
}
}

tElemento BorrarMinimo (APO A)


{
int pos;
int pos_min,acabar;
tElemento minimo,aux;

if (A->ultimo == -1) {
error("No hay elementos.");
}

minimo = A->apo[0];
A->apo[0] = A->apo[A->ultimo];
A->ultimo--;
if (A->ultimo <= 0) return minimo;
pos = 0;
acabar = 0;
while (pos <= (A->ultimo-1)/2 && !acabar) {
if (2*pos+1 == A->ultimo)
pos_min = 2*pos+1;
else if (A->apo[2*pos+1] < A->apo[2*pos+2])
pos_min = 2*pos+1;

else pos_min = 2*pos+2;


if (A->apo[pos] > A->apo[pos_min]) {
aux = A->apo[pos];
A->apo[pos] = A->apo[pos_min];
A->apo[pos_min] = aux;
pos = pos_min;
}
else acabar=1;
}
108
Manual del Alumno
return minimo;
}

GRAFOS

1. INTRODUCCIN.

El origen de la palabra grafo es griego y su significado etimolgico es "trazar". Aparece con


gran frecuencia como respuesta a problemas de la vida cotidiana,algunos ejemplos podran ser
los siguientes:un grfico de una serie de tareas a realizar indicando su secuenciacin (un
organigrama),grafos matemticos que representan las relaciones binarias,una red de
carreteras,la red de enlaces ferroviarios o areos o la red elctrica de una ciudad.(Vase la
figura 1).En cada caso,es conveniente representar grficamente el problema dibujando un
grafo como un conjunto de puntos(vrtices)con lneas conectndolos (arcos).

De aqu se podra deducir que un grafo es bsicamente un objeto geomtrico aunque en


realidad sea un objeto combinatorio,es decir,un conjunto de puntos y un conjunto de lneas
tomado de entre el conjunto de lneas que une cada par de vrtices.Por otro lado,y debido a su
generalidad y a la gran diversidad de formas que pueden usarse,resulta complejo tratar con
todas las ideas relacionadas con un grafo.

Para facilitar el estudio de este tipo de dato,a continuacin se realizar un estudio de la teora
de grafos desde el punto de vista de las ciencias de la computacin. Considerando que dicha
teora es compleja y amplia,aqu slo se realizar una introduccin a la misma,describindose
el grafo como un tipo de dato y mostrndose los problemas tpicos y los algoritmos que
permiten solucionarlos usando un ordenador.
109
Manual del Alumno
Los grafos son estructuras de datos no lineales que tienen una naturaleza generalmente
dinmica. Su estudio podra dividirse en dos grandes bloques:

Grafos Dirigidos.
Grafos no Dirigidos(pueden ser considerados un caso particular de los anteriores).

Un ejemplo de grafo dirigido lo constituye la red de aguas de una ciudad ya que cada tubera
slo admite que el agua la recorra en un nico sentido.Por el contrario,la red de carreteras de
un pas representa en general un grafo no dirigido,puesto que una misma carretera puede ser
recorrida en ambos sentidos.No obstante,podemos dar unas definiciones generales para
ambos tipos.
A continuacin daremos definiciones de los dos tipos de grafos y de los conceptos que llevan
asociados.

2. DEFINICIONES Y TERMINOLOGA FUNDAMENTAL.


Un grafo G es un conjunto en el que hay definida una relacin binaria,es decir,G=(V,A) tal que
V es un conjunto de objetos a los que denominaremos vrtices o nodos y es una
relacin binaria a cuyos elementos denominaremos arcos o aristas.
Dados ,puede ocurrir que:

1. , en cuyo caso diremos que x e y estn unidos mediante un arco,y


2. , en cuyo caso diremos que no lo estn.

Si las aristas tienen asociada una direccin(las aristas (x,y) y (y,x) no son equivalentes)
diremos que el grafo es dirigido,en otro caso ((x,y)=(y,x)) diremos que el grafo es no dirigido.

Conceptos asociados a grafos:

Diremos que un grafo es completo si A=VxV,o sea,si para cualquier pareja de vrtices
existe una arista que los une(en ambos sentidos si el grafo es no dirigido).El nmero de
aristas ser:
o grafos dirigidos:

o grafos no dirigidos:
110
Manual del Alumno

donde n=|V|

Un grafo dirigido es simtrico si para toda arista (x,y)perteneciente a A tambin


aparece la arista (y,x)perteneciente a A;y es antisimtrico si dada una arista (x,y)
perteneciente a A implica que (y,x) no pertenece a A.

Tanto a las aristas como a los vrtices les puede ser asociada informacin.A esta
informacin se le llama etiqueta.Si la etiqueta que se asocia es un nmero se le llama
peso,costo o longitud.Un grafo cuyas aristas o vrtices tienen pesos asociados recibe
el nombre de grafo etiquetado o ponderado.

El nmero de elementos de V se denomina orden del grafo.Un grafo nulo es un grafo


de orden cero.

Se dice que un vrtice x es incidente a un vrtice y si existe un arco que vaya de x a y


((x,y)pertenece a A),a x se le denomina origen del arco y a y extremo del mismo.De
igual forma se dir que y es adyacente a x.En el caso de que el grafo sea no dirigido si
x es adyacente(resp. incidente) a y entonces y tambin es adyacente (resp. incidente)
a x.

Se dice que dos arcos son adyacentes cuando tienen un vrtice comn que es a la vez
origen de uno y extremo del otro.

Se denomina camino (algunos autores lo llaman cadena si se trata de un grafo no


dirigido)en un grafo dirigido a una sucesin de arcos adyacentes:
C={(v1,v2),(v2,v3),...,(vn-1,vn), para todo vi perteneciente a V}

La longitud del camino es el nmero de arcos que comprende y en el caso en el que


el grafo sea ponderado se calcular como la suma de los pesos de las aristas que lo
constituyen.
Ejemplo.
o En el grafo dirigido de la figura 2,un camino que une los vrtices 1 y 4 es C=
{(1,3),(3,2),(2,1)},su longitud es 3.
o En el grafo no dirigido de la figura 2,un camino que une los vrtices 1 y 4 es
'
C = {(1,2),(2,4)}.Su longitud es 2.

Un camino se dice simple cuando todos sus arcos son distintos y se dice elemental
cuando no utiliza un mismo vrtice dos veces.Por tanto todo camino elemental es
simple y el recproco no es cierto.

Un camino se dice Euleriano si es simple y adems contiene a todos los arcos del
grafo.

Un circuito(o ciclo para grafos no dirigidos)es un camino en el que coinciden los


vrtices inicial y final.Un circuito se dice simple cuando todos los arcos que lo forman
son distintos y se dice elemental cuando todos los vrtices por los que pasa son
distintos.La longitud de un circuito es el nmero de arcos que lo componen.Un bucle
es un circuito de longitud 1(estn permitidos los arcos de la forma(i,i) y notemos que un
grafo antisimtrico carecera de ellos).
111
Manual del Alumno
Un circuito elemental que incluye a todos los vrtices de un grafo lo llamaremos circuito
Hamiltoniano.

Un grafo se denomina simple si no tiene bucles y no existe ms que un camino para


unir dos nodos.

Diremos que un grafo no dirigido es bipartido si el conjunto de sus vrtices puede ser
dividido en dos subconjuntos(disjuntos) de tal forma que cualquiera de las aristas que
componen el grafo tiene cada uno de sus extremos en un subconjunto distinto.Un grafo
no dirigido ser bipartido si y slo si no contiene ciclos con un nmero de aristas par.

' '
Dado un grafo G=(V,A),diremos que G =(V,A ) con es un grafo parcial de G y
' ' ' '
un subgrafo de G es todo grafo G =(V ,A ) con y donde A ser el
conjunto de todas aquellas aristas que unan en el grafo G dos vrtices que estn en
'
V . Se podran combinar ambas definiciones dando lugar a lo que llamaremos subgrafo
parcial

Se denomina grado de entrada de un vrtice x al nmero de arcos incidentes en l.Se


denota .
Se denomina grado de salida de un vrtice x al nmero de arcos adyacentes a l.Se

denota .
Para grafos no dirigidos tanto el grado de entrada como el de salida coinciden y
hablamos entonces de grado y lo notamos por .
A todo grafo no dirigido se puede asociar un grafo denominado dual construido de la
siguiente forma:

donde A' est construido de la siguiente forma:si e1,e2 pertenece a A son adyacentes --
> (e1,e2)pertenece a A' con e1,e2 pertenece a V'.En definitiva,para construir un grafo
dual se cambian vrtices por aristas y viceversa.
112
Manual del Alumno

Dado un grafo G,diremos que dos vrtices estn conectados si entre ambos existe un
camino que los une.
Llamaremos componente conexa a un conjunto de vrtices de un grafo tal que entre
cada par de vrtices hay al menos un camino y si se aade algn otro vrtice esta
concicin deja de verificarse.Matemticamente se puede ver como que la conexin es
una relacin de equivalencia que descompone a V en clases de equivalencia,cada uno
de los subgrafos a los que da lugar cada una de esas clases de equivalencia
constituira una componente conexa.Un grafo diremos que es conexo si slo existe
una componente conexa que coincide con todo el grafo. .

3. TDA GRAFO.
A la hora de disear el TDA grafo hay que tener en cuenta que hay que manejar datos
correspondientes a sus vrtices y aristas,pudiendo cada uno de ellos estar o no
etiquetados.Adems hay que proporcionar operaciones primitivas que permitan manejar el tipo
de dato sin necesidad de conocer la implementacin.As,los tipos de datos que se usarn y las
operaciones primitivas consideradas son las siguientes:

NUEVOS TIPOS APORTADOS.


Los nuevos tipos aportados por el TDA grafo son los siguientes:

grafo.
vertice.
arista.

4. REPRESENTACIONES PARA EL TDA GRAFO.


Existen diversas representaciones de naturaleza muy diferente que resultan adecuadas para
manejar un grafo,y en la mayora de los casos no se puede decir que una sea mejor que otra
siempre ya que cada una puede resultar ms adecuada dependiendo del problema concreto al
que se desea aplicar.As,si existe una representacin que es peor que otra para todas las
operaciones excepto una es posible que an as nos decantemos por la primera porque
precisamente esa operacin es la nica en la que tenemos especial inters en que se realice
de forma eficiente.A continuacin veremos dos de las representaciones ms usuales:Matriz de
adyacencia(o booleana) y Lista de adyacencia.
113
Manual del Alumno

MATRIZ DE ADYACENCIA.
Grafos dirigidos.
G=(V,A) un grafo dirigido con |V|=n .Se define la matriz de adyacencia o booleana asociada a
G como Bnxn con

Como se ve,se asocia cada fila y cada columna a un vrtice y los elementos bi,j de la matriz son
1 si existe el arco (i,j) y 0 en caso contrario.
Grafos no dirigidos.
G=(V,A) un grafo no dirigido con |V|=n .Se define la matriz de adyacencia o booleana asociada
a G como Bnxn con:

La matriz B es simetrica con 1 en las posiciones ij y ji si existe la arista (i,j).


EJEMPLO:
114
Manual del Alumno

Si el grafo es etiquetado,entonces tanto bi,j como bi,j representan al coste o valor asociado al
arco (i,j) y se suelen denominar matrices de coste. Si el arco (i,j) no pertenece a A entonces se
asigna bi,j o bi,j un valor que no puede ser utilizado como una etiqueta valida.
La principal ventaja de la matriz de adyacencia es que el orden de eficiencia de las operaciones
de obtencion de etiqueta de un arco o ver si dos vertices estan conectados son independientes
del nmero de vrtices y de arcos. Por el contrario, existen dos grandes inconvenientes:

Es una representacin orientada hacia grafos que no modifica el nmero de sus


vertices ya que una matriz no permite que se le o supriman filas o columnas.
Se puede producir un gran derroche de memoria en grafos poco densos (con gran
nmero de vrtices y escaso nmero de arcos).

Para evitar estos inconvenientes se introduce otra representacin: las listas de adyacencia.

LISTAS DE ADYACENCIA.
En esta estructura de datos la idea es asociar a cada vertice i del grafo una lista que contenga
todos aquellos vrtices j que sean adyacentes a l. De esta forma sllo reservar memoria para
los arcos adyacentes a i y no para todos los posibles arcos que pudieran tener como origen i.
El grafo, por tanto, se representa por medio de un vector de n componentes (si |V|=n) donde
cada componente va a ser una lista de adyacencia correspondiente a cada uno de los vertices
del grafo. Cada elemento de la lista consta de un campo indicando el vrtice adyacente. En
caso de que el grafo sea etiquetado, habr que aadir un segundo campo para mostrar el valor
de la etiqueta.
115
Manual del Alumno

Esta representacion requiere un espacio proporcional a la suma del nmero de vrtices, ms el


nmero de arcos, y se suele usar cuando el nmero de arcos es mucho menor que el nmero
de arcos de un grafo completo. Una desventaja es que puede llevar un tiempo O(n) determinar
si existe un arco del vrtice i al vrtice j, ya que puede haber n vertices en la lista de
adyacencia asociada al vrtice i.
Mediante el uso del vector de listas de adyacencias slo se reserva memoria para los arcos
existentes en el grafo con el consiguiente ahorro de la misma. Sin embargo, no permite que
116
Manual del Alumno
haya vrtices que puedan ser aadidos o suprimidos del grafo, debido a que la dimension del
grafo debe ser predeterminadoa y fija. Para solucionar esto se puede usar una lista de listas de
adyacencia. Slo los vrtices del grafo que sean origen de algun arco aparecern en la lista.
De esta forma se pueden aadir y suprimir arcos sin desperdicio de memoria ya que
simplemente habr que modificar la lista de listas para reflejar los cambios.

Como puede verse en el ejemplo de las figuras anteriores tanto el vector de listas de
adyacencias como en la lista de listas se ha razonado en funcin de los vrtices que actan
como origenes de los arcos. Anlogamente se poda haber hecho con lod vertices destino, y
combinando ambas representaciones podra pensarse en utilizar dos vectores de listas de
adyacencia o dos listas de listas de adyacencia.

REPRESENTACION PROPUESTA.
La eleccin de una estructura idnea para representar el TDA grafo no es una tarea fcil ya
que existen dos representaciones totalmente contrapuestas: por un lado tenemos la matriz de
adyacencias que es muy eficiente para comprobar si existe una arista uniendo dos vertices
peero que sin embargo desperdicia una gran cantidad de espacio si el grafo no es completo o
esta lejos de serlo, adems no tiene la posibilidad de aadir nuevos vrtices; y por otra parte
est la lista de adyacencias que no tiene el problema de la anterior respecto al espacio pero
que sin embargo no es tan eficiente a la hora de ver si existe una arista entre dos nodos
determinados.
Teniendo en cuenta estas consideraciones se ha optado por realizar una mezcla de ambas
representaciones intentando aprovechar de alguna forma las ventajas que ambas poseen. Por
otra parte siguiendo con la idea de tratar tanto los grafos dirigidos como los no dirigidos bajo
una misma estructura, la estructura elegida posee dos apariencias ligeramente diferentes para
tratar de forma adecuada cada uno de estos dos tipos de grafos.
La estructura consiste (en el caso de que tengamos un grafo dirigido en una lista de vrtices
donde cada uno de estos posee dos listas, una de aristas incidentes a l y otra de adyacentes.
Cada vez que se aade una arista al grafo se inserta en la lista de aristas adyacentes del
vertice origen y en la de incidentes del vrtice destino. De esta forma la estructura desplegada
se asemejara a una matriz de adyacencia en la cual hay una arista por cada 1 y el ndice de la
matriz es la posicin dentro de la lista de vertices.
117
Manual del Alumno
Graficamente la estructura para un grafo dirigido queda como se puede apreciar en la siguiente
figura.El puntero que de la estructura arco que apunta al destino se ha sustituido por la etiqueta
del nodo destino en el grafico para simplificarlo y hacerlo mas claro.

Esta estructura no seria la mas idonea si trabajamos con solo con grafos no dirigidos ya que
por cada arista no dirigida tendriamos que insertar en la estructura una misma arista dirigida
repetida dos veces (una con un vrtice como origen y el otro como destino y al contrario). En
muchos problemas si asumimos el desperdicio de espacio podria , de todas formas, resultar
interesante representar un grafo no dirigido como un grafo dirigido simetrico, el problema se
preesenta cuando al tener dos aristas dirigidas esto supone la presencia de un ciclo en el grafo
que realmente no existe.
118
Manual del Alumno
Teniendo en cuenta el razonamiento anterior, en el caso de que queramos manejar grafos no
dirigido la estructura consistiria en tener una lista de adyacencia para cada uno de los vertices
pero tratando aquellas aristas que aparecen en la lista de adyacencia de dos vertices distintos
y que unen ambos vrtices como una nica arista lgica (a estas dos aristas que forman una
misma arista lgica las llamaremos aristas gemelas).
Estructuras Internas.
Esta representacion tiene tres estructuras diferenciadas:

Estructura correspondiente a un vrtice.

o nodo: Codigo interno que permite numerar los nodos de 1 a n.


o etiq: Puntero a caracter en el que se encuentra la informacin que posee ese
vrtice, es decir su etiqueta.
o ady: Es un puntero a una lista que contiene las aristas que tienen como origen
ese vrtice.
o inc: Es un puntero a una lista que contiene las aristas que tienen como destino
ese vrtice (solo para grafos dirigidos).
o sig: Es un puntero que apunta al vrtice que ocupa la posicion siguiente dentro
de la lista de vertices.

Estructura bsica del grafo.

En realidad se usa la misma estructura que para los nodos pero poniendo los campos
etiq, ady y sig a NULL. Los dos campos restantes contienen:
o nodo: Contien el nmero de nodos del grafo.
o sig: Es un puntero que apunta al vrtice que ocupa la primera posicion dentro
de la lista de vertices.

Estructura correspondiente a una arista (grafo dirigido).

o origen: Es un puntero al vrtice que es el origen de esa arista.


o destino: Es un puntero al vrtice que es el destino de esa arista.(Nosotros
hemos sustituido el puntero por la etiqueta del nodo destino para mayor
claridad del dibujo).
o valor: Este campo contiene el peso de la arista que sera un numero entero.
o sig: Puntero que apunta a la siguente arista dentro de la lista de aristas
adyacentes o incidentes.

Estructuras Internas del TDA grafo.

/* Implementacion basada en una lista de nodos de los que cuelga */


/* la lista de arcos de salida. */

#include
#include
#include

#define TE 5
#define Nulo NULL

typedef char *tetq;


typedef float tvalor;
119
Manual del Alumno

typedef struct arco {


struct nodo *origen;
struct nodo *destino;
tvalor valor;
struct arco *sig;
} *tarco;

typedef struct nodo {


int nodo;
tetq etiq;
tarco ady;
tarco inc;
struct nodo *sig;
} *tnodo;

typedef tnodo tgrafo;

5. IMPLEMENTACIN DE EL TDA GRAFO.

LISTA DE PRIMITIVAS.
Lista de primitivas para los grafos dirigidos:

Crear: Funcin que se encarga de crear un grafo vacio.


Etiqueta: Funcion que devuelve la etiqueta asociada a un nodo en un grafo.
Label: Funcion que devuelve la Label de un nodo en el grafo.
LocalizaLabel: Esta funcin recibe el entero l (el label asociado a un nodo que se
supone pertenece al grafo y nos devuelve el nodo asociado con esa label.
ExisteArco: Funcin que devuelve 1 si existe un arco entre el nodo o y el nodo d en el
grafo g, si no existe dicho arco devuelve 0.
PrimerArco: Devuelve el primer arco que sale del nodo n en el grafo g, si no existe
dicho primer arco devuelve Nulo.
SiguienteArco: Funcin que devuelve el arco siguiente al arco a en el nodo n si no
existe dicho arco devuelve Nulo.
PrimerArcoInv: Devuelve el primer arco que entra en el nodo n en el grafo g. Si no
existe dicho arco devuelve Nulo.
SiguienteArcoInv: Devuelve el siguiente arco tras a que entra en el nodo n, si no
existe dicho arco devuelve Nulo.
PrimerNodo: Devuelve el primer nodo del grafo G, si no existe devuelve nulo.
SiguienteNodo: Devuelve el nodo siguiente en orden al nodo n en el grafo g. Si no
existe devuelve nulo.
NodoOrigen: Devuelve el nodo origen del arco a.
NodoDestino: Devuelve el nodo destino del arco a.
presentarGrafo: Escribe el grafo g en pantalla.
NumeroNodos: Devuelve el numero de nodos de un grafo g.
grafoVacio: Devuelve Nulo si el grafo esta vacio.
EtiqArco: Funcion que devuelve la etiqueta asociada a un arco, es decir el peso del
arco.
InsertarNodo: Funcion que inserta un nodo nuevo en un grafo.
InsertarArco: Funcion que se encarga de insertar un arco entre el nodo org y el dest
en el grafo g, asociado al arco le podemos dar un valor.
BorrarArco: Funcion que borra el arco existente entre los nodos org y dest.
DesconectarNodo: Funcin que devuelve el grafo que se obtiene al eliminar un nodo
de un grafo G.Todos los arcos que entran o salen del nodo a eliminar tambien
desaparecen.
Destruir: Funcion que destruye el grafo g liberando la memoria que ocupa.
CopiarGrafo: Funcion que hace una copia del grafo g.
120
Manual del Alumno

IMPLEMENTACIN DE LAS PRIMITIVAS.

tgrafo Crear(void)
{
tnodo aux;

aux = (tnodo)malloc(sizeof(struct nodo));


if (aux == NULL) {
error(\"Error en Crear.\");
} else {
aux->nodo = 0;
aux->etiq = NULL;
aux->ady = NULL;
aux->inc = NULL;
aux->sig = NULL;
return aux;
}
}

tetiq Etiqueta(tnodo n, tgrafo g)


{
return(n->etiq);
}

int Label(tnodo n, tgrafo g)


{
return(n->nodo);
}

tnodo LocalizaLabel(int l, tgrafo g)


{
tnodo n;
int enc=0;

for (n=g->sig; n!=NULL && !enc; ) {


if (n->nodo == l)
enc = 1;
else
n = n->sig;
}
return n;
}

int ExisteArco(tnodo o, tnodo d, tgrafo g)


{
tarco a;

a=o->ady;
while (a!=NULL) {
if ((a->origen==o) && (a->destino==d))
return 1;
121
Manual del Alumno
else
a = a->sig;
}
return 0;
}

tarco PrimerArco(tnodo n, tgrafo g)


{
return(n->ady);
}

tarco SiguienteArco(tnodo n, tarco a, tgrafo g)


{
return(a->sig);
}

tarco PrimerArcoInv(tnodo n, tgrafo g)


{
return(n->inc);
}

tarco SiguienteArcoInv(tnodo n, tarco a, tgrafo g)


{
return(a->sig);
}

tnodo PrimerNodo(tgrafo g)
{
return(g->sig);
}

tnodo SiguienteNodo(tnodo n, tgrafo g)


{
return(n->sig);
}

tnodo NodoOrigen(tarco a, tgrafo g)


{
return(a->origen);
}

tnodo NodoDestino(tarco a, tgrafo g)


{
return(a->destino);
}
122
Manual del Alumno
void PresentarGrafo(tgrafo g)
{
tnodo n;
tarco a;

n=PrimerNodo(g);
while (n!=Nulo) {
a=PrimerArco(n,g);
while (a!=Nulo) {
printf(\"%s -> %s \",a->origen->etiq,a->destino->etiq);
printf(\" (%f)\\n\",a->valor);
a=SiguienteArco(n,a,g);
}
n=SiguienteNodo(n,g);
}
}

int NumeroNodos(tgrafo g)
{
return(g->nodo);
}

int GrafoVacio(tgrafo g)
{
return(g->sig == NULL);
}

float EtiqArco(tnodo o, tnodo d, tgrafo g)


{
tarco a;

a=o->ady;
while (a!=NULL) {
if ((a->origen == o) && (a->destino == d))
return (a->valor);
else
a = a->sig;
}
return 0;
}

void InsertarNodo(tetq dato, tgrafo g)


{
tnodo aux,p;

aux = (tnodo)malloc(sizeof(struct nodo));


if (aux == NULL)
error(\"Error Memoria Insuficiente.\");
else {
p=g;
while(p->sig != NULL)
p = p->sig;
aux->etiq = (char *)malloc(sizeof (char)*TE);"+
123
Manual del Alumno
if (aux->etiq == NULL)
error(\"Error Memoria Insuficiente.\");
aux->nodo = p->nodo+1;
strcpy(aux->etiq,dato);+
aux->ady = NULL;
aux->inc = NULL;
aux->sig = NULL;
p->sig = aux;
g->nodo++;
}
}

void InsertarArco (tnodo org,tnodo dest,tvalor valor,tgrafo g)


{
tarco aux;
tarco aux_inv;

aux = (tarco)malloc(sizeof(struct arco));


aux_inv= (tarco)malloc(sizeof(struct arco));
if ((aux==NULL) || (aux_inv==NULL))
error("Memoria Insuficiente.");
else {
aux->origen = org;
aux->destino = dest;
aux->valor = valor;
aux-> sig= org->ady;
org->ady = aux;

aux_inv->origen = org;
aux_inv->destino = dest;
aux_inv-> valor= valor;
aux_inv-> sig= dest->inc;
des_inc-> = aux_inv;
}
}

void BorrarArco(tnodo org, tnodo dest, tgrafo g)


{
tarco a,ant;
int enc=0;

if (org->ady==NULL) return;
else if (org->ady->destino==dest) {
a = org->ady;
org->ady = a->sig;
free(a);
}
else {
ant = org->ady;
a = ant->sig;
while (!enc && (a!=NULL)) {
if (a->destino==dest) enc=1;
else {
a = a->sig;
ant = ant->sig;
}
}
124
Manual del Alumno
if (a==NULL) return;
else {
ant->sig = a->sig;
free(a);
}
}

enc=0;
if (dest->inc==NULL) return;
else if (dest->inc->origen==org) {
a = dest->inc;
dest->inc = a->sig;
free(a);
}
else {
ant = dest->inc;
a = ant->sig;
while (!enc && (a!=NULL)) {
if (a->origen == org) enc=1;
else {
a = a->sig;
ant = ant->sig;
}
}
if (a==NULL) return;
else {
ant->sig = a->sig;
free(a);
}
}
}

void Destruir(tgrafo G)
{
tnodo n;
tarco a_aux;

while (g->sig != NULL) {


n = g->sig;
while (n->ady != NULL) {
a_aux = n->ady;
n->ady = a_aux->sig;
free(a_aux);
}
while (n->inc != NULL) {
a_aux = n->inc;
n->inc = a_aux->sig;
free(a_aux);
}
g->sig = n->sig;
free(n->etiq);
free(n);
}
free(g);
}

tgrafo DesconectarNodo(tnodo a_eliminar,tgeafo g)


125
Manual del Alumno
{
tgrafo g_nd;
tnodo n;
tnodo org;dst;
tnodo o,d;
tarco a;

g_nd = Crear();
for (n=PrimerNodo(g); n!=NULL; n=SiguienteNodo(n,g))
InsertarNodo(Etiqueta(n,g),g_nd);

for (n=PrimerNodo(g); n!=NULL; n=SiguienteNodo(n,g))


for (a=PrimerArco(n,g); a!=NULL; a=SiguienteArco(n,a,g)) {
org = NodoOrigen(a,g);
dst = NodoDestino(a,g);
if ((org!=a_eliminar) && dst!=a_eliminar)) {
o = LocalizaLabel(Label(org,g), g_nd);
d = LocalizaLabel(Label(dst,g), g_nd);
InsertarArco(o,d,g_nd);
}
}
return g_nd;
}

tgrafo CopiarGrafo(tgrafo g)
{
tgrafo g_nd;
tnodo n;
tnodo org;dst;
tnodo o,d;
tarco a;
int lb;

g_nd = Crear();
for (n=PrimerNodo(g); n!=NULL; n=SiguienteNodo(n,g))
InsertarNodo(Etiqueta(n,g),g_nd);

for (n=PrimerNodo(g); n!=NULL; n=SiguienteNodo(n,g))


for (a=PrimerArco(n,g); a!=NULL; a=SiguienteArco(n,a,g)) {
org = NodoOrigen(a,g);
dst = NodoDestino(a,g);
o = LocalizaLabel(Label(org,g), g_nd);
d = LocalizaLabel(Label(dst,g), g_nd);
InsertarArco(o,d,g_nd);
}
}
return g_nd;

}
126
Manual del Alumno

GRAFOS

1. INTRODUCCIN.

El origen de la palabra grafo es griego y su significado etimolgico es "trazar". Aparece con


gran frecuencia como respuesta a problemas de la vida cotidiana,algunos ejemplos podran ser
los siguientes:un grfico de una serie de tareas a realizar indicando su secuenciacin (un
organigrama),grafos matemticos que representan las relaciones binarias,una red de
carreteras,la red de enlaces ferroviarios o areos o la red elctrica de una ciudad.(Vase la
figura 1).En cada caso,es conveniente representar grficamente el problema dibujando un
grafo como un conjunto de puntos(vrtices)con lneas conectndolos (arcos).

De aqu se podra deducir que un grafo es bsicamente un objeto geomtrico aunque en


realidad sea un objeto combinatorio,es decir,un conjunto de puntos y un conjunto de lneas
tomado de entre el conjunto de lneas que une cada par de vrtices.Por otro lado,y debido a su
generalidad y a la gran diversidad de formas que pueden usarse,resulta complejo tratar con
todas las ideas relacionadas con un grafo.

Para facilitar el estudio de este tipo de dato,a continuacin se realizar un estudio de la teora
de grafos desde el punto de vista de las ciencias de la computacin. Considerando que dicha
teora es compleja y amplia,aqu slo se realizar una introduccin a la misma,describindose
el grafo como un tipo de dato y mostrndose los problemas tpicos y los algoritmos que
permiten solucionarlos usando un ordenador.

Los grafos son estructuras de datos no lineales que tienen una naturaleza generalmente
dinmica. Su estudio podra dividirse en dos grandes bloques:

Grafos Dirigidos.
Grafos no Dirigidos(pueden ser considerados un caso particular de los anteriores).

Un ejemplo de grafo dirigido lo constituye la red de aguas de una ciudad ya que cada tubera
slo admite que el agua la recorra en un nico sentido.Por el contrario,la red de carreteras de
127
Manual del Alumno
un pas representa en general un grafo no dirigido,puesto que una misma carretera puede ser
recorrida en ambos sentidos.No obstante,podemos dar unas definiciones generales para
ambos tipos.
A continuacin daremos definiciones de los dos tipos de grafos y de los conceptos que llevan
asociados.

2. DEFINICIONES Y TERMINOLOGA FUNDAMENTAL.


Un grafo G es un conjunto en el que hay definida una relacin binaria,es decir,G=(V,A) tal que
V es un conjunto de objetos a los que denominaremos vrtices o nodos y es una
relacin binaria a cuyos elementos denominaremos arcos o aristas.
Dados ,puede ocurrir que:

1. , en cuyo caso diremos que x e y estn unidos mediante un arco,y


2. , en cuyo caso diremos que no lo estn.

Si las aristas tienen asociada una direccin(las aristas (x,y) y (y,x) no son equivalentes)
diremos que el grafo es dirigido,en otro caso ((x,y)=(y,x)) diremos que el grafo es no dirigido.

Conceptos asociados a grafos:

Diremos que un grafo es completo si A=VxV,o sea,si para cualquier pareja de vrtices
existe una arista que los une(en ambos sentidos si el grafo es no dirigido).El nmero de
aristas ser:
o grafos dirigidos:

o grafos no dirigidos:
128
Manual del Alumno
donde n=|V|

Un grafo dirigido es simtrico si para toda arista (x,y)perteneciente a A tambin


aparece la arista (y,x)perteneciente a A;y es antisimtrico si dada una arista (x,y)
perteneciente a A implica que (y,x) no pertenece a A.

Tanto a las aristas como a los vrtices les puede ser asociada informacin.A esta
informacin se le llama etiqueta.Si la etiqueta que se asocia es un nmero se le llama
peso,costo o longitud.Un grafo cuyas aristas o vrtices tienen pesos asociados recibe
el nombre de grafo etiquetado o ponderado.

El nmero de elementos de V se denomina orden del grafo.Un grafo nulo es un grafo


de orden cero.

Se dice que un vrtice x es incidente a un vrtice y si existe un arco que vaya de x a y


((x,y)pertenece a A),a x se le denomina origen del arco y a y extremo del mismo.De
igual forma se dir que y es adyacente a x.En el caso de que el grafo sea no dirigido si
x es adyacente(resp. incidente) a y entonces y tambin es adyacente (resp. incidente)
a x.

Se dice que dos arcos son adyacentes cuando tienen un vrtice comn que es a la vez
origen de uno y extremo del otro.

Se denomina camino (algunos autores lo llaman cadena si se trata de un grafo no


dirigido)en un grafo dirigido a una sucesin de arcos adyacentes:
C={(v1,v2),(v2,v3),...,(vn-1,vn), para todo vi perteneciente a V}

La longitud del camino es el nmero de arcos que comprende y en el caso en el que


el grafo sea ponderado se calcular como la suma de los pesos de las aristas que lo
constituyen.
Ejemplo.
o En el grafo dirigido de la figura 2,un camino que une los vrtices 1 y 4 es C=
{(1,3),(3,2),(2,1)},su longitud es 3.
o En el grafo no dirigido de la figura 2,un camino que une los vrtices 1 y 4 es
'
C = {(1,2),(2,4)}.Su longitud es 2.

Un camino se dice simple cuando todos sus arcos son distintos y se dice elemental
cuando no utiliza un mismo vrtice dos veces.Por tanto todo camino elemental es
simple y el recproco no es cierto.

Un camino se dice Euleriano si es simple y adems contiene a todos los arcos del
grafo.

Un circuito(o ciclo para grafos no dirigidos)es un camino en el que coinciden los


vrtices inicial y final.Un circuito se dice simple cuando todos los arcos que lo forman
son distintos y se dice elemental cuando todos los vrtices por los que pasa son
distintos.La longitud de un circuito es el nmero de arcos que lo componen.Un bucle
es un circuito de longitud 1(estn permitidos los arcos de la forma(i,i) y notemos que un
grafo antisimtrico carecera de ellos).

Un circuito elemental que incluye a todos los vrtices de un grafo lo llamaremos circuito
Hamiltoniano.

Un grafo se denomina simple si no tiene bucles y no existe ms que un camino para


unir dos nodos.
129
Manual del Alumno
Diremos que un grafo no dirigido es bipartido si el conjunto de sus vrtices puede ser
dividido en dos subconjuntos(disjuntos) de tal forma que cualquiera de las aristas que
componen el grafo tiene cada uno de sus extremos en un subconjunto distinto.Un grafo
no dirigido ser bipartido si y slo si no contiene ciclos con un nmero de aristas par.

' '
Dado un grafo G=(V,A),diremos que G =(V,A ) con es un grafo parcial de G y
' ' ' '
un subgrafo de G es todo grafo G =(V ,A ) con y donde A ser el
conjunto de todas aquellas aristas que unan en el grafo G dos vrtices que estn en
'
V . Se podran combinar ambas definiciones dando lugar a lo que llamaremos subgrafo
parcial

Se denomina grado de entrada de un vrtice x al nmero de arcos incidentes en l.Se


denota .
Se denomina grado de salida de un vrtice x al nmero de arcos adyacentes a l.Se

denota .
Para grafos no dirigidos tanto el grado de entrada como el de salida coinciden y
hablamos entonces de grado y lo notamos por .
A todo grafo no dirigido se puede asociar un grafo denominado dual construido de la
siguiente forma:

donde A' est construido de la siguiente forma:si e1,e2 pertenece a A son adyacentes --
> (e1,e2)pertenece a A' con e1,e2 pertenece a V'.En definitiva,para construir un grafo
dual se cambian vrtices por aristas y viceversa.
130
Manual del Alumno

Dado un grafo G,diremos que dos vrtices estn conectados si entre ambos existe un
camino que los une.
Llamaremos componente conexa a un conjunto de vrtices de un grafo tal que entre
cada par de vrtices hay al menos un camino y si se aade algn otro vrtice esta
concicin deja de verificarse.Matemticamente se puede ver como que la conexin es
una relacin de equivalencia que descompone a V en clases de equivalencia,cada uno
de los subgrafos a los que da lugar cada una de esas clases de equivalencia
constituira una componente conexa.Un grafo diremos que es conexo si slo existe
una componente conexa que coincide con todo el grafo. .

3. TDA GRAFO.
A la hora de disear el TDA grafo hay que tener en cuenta que hay que manejar datos
correspondientes a sus vrtices y aristas,pudiendo cada uno de ellos estar o no
etiquetados.Adems hay que proporcionar operaciones primitivas que permitan manejar el tipo
de dato sin necesidad de conocer la implementacin.As,los tipos de datos que se usarn y las
operaciones primitivas consideradas son las siguientes:

NUEVOS TIPOS APORTADOS.


Los nuevos tipos aportados por el TDA grafo son los siguientes:

grafo.
vertice.
arista.

4. REPRESENTACIONES PARA EL TDA GRAFO.


Existen diversas representaciones de naturaleza muy diferente que resultan adecuadas para
manejar un grafo,y en la mayora de los casos no se puede decir que una sea mejor que otra
siempre ya que cada una puede resultar ms adecuada dependiendo del problema concreto al
que se desea aplicar.As,si existe una representacin que es peor que otra para todas las
operaciones excepto una es posible que an as nos decantemos por la primera porque
precisamente esa operacin es la nica en la que tenemos especial inters en que se realice
de forma eficiente.A continuacin veremos dos de las representaciones ms usuales:Matriz de
adyacencia(o booleana) y Lista de adyacencia.
131
Manual del Alumno

MATRIZ DE ADYACENCIA.
Grafos dirigidos.
G=(V,A) un grafo dirigido con |V|=n .Se define la matriz de adyacencia o booleana asociada a
G como Bnxn con

Como se ve,se asocia cada fila y cada columna a un vrtice y los elementos bi,j de la matriz son
1 si existe el arco (i,j) y 0 en caso contrario.
Grafos no dirigidos.
G=(V,A) un grafo no dirigido con |V|=n .Se define la matriz de adyacencia o booleana asociada
a G como Bnxn con:

La matriz B es simetrica con 1 en las posiciones ij y ji si existe la arista (i,j).


EJEMPLO:
132
Manual del Alumno

Si el grafo es etiquetado,entonces tanto bi,j como bi,j representan al coste o valor asociado al
arco (i,j) y se suelen denominar matrices de coste. Si el arco (i,j) no pertenece a A entonces se
asigna bi,j o bi,j un valor que no puede ser utilizado como una etiqueta valida.
La principal ventaja de la matriz de adyacencia es que el orden de eficiencia de las operaciones
de obtencion de etiqueta de un arco o ver si dos vertices estan conectados son independientes
del nmero de vrtices y de arcos. Por el contrario, existen dos grandes inconvenientes:

Es una representacin orientada hacia grafos que no modifica el nmero de sus


vertices ya que una matriz no permite que se le o supriman filas o columnas.
Se puede producir un gran derroche de memoria en grafos poco densos (con gran
nmero de vrtices y escaso nmero de arcos).

Para evitar estos inconvenientes se introduce otra representacin: las listas de adyacencia.

LISTAS DE ADYACENCIA.
En esta estructura de datos la idea es asociar a cada vertice i del grafo una lista que contenga
todos aquellos vrtices j que sean adyacentes a l. De esta forma sllo reservar memoria para
los arcos adyacentes a i y no para todos los posibles arcos que pudieran tener como origen i.
El grafo, por tanto, se representa por medio de un vector de n componentes (si |V|=n) donde
cada componente va a ser una lista de adyacencia correspondiente a cada uno de los vertices
del grafo. Cada elemento de la lista consta de un campo indicando el vrtice adyacente. En
caso de que el grafo sea etiquetado, habr que aadir un segundo campo para mostrar el valor
de la etiqueta.
133
Manual del Alumno

Esta representacion requiere un espacio proporcional a la suma del nmero de vrtices, ms el


nmero de arcos, y se suele usar cuando el nmero de arcos es mucho menor que el nmero
de arcos de un grafo completo. Una desventaja es que puede llevar un tiempo O(n) determinar
si existe un arco del vrtice i al vrtice j, ya que puede haber n vertices en la lista de
adyacencia asociada al vrtice i.
Mediante el uso del vector de listas de adyacencias slo se reserva memoria para los arcos
existentes en el grafo con el consiguiente ahorro de la misma. Sin embargo, no permite que
134
Manual del Alumno
haya vrtices que puedan ser aadidos o suprimidos del grafo, debido a que la dimension del
grafo debe ser predeterminadoa y fija. Para solucionar esto se puede usar una lista de listas de
adyacencia. Slo los vrtices del grafo que sean origen de algun arco aparecern en la lista.
De esta forma se pueden aadir y suprimir arcos sin desperdicio de memoria ya que
simplemente habr que modificar la lista de listas para reflejar los cambios.

Como puede verse en el ejemplo de las figuras anteriores tanto el vector de listas de
adyacencias como en la lista de listas se ha razonado en funcin de los vrtices que actan
como origenes de los arcos. Anlogamente se poda haber hecho con lod vertices destino, y
combinando ambas representaciones podra pensarse en utilizar dos vectores de listas de
adyacencia o dos listas de listas de adyacencia.

REPRESENTACION PROPUESTA.
La eleccin de una estructura idnea para representar el TDA grafo no es una tarea fcil ya
que existen dos representaciones totalmente contrapuestas: por un lado tenemos la matriz de
adyacencias que es muy eficiente para comprobar si existe una arista uniendo dos vertices
peero que sin embargo desperdicia una gran cantidad de espacio si el grafo no es completo o
esta lejos de serlo, adems no tiene la posibilidad de aadir nuevos vrtices; y por otra parte
est la lista de adyacencias que no tiene el problema de la anterior respecto al espacio pero
que sin embargo no es tan eficiente a la hora de ver si existe una arista entre dos nodos
determinados.
Teniendo en cuenta estas consideraciones se ha optado por realizar una mezcla de ambas
representaciones intentando aprovechar de alguna forma las ventajas que ambas poseen. Por
otra parte siguiendo con la idea de tratar tanto los grafos dirigidos como los no dirigidos bajo
una misma estructura, la estructura elegida posee dos apariencias ligeramente diferentes para
tratar de forma adecuada cada uno de estos dos tipos de grafos.
La estructura consiste (en el caso de que tengamos un grafo dirigido en una lista de vrtices
donde cada uno de estos posee dos listas, una de aristas incidentes a l y otra de adyacentes.
Cada vez que se aade una arista al grafo se inserta en la lista de aristas adyacentes del
vertice origen y en la de incidentes del vrtice destino. De esta forma la estructura desplegada
se asemejara a una matriz de adyacencia en la cual hay una arista por cada 1 y el ndice de la
matriz es la posicin dentro de la lista de vertices.
135
Manual del Alumno
Graficamente la estructura para un grafo dirigido queda como se puede apreciar en la siguiente
figura.El puntero que de la estructura arco que apunta al destino se ha sustituido por la etiqueta
del nodo destino en el grafico para simplificarlo y hacerlo mas claro.

Esta estructura no seria la mas idonea si trabajamos con solo con grafos no dirigidos ya que
por cada arista no dirigida tendriamos que insertar en la estructura una misma arista dirigida
repetida dos veces (una con un vrtice como origen y el otro como destino y al contrario). En
muchos problemas si asumimos el desperdicio de espacio podria , de todas formas, resultar
interesante representar un grafo no dirigido como un grafo dirigido simetrico, el problema se
preesenta cuando al tener dos aristas dirigidas esto supone la presencia de un ciclo en el grafo
que realmente no existe.
136
Manual del Alumno
Teniendo en cuenta el razonamiento anterior, en el caso de que queramos manejar grafos no
dirigido la estructura consistiria en tener una lista de adyacencia para cada uno de los vertices
pero tratando aquellas aristas que aparecen en la lista de adyacencia de dos vertices distintos
y que unen ambos vrtices como una nica arista lgica (a estas dos aristas que forman una
misma arista lgica las llamaremos aristas gemelas).
Estructuras Internas.
Esta representacion tiene tres estructuras diferenciadas:

Estructura correspondiente a un vrtice.

o nodo: Codigo interno que permite numerar los nodos de 1 a n.


o etiq: Puntero a caracter en el que se encuentra la informacin que posee ese
vrtice, es decir su etiqueta.
o ady: Es un puntero a una lista que contiene las aristas que tienen como origen
ese vrtice.
o inc: Es un puntero a una lista que contiene las aristas que tienen como destino
ese vrtice (solo para grafos dirigidos).
o sig: Es un puntero que apunta al vrtice que ocupa la posicion siguiente dentro
de la lista de vertices.

Estructura bsica del grafo.

En realidad se usa la misma estructura que para los nodos pero poniendo los campos
etiq, ady y sig a NULL. Los dos campos restantes contienen:
o nodo: Contien el nmero de nodos del grafo.
o sig: Es un puntero que apunta al vrtice que ocupa la primera posicion dentro
de la lista de vertices.

Estructura correspondiente a una arista (grafo dirigido).

o origen: Es un puntero al vrtice que es el origen de esa arista.


o destino: Es un puntero al vrtice que es el destino de esa arista.(Nosotros
hemos sustituido el puntero por la etiqueta del nodo destino para mayor
claridad del dibujo).
o valor: Este campo contiene el peso de la arista que sera un numero entero.
o sig: Puntero que apunta a la siguente arista dentro de la lista de aristas
adyacentes o incidentes.

Estructuras Internas del TDA grafo.

/* Implementacion basada en una lista de nodos de los que cuelga */


/* la lista de arcos de salida. */

#include
#include
#include

#define TE 5
#define Nulo NULL

typedef char *tetq;


typedef float tvalor;
137
Manual del Alumno

typedef struct arco {


struct nodo *origen;
struct nodo *destino;
tvalor valor;
struct arco *sig;
} *tarco;

typedef struct nodo {


int nodo;
tetq etiq;
tarco ady;
tarco inc;
struct nodo *sig;
} *tnodo;

typedef tnodo tgrafo;

5. IMPLEMENTACIN DE EL TDA GRAFO.

LISTA DE PRIMITIVAS.
Lista de primitivas para los grafos dirigidos:

Crear: Funcin que se encarga de crear un grafo vacio.


Etiqueta: Funcion que devuelve la etiqueta asociada a un nodo en un grafo.
Label: Funcion que devuelve la Label de un nodo en el grafo.
LocalizaLabel: Esta funcin recibe el entero l (el label asociado a un nodo que se
supone pertenece al grafo y nos devuelve el nodo asociado con esa label.
ExisteArco: Funcin que devuelve 1 si existe un arco entre el nodo o y el nodo d en el
grafo g, si no existe dicho arco devuelve 0.
PrimerArco: Devuelve el primer arco que sale del nodo n en el grafo g, si no existe
dicho primer arco devuelve Nulo.
SiguienteArco: Funcin que devuelve el arco siguiente al arco a en el nodo n si no
existe dicho arco devuelve Nulo.
PrimerArcoInv: Devuelve el primer arco que entra en el nodo n en el grafo g. Si no
existe dicho arco devuelve Nulo.
SiguienteArcoInv: Devuelve el siguiente arco tras a que entra en el nodo n, si no
existe dicho arco devuelve Nulo.
PrimerNodo: Devuelve el primer nodo del grafo G, si no existe devuelve nulo.
SiguienteNodo: Devuelve el nodo siguiente en orden al nodo n en el grafo g. Si no
existe devuelve nulo.
NodoOrigen: Devuelve el nodo origen del arco a.
NodoDestino: Devuelve el nodo destino del arco a.
presentarGrafo: Escribe el grafo g en pantalla.
NumeroNodos: Devuelve el numero de nodos de un grafo g.
grafoVacio: Devuelve Nulo si el grafo esta vacio.
EtiqArco: Funcion que devuelve la etiqueta asociada a un arco, es decir el peso del
arco.
InsertarNodo: Funcion que inserta un nodo nuevo en un grafo.
InsertarArco: Funcion que se encarga de insertar un arco entre el nodo org y el dest
en el grafo g, asociado al arco le podemos dar un valor.
BorrarArco: Funcion que borra el arco existente entre los nodos org y dest.
DesconectarNodo: Funcin que devuelve el grafo que se obtiene al eliminar un nodo
de un grafo G.Todos los arcos que entran o salen del nodo a eliminar tambien
desaparecen.
Destruir: Funcion que destruye el grafo g liberando la memoria que ocupa.
CopiarGrafo: Funcion que hace una copia del grafo g.
138
Manual del Alumno

IMPLEMENTACIN DE LAS PRIMITIVAS.

tgrafo Crear(void)
{
tnodo aux;

aux = (tnodo)malloc(sizeof(struct nodo));


if (aux == NULL) {
error(\"Error en Crear.\");
} else {
aux->nodo = 0;
aux->etiq = NULL;
aux->ady = NULL;
aux->inc = NULL;
aux->sig = NULL;
return aux;
}
}

tetiq Etiqueta(tnodo n, tgrafo g)


{
return(n->etiq);
}

int Label(tnodo n, tgrafo g)


{
return(n->nodo);
}

tnodo LocalizaLabel(int l, tgrafo g)


{
tnodo n;
int enc=0;

for (n=g->sig; n!=NULL && !enc; ) {


if (n->nodo == l)
enc = 1;
else
n = n->sig;
}
return n;
}

int ExisteArco(tnodo o, tnodo d, tgrafo g)


{
tarco a;

a=o->ady;
while (a!=NULL) {
if ((a->origen==o) && (a->destino==d))
return 1;
139
Manual del Alumno
else
a = a->sig;
}
return 0;
}

tarco PrimerArco(tnodo n, tgrafo g)


{
return(n->ady);
}

tarco SiguienteArco(tnodo n, tarco a, tgrafo g)


{
return(a->sig);
}

tarco PrimerArcoInv(tnodo n, tgrafo g)


{
return(n->inc);
}

tarco SiguienteArcoInv(tnodo n, tarco a, tgrafo g)


{
return(a->sig);
}

tnodo PrimerNodo(tgrafo g)
{
return(g->sig);
}

tnodo SiguienteNodo(tnodo n, tgrafo g)


{
return(n->sig);
}

tnodo NodoOrigen(tarco a, tgrafo g)


{
return(a->origen);
}

tnodo NodoDestino(tarco a, tgrafo g)


{
return(a->destino);
}
140
Manual del Alumno
void PresentarGrafo(tgrafo g)
{
tnodo n;
tarco a;

n=PrimerNodo(g);
while (n!=Nulo) {
a=PrimerArco(n,g);
while (a!=Nulo) {
printf(\"%s -> %s \",a->origen->etiq,a->destino->etiq);
printf(\" (%f)\\n\",a->valor);
a=SiguienteArco(n,a,g);
}
n=SiguienteNodo(n,g);
}
}

int NumeroNodos(tgrafo g)
{
return(g->nodo);
}

int GrafoVacio(tgrafo g)
{
return(g->sig == NULL);
}

float EtiqArco(tnodo o, tnodo d, tgrafo g)


{
tarco a;

a=o->ady;
while (a!=NULL) {
if ((a->origen == o) && (a->destino == d))
return (a->valor);
else
a = a->sig;
}
return 0;
}

void InsertarNodo(tetq dato, tgrafo g)


{
tnodo aux,p;

aux = (tnodo)malloc(sizeof(struct nodo));


if (aux == NULL)
error(\"Error Memoria Insuficiente.\");
else {
p=g;
while(p->sig != NULL)
p = p->sig;
aux->etiq = (char *)malloc(sizeof (char)*TE);"+
141
Manual del Alumno
if (aux->etiq == NULL)
error(\"Error Memoria Insuficiente.\");
aux->nodo = p->nodo+1;
strcpy(aux->etiq,dato);+
aux->ady = NULL;
aux->inc = NULL;
aux->sig = NULL;
p->sig = aux;
g->nodo++;
}
}

void InsertarArco (tnodo org,tnodo dest,tvalor valor,tgrafo g)


{
tarco aux;
tarco aux_inv;

aux = (tarco)malloc(sizeof(struct arco));


aux_inv= (tarco)malloc(sizeof(struct arco));
if ((aux==NULL) || (aux_inv==NULL))
error("Memoria Insuficiente.");
else {
aux->origen = org;
aux->destino = dest;
aux->valor = valor;
aux-> sig= org->ady;
org->ady = aux;

aux_inv->origen = org;
aux_inv->destino = dest;
aux_inv-> valor= valor;
aux_inv-> sig= dest->inc;
des_inc-> = aux_inv;
}
}

void BorrarArco(tnodo org, tnodo dest, tgrafo g)


{
tarco a,ant;
int enc=0;

if (org->ady==NULL) return;
else if (org->ady->destino==dest) {
a = org->ady;
org->ady = a->sig;
free(a);
}
else {
ant = org->ady;
a = ant->sig;
while (!enc && (a!=NULL)) {
if (a->destino==dest) enc=1;
else {
a = a->sig;
ant = ant->sig;
}
}
142
Manual del Alumno
if (a==NULL) return;
else {
ant->sig = a->sig;
free(a);
}
}

enc=0;
if (dest->inc==NULL) return;
else if (dest->inc->origen==org) {
a = dest->inc;
dest->inc = a->sig;
free(a);
}
else {
ant = dest->inc;
a = ant->sig;
while (!enc && (a!=NULL)) {
if (a->origen == org) enc=1;
else {
a = a->sig;
ant = ant->sig;
}
}
if (a==NULL) return;
else {
ant->sig = a->sig;
free(a);
}
}
}

void Destruir(tgrafo G)
{
tnodo n;
tarco a_aux;

while (g->sig != NULL) {


n = g->sig;
while (n->ady != NULL) {
a_aux = n->ady;
n->ady = a_aux->sig;
free(a_aux);
}
while (n->inc != NULL) {
a_aux = n->inc;
n->inc = a_aux->sig;
free(a_aux);
}
g->sig = n->sig;
free(n->etiq);
free(n);
}
free(g);
}

tgrafo DesconectarNodo(tnodo a_eliminar,tgeafo g)


143
Manual del Alumno
{
tgrafo g_nd;
tnodo n;
tnodo org;dst;
tnodo o,d;
tarco a;

g_nd = Crear();
for (n=PrimerNodo(g); n!=NULL; n=SiguienteNodo(n,g))
InsertarNodo(Etiqueta(n,g),g_nd);

for (n=PrimerNodo(g); n!=NULL; n=SiguienteNodo(n,g))


for (a=PrimerArco(n,g); a!=NULL; a=SiguienteArco(n,a,g)) {
org = NodoOrigen(a,g);
dst = NodoDestino(a,g);
if ((org!=a_eliminar) && dst!=a_eliminar)) {
o = LocalizaLabel(Label(org,g), g_nd);
d = LocalizaLabel(Label(dst,g), g_nd);
InsertarArco(o,d,g_nd);
}
}
return g_nd;
}

tgrafo CopiarGrafo(tgrafo g)
{
tgrafo g_nd;
tnodo n;
tnodo org;dst;
tnodo o,d;
tarco a;
int lb;

g_nd = Crear();
for (n=PrimerNodo(g); n!=NULL; n=SiguienteNodo(n,g))
InsertarNodo(Etiqueta(n,g),g_nd);

for (n=PrimerNodo(g); n!=NULL; n=SiguienteNodo(n,g))


for (a=PrimerArco(n,g); a!=NULL; a=SiguienteArco(n,a,g)) {
org = NodoOrigen(a,g);
dst = NodoDestino(a,g);
o = LocalizaLabel(Label(org,g), g_nd);
d = LocalizaLabel(Label(dst,g), g_nd);
InsertarArco(o,d,g_nd);
}
}
return g_nd;

}
144
Manual del Alumno

EJERCICIOS DE RBOLES GENERALES

Ejercicio No 1:

Escribir una funcin para calcular la altura de un rbol cualquiera.

Ejercicio No 2:

Escribir una funcin no recursiva para calcular la altura de un rbol cualquiera

Ejercicio No 3:

Responder a las siguientes preguntas sobre el rbol siguiente:

1. Qu nodo es la raz?
2. Cuntos caminos diferentes de longitud tres hay?
3. Es un camino la sucesin de nodos HGFBACI?
4. Qu nodos son los ancestros de K?
5. Qu nodos son los ancestros propios de N?
6. Qu nodos son los descendientes propios de M?
7. Qu nodos son las hojas?
8. Cul es la altura del nodo C?
9. Cul es la altura del rbol?
10. Cul es la profundidad del nodo C?
11. Cul es el hermano a la derecha de D?
12. Es I hermano a la derecha de F?
13. Est F a la izquierda de J?
14. Est L a la derecha de J?
15. Qu nodos estn a la izquierda y a la derecha de J?
16. Cuntos hijos tiene A?
17. Listar los nodos del rbol en preorden,postorden e inorden.
145
Manual del Alumno

Ejercicio No 4:
Considerando la funcin de listado en preorden de un rbol general que se ha presentado en la
pgina web referente a rboles generales,escribir dos funciones de escritura y lectura de un
rbol con etiquetas de tipo entero usando la misma estructura recursiva.Supngase que se
dispone de dos funciones de escritura y lectura (Escribir y Leer respectivamente)as como de
una variable FINAL tal como se indic en teora de rboles generales

EJERCICIOS DE RBOLES BINARIOS

Ejercicio No 1:

Supongamos que tenemos una funcin valor tal que dado un valor de tipo char (una letra del
alfabeto)devuelve un valor entero asociado a dicho identificador.Supongamos tambien la
existencia de un rbol de expresin T cuyos nodos hoja son letras del alfabeto y cuyos nodos
interiores son los caracteres *,+,-,/.Disear una funcin que tome como parmetros un nodo y
un rbol binario y devuelva el resultado entero de la evaluacin de la expresin representada.

Ejercicio No 2:

El recorrido en preorden de un determinado rbol binario es: GEAIBMCLDFKJH y en inorden


IABEGLDCFMKHJ .Resolver:

A)Dibujar el rbol binario.

B)Dar el recorrido en postorden.

C)Disear una funcin para dar el recorrido en postorden dado el recorrido en preorden e
inorden y escribir un programa para comprobar el resultado del apartado anterior

Ejercicio No 3:

Implementar una funcin no recursiva para recorrer un rbol binario en inorden

Ejercicio No 4:

Implementar una funcin no recursiva para recorrer un rbol binario en postorden.

Ejercicio No 5:

Escribir una funcin que realice la reflexin de un rbol binario.

Ejercicio No 6:

Escribir una funcin recursiva que encuentre el nmero de nodos de un rbol binario.

Ejercicio No 7:

Escribir una funcin recursiva que encuentre la altura de un rbol binario

EJERCICIOS DE RBOLES BINARIOS DE BSQUEDA


146
Manual del Alumno

Ejercicio No 1:

Puede reconstruirse de forma nica un ABB dado su inorden? Y dados el preorden y el


postorden?

Ejercicio No 2:

Construir un ABB con las claves 50,25,75,10,40,60,90,35,45,70,42.

Ejercicio No 3:

Construir un ABB equilibrado a partir de las claves 10,75,34,22,64,53,41,5,25,74,20,15,90.

Ejercicio No 4:

Bajo qu condiciones puede un rbol ser parcialmente ordenado y binario de bsqueda


simultneamente?Razonar la respuesta.

EJERCICIOS DE RBOLES AVL

Ejercicio No 1:

Dada la secuencia de claves enteras:100,29,71,82,48,39,101,22,46, 17,3,20,25,10.Representar


grficamente el rbol AVL correspondiente.

Elimine claves consecutivamente hasta encontrar un desequilibrio y dibuje la estructura del


rbol tras efectuarse la oportuna restauracin

Ejercicio No 2:

Obtener la secuencia de rotaciones resultante de la insercin del conjunto de elementos


{1,2,3,4,5,6,7,15,14,13,12,11,10,9,8} en un rbol AVL

Ejercicio No 3:

Inserte las claves en el orden indicado a fin de incorporarlas a un rbol AVL.

1. 10,100,20,80,40,70.
2. 5,10,20,30,40,50,60.

EJERCICIOS DE RBOLES APO

Ejercicio No 1:

Construir un APO con las claves 50,25,75,10,40,60,90,35,45,70,42.


147
Manual del Alumno
Ejercicio No 2:

Construir el TDA APO a partir del TDA pila.

Ejercicio No 3:

Puede construirse un APO de forma unvoca dado su recorrido en preorden?

EJERCICIOS DE RBOLES B ,B* y B+

Ejercicio No 1:

Dada la secuencia de claves enteras:190,57,89,90,121,170,35,48, 91,22,126,132 y 80;dibuje el


rbol B de orden 5 cuya raz es R,que se corresponde con dichas claves

Ejercicio No 2:

En el rbol R del problema anterior,elimine la clave 91 y dibuje el rbol resultante.Elimine ahora


la clave 48.Dibuje el rbol resultante,ha habido reduccin en el nmero de nodos?

Ejercicio No 3:

Dada la siguiente secuencia de claves:7,25,27,15,23,19,14,29,10, 50,18,22,46,17,70,33 y


58;dibuje el rbol B+ de orden 5 cuya raz es R,que se corresponde con dichas claves.

Ejercicio No 4:

Construir cada uno de los B-rboles que se van generando conforme se van insertando los
nmeros 1,9,32,3,53,43,44,57,67,7,45,34,23,12,23,56,73,65,49,85,89, 64,54,75,77,49, en un B-
rbol de orden 5.

Ejercicio No 5:

Supongamos que se insertan un conjunto de elementos en un B-rbol en un determinado


orden.La altura del B-rbol resultado es independiente del orden en que se han insertado los
elementos?.Raznese la respuesta.

EJERCICIOS DE GRAFOS

Ejercicio No 1:

Realizar un procedimiento que imprima un grafo

Ejercicio No 2:
148
Manual del Alumno
Construir un procedimiento que determine el nmero de componentes conexas que posee un
grafo.

Ejercicio No 3:

Construir procedimientos que devuelvan el grado de entrada y grado de salida de un vrtice


para un grafo dirigido y grado de un vrtice para uno no dirigido.

Ejercicio No 4:

Un grafo no dirigido se dice de Euler si existe un camino Euleriano que incluye a todas sus
aristas.Construir una funcin que dado un grafo no dirigido determine si es de Euler.

Ejercicio No 5:

Realizar un procedimiento que dado un grafo no dirigido determine cual es su grafo dual.

Ejercicio No 6:

Puede recuperarse un grafo no dirigido a partir de sus recorridos en anchura y profundidad?

Ejercicio No 7:

Dado un grafo no dirigido G=(V,E),con v>1 vrtices,demostrar que las 3 siguientes


afirmaciones son equivalentes:

a.-G es conexo y no tiene ciclos simples.

b.-G es conexo y tiene v-1 aristas.

c.-Cada par de vrtices de G estn conectados por exactamente un camino.

Das könnte Ihnen auch gefallen