Vous êtes sur la page 1sur 94

CAPTULO 16

ESTRUCTURAS DE DATOS EN C
Los tipos de datos provistos por los lenguajes de programacin de alto nivel son herramientas provistas por el lenguaje y que le permiten al programador representar eventos del problema que est resolviendo. Por ejemplo en un programa que calcule el crecimiento de una inversin, podemos tener una variable entera para modelar el nmero de meses transcurridos desde que se realiz la inversin. Tambin podemos usar una variable flotante para modelar el capital acumulado de dicha inversin. Los tipos de datos mencionados constituyen una implementacin restringida de las entidades matemticas que son los nmeros enteros y reales. Son restringidas por que slo nos permiten representar un subrango de los nmeros enteros o reales. La definicin de un tipo de dato no slo est formada por el rango de valores que pueden representarse con ese tipo sino tambin por las operaciones que pueden realizarse con datos de ese tipo. Por ejemplo el tipo entero define las operaciones de negativo, suma, resta, multiplicacin, divisin entera y mdulo, entre otras. Muchos de los problemas planteados para ser resueltos por la computadora, requieren que se manejen colecciones de datos relacionados, y para ello la mayora de los lenguajes de alto nivel nos permiten construir estructuras de datos a partir de datos simples u otras estructuras previamente definidas. En el lenguaje C estos tipos estructurados son los arreglos, las estructuras y las uniones. En estos tipos estructurados, aunque el programador puede establecer el tamao y forma del tipo de dato, las operaciones que pueden hacerse con los datos de ese tipo ya estn definidas. En este captulo, se estudiarn otras estructuras de datos no predefinidas por el lenguaje C pero que tienen gran aplicacin en las ciencias computacionales. Se definirn sus operaciones y la forma de implementarlas en el lenguaje C. Las estructuras de datos que se vern en este captulo son las pilas, colas, listas ligadas y rboles binarios. Existen otras estructuras y variantes de las que se tratarn en este captulo pero que no se tratan aqu por limitaciones de tiempo y espacio.

Pilas
Una pila es una coleccin ordenada de elementos homogneos (todos del mismo tipo) en la que slo pueden insertarse nuevos elementos o extraerse elementos por un extremo de la pila llamado tope . El primer elemento a extraerse es el ltimo que se insert. Slo se puede acceder al elemento que se encuentre en el tope de la pila. La figura 16-1 representa una pila usada para almacenar enteros en diferentes estados. La flecha apunta al tope de la pila. (16-1a) La pila est vaca; (16-1b) la pila despus de haber insertado el nmero 1; (16-1c) despus de haber insertado el nmero 2; (16-1d)

ITSON

Manuel Domitsu Kono

386

Estructuras de Datos en C

despus de haber insertado el nmero 3; (16-1e) despus de haber extrado el nmero 3; (16-1f) despus de haber insertado el nmero 4; (16-1g) despus de haber extrado el nmero 4; (16-1h) despus de haber extrado el nmero 2.
Tope 1 1 2 1 2 3 1 2 1 2 4 1 2 1

(a)

(b)

(c)

(d)

(e)

(f)

(g)

(h)

Figura 16-1

Operaciones Primitivas de las Pilas


Las operaciones primitivas definidas para una pila son: Inicializar: Vaca: Llena: Insertar: Extraer: Vaca la pila. Regresa verdadero si la pila est vaca. Regresa verdadero si la pila est llena. Inserta un elemento en la pila. Extrae un elemento de la pila.

Implementacin de una Pila en C


A continuacin se implementar una pila para almacenar enteros en C. El primer paso es determinar el tipo de datos en el que se almacenar la pila. Aqu se emplear un arreglo, aunque ms adelante se estudiar otra estructura de datos, la lista ligada, en la que puede implementarse una pila. Primero definiremos un tipo estructura que contendr los datos que nos permitan acceder a la pila:
typedef struct { int *ppila; int *ptope; int tamPila; } PILA;

/* Apuntador al inicio del arreglo en que se implementa la pila. */ /* Apuntador al tope de la pila. */ /* Tamao mximo de la pila. */

Todas las funciones que implementan las operaciones de la pila reciben como parmetro la direccin de una variable tipo PILA, la cual contiene los datos con los que las funciones van a operar. La operacin de inicializar la pila se implementa con la funcin inicializarPila() cuyo cdigo es:

ITSON

Manuel Domitsu Kono

Captulo 16

Estructuras de Datos en C

387

void inicializarPila(PILA *spila) { spila->ptope = spila->ppila; }

La funcin inicializarPila() slo requiere que el tope de la pila se encuentre al inicio del arreglo. Dado que slo se puede acceder al elemento apuntado por ptope no se requiere borrar realmente los datos. La operacin vaca se implementa mediante la funcin pilaVacia() que regresa un 1 si la pila est vaca, 0 en caso contrario y su cdigo es
int pilaVacia(PILA *spila) { return spila->ptope == spila->ppila; }

Vemos que si el apuntador ptope apunta al inicio del arreglo, la pila est vaca. La operacin llena se implementa mediante la funcin pilaLlena() que regresa un 1 si la pila est llena, 0 en caso contrario. Su cdigo es
int pilaLlena(PILA *spila) { return spila->ptope == spila->ppila + pila->tamPila; }

La pila est llena si el apuntador ptope apunta a la localidad siguiente del ltimo elemento del arreglo. La operacin inserta se implementa mediante la funcin insertarPila() que inserta un dato en la pila si no est llena. La funcin regresa un 1 si hay xito, 0 en caso contrario.
int insertarPila(PILA *spila, int dato) { if(pilaLlena(spila)) return 0; *(spila->ptope) = dato; spila->ptope++; return 1; }

La funcin primero verifica que haya espacio para insertar el dato. Si lo hay lo inserta en la localidad apuntada por ptope y luego mueve ptope a que apunte a la siguiente localidad. La operacin extraer se implementa con la funcin extraerPila() que extrae un dato de la pila si no est vaca. La funcin regresa un 1 si hay xito, 0 en caso contrario. El dato extrado es almacenado en la direccin dada por el parmetro pdato.
int extraerPila(PILA *spila, int *pdato) { if(pilaVacia(spila)) return 0; spila->ptope--; *pdato = *(spila->ptope); return 1; }

ITSON

Manuel Domitsu Kono

388

Estructuras de Datos en C

La funcin primero verifica que la pila contenga elementos. Si los hay mueve el apuntador ptope a que apunte al que est en el tope y luego lo copia a la localidad apuntada por el parmetro pdato. Note que no es necesario borrar fsicamente de la pila el dato extrado. Ya que no es posible acceder a las localidades ms all del apuntador ptope. A continuacin se muestra, como ejemplo, la secuencia de llamadas que producira los diferentes estados de la pila mostrados en la figura 16-1.
/* Tamao mximo de la pila */ const int TAMPILA = 10; /* Se declara la pila */ int pila[10]; int main(void) { /* Se inicializa la estructura con los datos para el manejo de la pila */ PILA spila = {pila, pila, TAMPILA}; int dato; inicializarPila(&spila); insertarPila(&spila, 1); insertarPila(&spila, 2); insertarPila(&spila, 3); extraerPila(&spila, &dato); insertarPila(&spila, 4); extraerPila(&spila, &dato); extraerPila(&spila, &dato); return 0; }

Implementacin de una Pila Generalizada en C


Las funciones que se implementaron en la seccin anterior slo sirven para pilas de enteros. Si deseamos tener una pila de caracteres, flotantes u otro tipo de datos deberemos modificar las funciones. Esto, sin embargo, hara que tuviramos una familia de funciones para cada tipo de dato. Otra solucin es desarrollar un grupo de funciones que implementen las operaciones de una pila que sean independientes del tipo de dato almacenado en la pila. La razn de la dependencia de las funciones en el tipo de datos es por dos razones: Primero el tamao de los datos, que determina el nmero de bytes que debe moverse ptope para apuntar de un dato a otro y segundo la forma de copiar los datos para hacer la insercin o extraccin de la pila. Para eliminar la dependencia en el tamao de los diferentes datos podemos hacer que las funciones para el manejo de la pila consideren a los datos como bloques de bytes en lugar de datos de un tipo en particular. Esto es, para la las funciones, cada elemento de la pila es un bloque de bytes, aunque el usuario declare la pila como un arreglo del tipo deseado.

ITSON

Manuel Domitsu Kono

Captulo 16

Estructuras de Datos en C

389

Apuntadores Tipo void


Para un modelo de memoria, los apuntadores ocupan el mismo espacio de memoria. por ejemplo en el modelo pequeo (small) los siguientes apuntadores ocupan 2 bytes cada uno
int *px; float *py;

el identificador de tipo que precede al identificador del apuntador no establece el tamao del apuntador sino el tamao del dato al que ste apunta. El compilador utiliza esta informacin cuando realiza operaciones de la aritmtica de apuntadores y para la desreferenciacin de los apuntadores. Hay un tipo de apuntador, sin embargo, que slo guarda la informacin de direccin sin conservar la informacin sobre el tamao del dato al que apunta. Este tipo de apuntador se conoce como un apuntador genrico o void. La sintaxis de la declaracin de un apuntador void es void *nomApunt; Las propiedades de los apuntadores void son las siguientes: 1. A un apuntador void le podemos asignar la direccin de una variable de cualquier tipo. Lo contrario no es vlido. Esto es, no se puede desreferenciar a un apuntador void. Por ejemplo:
int x; float y; void *pu, *pv; --pu = &x; pv = &y; --x = *pu; y = *pv; ---

/* Error */ /* Error */

La desreferenciacin no es vlida ya que no se conoce el tamao del valor al que apunta un apuntador void. En lugar de ello debemos primero convertir los apuntadores void a un apuntador de un tipo dado usando el operador cast:
--x = *(int *)pu; y = *(float *)pv; ---

2. A un apuntador void le podemos asignar el valor de un apuntador de cualquier tipo. Lo contrario, esto es, asignarle a un apuntador de un tipo dado el valor de un apuntador void tambin es vlido. Por ejemplo:
int *px; float *py; void *pu, *pv; --pu = px; pv = py;

ITSON

Manuel Domitsu Kono

390

Estructuras de Datos en C

--px = pu; py = pv; ---

3. La aritmtica de apuntadores no es vlida con los apuntadores void. Por ejemplo las siguientes instrucciones generan errores:
void *pu, *pv; pu++; pv -= 4; /* Error */ /* Error */

Estas operaciones slo pueden hacerse convirtiendo el apuntador a un tipo apropiado y luego efectuar la operacin aritmtica:
pu = (int *)pu + 1; pv = (float *)pv - 4;

o
pu = (char *)pu + 1 * sizeof(int); pv = (char *)pv - 4 * sizeof(float);

La propiedad de que a un apuntador void le podemos asignar la direccin de una variable de cualquier tipo nos permite crear funciones genricas para el manejo de pilas. A continuacin implementaremos las funciones para el manejo de pilas genricas como un mdulo. El archivo de encabezados de este mdulo es:

PILA.H
#ifndef PILA_H #define PILA_H typedef struct { void *ppila; void *ptope; int tamPila; int tamElem; } PILA; void inicializarPila(PILA *spila); int pilaVacia(PILA *spila); int pilaLlena(PILA *spila); int insertarPila(PILA *spila, void *dato); int extraerPila(PILA *spila, void *dato); #endif

/* Apuntador al arreglo donde se implementa la pila. */ /* Apuntador al final de la pila (donde se inserta o extrae un elemento). */ /* Tamao mximo de la pila. */ /* Tamao en bytes de cada dato a almacenarse en la pila. */

Los apuntadores ppila y ptope son declarados void para que puedan recibir direcciones de cualquier tipo de dato. Tambin vemos que a la estructura PILA se le ha agregado el campo tamElem que contiene el tamao en bytes de cada uno de los datos que van a almacenarse en la pila. El cdigo de las

ITSON

Manuel Domitsu Kono

Captulo 16

Estructuras de Datos en C

391

funciones para el manejo de la pila genrica se encuentra en PILA.C. Las funciones inicializarPila() y pilaVacia() no sufren modificaciones con respecto a las funciones para la pila de enteros. PILA.C
/************************************************************* * PILA.C * * Este mdulo implementa las operaciones de una pila * generalizada, esto es, que opera con cualquier tipo de dato. * La pila debe implementarse en un arreglo del tipo del dato * deseado. Todas las funciones reciben como parmetro un * apuntador a una estructura tipo PILA, que contiene la * informacin que requieren las funciones para manejar la * pila. La estructura est definida en "PILA.H". *************************************************************/ #include <mem.h> #include "pila.h" /************************************************************* * void inicializarPila(PILA *spila) * * Esta funcin inicializa (vaca) una pila. spila es un * apuntador a la estructura que contiene los datos que definen * la pila. *************************************************************/ void inicializarPila(PILA *spila) { spila->ptope = spila->ppila; } /************************************************************* * int pilaVacia(PILA *spila) * * Esta funcin regresa un 1 si la pila est vaca, 0 en caso * contrario. spila es un apuntador a la estructura contiene * los datos que definen la pila. *************************************************************/ int pilaVacia(PILA *spila) { return spila->ptope == spila->ppila; } /************************************************************* *int pilaLlena(PILA *spila) * * Esta funcin regresa un 1 si la pila est llena, 0 en caso * contrario. spila es un apuntador a la estructura que * contiene los datos que definen la pila. *************************************************************/ int pilaLlena(PILA *spila) { return spila->ptope == (char *)spila->ppila + spila->tamPila * spila->tamElem; } /************************************************************* * int insertarPila(PILA *spila, void *dato) * * Esta funcin inserta un dato en la pila si no est llena. * La funcin regresa un 1 si hay xito, 0 en caso contrario. * spila es un apuntador a la estructura que contiene los

ITSON

Manuel Domitsu Kono

392

Estructuras de Datos en C

* datos que definen la pila. dato es apuntador al dato a * insertar. *************************************************************/ int insertarPila(PILA *spila, void *dato) { if(pilaLlena(spila)) return 0; memcpy(spila->ptope, dato, spila->tamElem); spila->ptope = (char *)spila->ptope + spila->tamElem; return 1; } /************************************************************* * int extraerPila(PILA *spila, void *dato) * * Esta funcin extrae un dato de la pila si no est vaca. * La funcin regresa un 1 si hay xito, 0 en caso contrario. * spila es un apuntador a la estructura que contiene los * datos que definen la pila. dato es apuntador a la variable * en que se almacena el dato a extraer. *************************************************************/ int extraerPila(PILA *spila, void *dato) { if(pilaVacia(spila)) return 0; spila->ptope = (char *)spila->ptope - spila->tamElem; memcpy(dato, spila->ptope, spila->tamElem); return 1; }

La funcin pilaLlena() regresa 1 si el apuntador ptope apunta a la siguiente localidad despus del ltimo elemento del arreglo. La direccin de esa localidad se calcula sumndole a la direccin de inicio del arreglo el nmero de bytes que ocupa todo el arreglo que contiene la pila, el cual se obtiene por:
spila->tamPila * spila->tamElem

Note la conversin del apuntador ppila de void a char para poder efectuar la suma. La funcin insertarPila() verifica primero si hay espacio en la pila para insertar el dato. Si lo hay utiliza la funcin memcpy() para copiar el dato a la localidad apuntada por ptope. En este caso el dato se maneja como un bloque de bytes de tamao tamElem. La funcin memcpy() cuyo prototipo est en mem.h tiene la siguiente sintaxis: void *memcpy(void *dest , const void *fte, size_t n); memcpy() copia n bytes de la localidad apuntada por fuente a la localidad apuntada por dest. La funcin regresa el valor de dest. Por ltimo, la funcin insertarPila() incrementa ptope para que apunte a la localidad siguiente al elemento insertado. La funcin extraerPila() trabaja en forma contraria a la funcin insertarPila(). El siguiente listado, DEMPILA.C , es un mdulo que implementa un programa de demostracin que muestra el comportamiento de una pila. Este mdulo debe ligarse a los mdulos: PILA y UTILS.

ITSON

Manuel Domitsu Kono

Captulo 16

Estructuras de Datos en C

393

DEMPILA.C
#include <stdio.h> #include <conio.h> #include "utils.h" #include "pila.h" const int TAMPILA = 10; char cpila[10]; /* Tamao mximo de la pila */ /* Pila */

/* Estructura con los datos de la pila */ PILA spila = {cpila, cpila, TAMPILA, 1}; void despliegaPantallaInicial(PILA *spila); void despliegaPila(PILA *spila); int main(void) { char c; despliegaPantallaInicial(&spila); while((c = getch()) != ESC) { /* Si se presion una tecla normal */ if(c) { /* Si la pila est llena */ if(!insertarPila(&spila, &c)) msj_error(0,22,"Pila llena!", "", ATTR(RED, LIGHTGRAY,0)); despliegaPila(&spila); } /* Si se presion la tecla [Supr] */ else if(getch() == DEL) { /* Si la pila no est vaca */ if(extraerPila(&spila, &c)) { gotoxy(39,22); putchar(c); } else msj_error(0,22,"Pila vaca!", "", ATTR(RED, LIGHTGRAY,0)); despliegaPila(&spila); } } return 0; } void despliegaPantallaInicial(PILA *spila) { int ic; clrscr(); gotoxy(15,3); printf("Este programa demuestra el comportamiento de una"); gotoxy(15,4); printf("pila. En esta pila se almacenan caracteres con"); gotoxy(15,5); printf("slo teclearlos. Para extraer un carcter de la"); gotoxy(15,6); printf("pila presiona la tecla [Supr]."); gotoxy(15,7); printf("Para terminar presiona la tecla [Esc]."); gotoxy(37, 9); printf("PILA"); gotoxy(37,10); printf("+---+");

ITSON

Manuel Domitsu Kono

394

Estructuras de Datos en C

gotoxy(30,11); printf("Tope \x10"); for(i = 0; i < spila->tamPila; i++) { gotoxy(37,11+i); printf(" "); } gotoxy(37,11+i); printf("+---+"); gotoxy(39,11); } void despliegaPila(PILA *spila) { int i; /* Borra la imagen de la pila anterior */ for(i = 0; i < spila->tamPila; i++) { gotoxy(30,11+i); printf(" "); gotoxy(39,11+i); putchar(' '); } gotoxy(30,12+i); printf(" "); for(i = 0; i < (char *)spila->ptope - spila->ppila; i++) { gotoxy(39,11+i); putchar(*((char *)(spila->ppila)+i)); } if(i == 10) gotoxy(30,12+i); else gotoxy(30,11+i); printf("Tope \x10"); if(i == 10) gotoxy(39,12+i); else gotoxy(39,11+i); }

Ejercicios Sobre Pilas


1. Escribe una funcin que nos permita inspeccionar el elemento del tope de la pila genrica. La pila deber quedar intacta, es decir, el elemento en el tope de la pila deber permanecer en la pila. La sintaxis de la funcin es int topePila(PILA *spila , void *dato); donde spila es el apuntador a la estructura con los datos de la pila y dato es un apuntador a la variable en la que se va a almacenar una copia del elemento en el tope de la pila. La funcin regresa 1 si hubo xito, 0 en caso contrario (la pila est vaca). 2. Escribe una funcin que nos regrese el nmero de elementos en una pila genrica. La sintaxis de la funcin es int longPila(PILA *spila ); donde spila es el apuntador a la estructura con los datos de la pila.

ITSON

Manuel Domitsu Kono

Captulo 16

Estructuras de Datos en C

395

Aplicaciones de Pilas
Las aplicaciones de las pilas en las ciencias computacionales son muchas y van desde la implementacin del mecanismo de las llamadas a subrutinas de los lenguajes de alto nivel, la evaluacin de expresiones en notacin postfija, la verificacin de los niveles de parntesis de una expresin, etc. Aqu se da una descripcin, un poco simplificada, del mecanismo de la llamada a una subrutina mientras que en el problema 1, al final del captulo se plantea otra aplicacin de las pilas: Una calculadora RPN. Cuando se carga un programa en C a la memoria RAM, para ser ejecutado, se inicializa uno de los registros del microprocesador, llamado apuntador del programa (IP) a la primera instruccin dentro de la funcin main(). Se crean las variables con clase de almacenamiento esttica en el segmento de datos y en el segmento que le corresponde segn el modelo de memoria se crea una pila, llamada pila del sistema. Si la funcin main() tiene variables automticas estas son creadas en la pila, es decir se reserva memoria en la pila para ellas. Para ejecutar el programa, el microprocesador lee de la direccin de memoria apuntada por el IP, la primera instruccin y en forma automtica incrementa el IP para que apunte a la segunda instruccin. Luego decodifica la primera instruccin y la ejecuta. A continuacin utiliza el IP para leer la segunda instruccin del programa incrementando en forma automtica el IP para que apunte a la tercera instruccin, decodifica la segunda instruccin y la ejecuta y as consecutivamente. Si la instruccin decodificada es una llamada a una funcin, el procedimiento para ejecutar la funcin es el siguiente: 1. Si la funcin tiene parmetros, stos son creados en la pila y los valores de los argumentos con los que se llama a la funcin son copiados a stos. Los parmetros en la pila aparecen en el orden inverso del que se declaran. 2. Se almacena en la pila la direccin almacenada en el IP. Esta es la direccin de la instruccin a la que regresar el programa despus de regresar de la funcin. 3. Si la funcin tiene variables automticas, stas son creadas en la pila. 4. Se carga en el IP la direccin de la primera instruccin de la funcin para su ejecucin. 5. Se lee la primera instruccin de la funcin, se incrementa el IP, se decodifica la instruccin y se ejecuta. Este ltimo paso se repite para el resto de las instrucciones de la funcin. Al terminar la ejecucin, ocurren los siguientes eventos: 1. Las variables automticas son destruidas, extrayndolas de la pila y descartando su contenido. 2. Se extrae de la pila la direccin de regreso de la funcin y se carga al IP. 3. Se destruyen los parmetros de la funcin, extrayndolos de la pila y descartando su contenido. 4. Se almacena el valor regresado por la funcin.

ITSON

Manuel Domitsu Kono

396

Estructuras de Datos en C

5. Como ya se carg en el IP la direccin de la siguiente instruccin despus de la llamada a la funcin, la siguiente instruccin leda por el microprocesador ser sa, con lo que el control del programa regresa a la funcin anterior. El proceso descrito anteriormente se ilustra con el siguiente programa. Las direcciones de memoria son supuestas y por simplificar supondremos que cada instruccin se almacena en dos bytes. En realidad, diferentes instrucciones tienen diferentes tamaos. E la tercera columna se muestra el nmero de la n figura que ilustra el estado de la del sistema cada que ocurre un cambio.

Direcciones

Instrucciones
int main(void) { int a, b;

Estado de la pila
Figura 16-2a Figura 16-2b

0x1000 0x1002 0x1004 0x1006 0x1008 }

instruccin_01; instruccin_02; f1(a, b); instruccin_03; instruccin_04; int f1(int x, int y) { int c, d;

Figura 16-2c Figura 16-2n

Figura 16-2d

0x2000 0x2002 0x2004 0x2006 0x2008 0x2010 0x2012 0x2014

instruccin_11; instruccin_12; f2(c); instruccin_13; instruccin_14; f3(d); instruccin_15; instruccin_16; }

Figura 16-2e Figura 16-2g Figura 16-2h Figura 16-2j

0x2100 0x2102

int f2(int z) { int e; instruccin_21; instruccin_22; }

Figura 16-2f

0x2200 0x2202

int f3(int w) { int f; instruccin_31; instruccin_32; }

Figura 16-2i

ITSON

Manuel Domitsu Kono

Captulo 16

Estructuras de Datos en C

397

Tope

a b

a b y x

b a 0x1006

a b y x c d

b a 0x1006

a b y x c d z

b a 0x1006

a b y x c d z e

b a 0x1006

c 0x2006

c 0x2006

(a)
a b y x c d Tope a b y x c d w

(b)
a b y x c d w f

(c)
a b y x c d

(d)
a b

(e)

(f)

b a 0x1006

b a 0x1006

b a 0x1006

b a 0x1006

d 0x2012

d 0x2012

(g)

(h)

(i) Figura 16-2.

(j)

(k)

(l)

Colas
Colas lineales
Una cola es una coleccin ordenada de elementos homogneos en la que los nuevos elementos se insertan por uno de los extremos de la cola llamada final y slo pueden extraerse elementos por el otro extremo de la cola llamado frente . El primer elemento a extraerse es el primero que se insert. La figura 16-3 representa una cola usada para almacenar enteros en diferentes estados. (16-3a) La cola est vaca; (16-3b) la cola despus de haber insertado el nmero 1; (16-3c) despus de haber insertado el nmero 2; (16-3d) despus de haber insertado el nmero 3; (16-3e) despus de haber extrado el nmero 1; (16-3f) despus de haber insertado el nmero 4; (16-3g) despus de haber extrado el nmero 2; (16-3h) despus de haber extrado el nmero 3.

ITSON

Manuel Domitsu Kono

398

Estructuras de Datos en C

Frente = Final

Frente Final

Frente Final

1 2

Frente Final

1 2 3

(a)
Frente Final 2 3 Frente Final

(b)
2 3 4

(c)

(d)

Frente Final

3 4

(e)

(f)

(g)

Frente Final

(h)

Figura 16-3.

Operaciones Primitivas de las Colas


Las operaciones primitivas definidas para una cola son: Inicializar: Vaca: Llena: Insertar: Extraer: Vaca la cola. Regresa verdadero si la cola est vaca. Regresa verdadero si la cola est llena. Inserta un elemento en la cola. Extrae un elemento de la cola.

Implementacin de una Cola Lineal en C


A continuacin se implementar una cola para almacenar enteros en C. La cola se almacenar en un arreglo, aunque ms adelante se estudiar otra estructura de datos, la lista ligada, en la que puede implementarse una cola. Primero definiremos un tipo estructura que contendr los datos que nos permitan acceder a la cola
typedef struct { int *pcola; int *pfrente; int *pfinal; int tamCola; } COLA;

/* Apuntador al arreglo donde se implementa la cola. */ /* Apuntador al inicio de la cola (donde se extrae un elemento. */ /* Apuntador al final de la cola (donde se inserta un elemento. */ /* Tamao mximo de la cola. */

ITSON

Manuel Domitsu Kono

Captulo 16

Estructuras de Datos en C

399

Todas las funciones que implementan las operaciones de la cola reciben como parmetro la direccin de una variable tipo COLA, la cual contiene los datos con los que las funciones van a operar. La operacin de inicializar la cola se implementa con la funcin inicializarCola() cuyo cdigo es:
void inicializarCola(COLA *scola) { scola->pfrente = scola->pfinal = scola->pcola; }

La funcin inicializarCola() slo requiere que tanto el frente como el final de la cola se encuentren al inicio del arreglo. La operacin vaca se implementa mediante la funcin colaVacia() que regresa un 1 si la cola est vaca, 0 en caso contrario y su cdigo es
int colaVacia(COLA *scola) { return scola->pfinal == scola->pfrente; }

Vemos que si los apuntadores al frente y al final de la cola apuntan a la misma localidad, la cola est vaca. La operacin llena se implementa mediante la funcin colaLlena() que regresa un 1 si la cola est llena, 0 en caso contrario. Su cdigo es
int colaLlena(COLA *scola) { return scola->pfinal == scola->pcola + scola->tamCola; }

La cola est llena si el apuntador pfinal apunta a la localidad siguiente del ltimo elemento del arreglo. La operacin inserta se implementa mediante la funcin insertarCola() que inserta un dato en la cola si no est llena. La funcin regresa un 1 si hay xito, 0 en caso contrario.
int insertarCola(COLA *scola, int dato) { if(colaLlena(scola)) return 0; *(scola->pfinal) = dato; scola->pfinal++; return 1; }

La funcin primero verifica que haya espacio para insertar el dato. Si lo hay, lo inserta en la localidad apuntada por pfinal y luego mueve pfinal a que apunte a la siguiente localidad. La operacin extraer se implementa con la funcin extraerCola() que extrae un dato de la cola si no est vaca. La funcin regresa un 1 si hay xito, 0 en caso contrario. El dato extrado es almacenado en la direccin dada por el parmetro pdato.
int extraerCola(COLA *scola, int *pdato) { if(colaVacia(scola)) return 0;

ITSON

Manuel Domitsu Kono

400

Estructuras de Datos en C

*pdato = *(scola->pfrente); scola->pfrente++; return 1; }

La funcin primero verifica que la cola contenga elementos. Si los hay, copia el elemento apuntado por pfrente a la localidad apuntada por el parmetro pdato, luego incrementa el apuntador pfrente a que apunte al siguiente elemento a extraer. Note que no es necesario borrar fsicamente de la cola el dato extrado. Ya que no es posible regresar el apuntador pfrente para que apunte a un dato previamente extrado. A continuacin se muestra, como ejemplo, la secuencia de llamadas que p roducira los diferentes estados de la cola mostrados en la figura 16-3.
const int TAMCOLA = 10;

int cola[10]; int main(void) { COLA scola = {cola, cola, cola, TAMCOLA}; int dato; inicializarCola(&scola); insertarCola(&scola, 1); insertarCola(&scola, 2); insertarCola(&scola, 3); extraerCola(&scola, &dato); insertarCola(&scola, 4); extraerCola(&scola, &dato); extraerCola(&scola, &dato); return 0; }

Podemos ver de la figura 16-3 y de las funciones insertarCola() y extraerCola() que los apuntadores al frente y al final de la cola siempre avanzan. Por lo tanto, el espacio liberado al extraer un elemento no puede reutilizarse y la condicin de cola llena puede ocurrir an cuando el nmero de elementos en la cola sea inferior al tamao del arreglo en que se almacena la cola, inclusive podemos tener la condicin de cola llena con la cola vaca de elementos.

Colas Circulares
La cola implementada en la seccin anterior se llama cola lineal y como vimos presenta el problema de que eventualmente caeremos en la situacin de cola llena al no poder reutilizar el espacio liberado al extraer elementos. Una implementacin de una cola que permite la reutilizacin del espacio liberado al extraer elementos se obtiene permitiendo que los apuntadores al frente y al final de la cola se regresen al principio de la cola despus de haber llegado al final del arreglo. Podemos imaginarnos que la cola forma un crculo con el principio del arreglo unido al final del arreglo. Este tipo de cola se llama cola circular. En la figura 16-4 se muestra una cola circular con tamao 5. En la figura 16-4a se muestra a la cola con tres elementos: 1, 2, 3. En la figura 16-4b, se ha insertado un cuarto elemento, el nmero 4. En este

ITSON

Manuel Domitsu Kono

Captulo 16

Estructuras de Datos en C

401

momento la cola se ha llenado. Note que no podemos insertar otro elemento en la localidad apuntada por pfinal ya que de hacerlo el apuntador pfinal se regresara al principio del arreglo y apuntara a la misma localidad apuntada por el apuntador pfrente. Esto sin embargo es la condicin para que la cola se encuentre vaca. En la figura 16-4c se ha extrado el nmero 1 de la cola. Esto nos permite insertar un nuevo elemento en la cola, el nmero 5, como se muestra en la figura 16-d. Podemos ver que el apuntador al final de la cola se ha movido al inicio del arreglo. Tambin podemos ver que de nuevo tenemos que la cola est llena ya que no podemos insertar un elemento en la localidad apuntada por pfinal ya que de hacerlo se volveran a empalmar los apuntadores al frente y final de la cola indicndonos que la pila esta vaca. De lo anterior podemos ver que en una cola circular cuando mucho podemos almacenar un nmero de elementos igual al tamao del arreglo menos uno.
Frente Final Frente Final Frente

1 2 3

1 2 3 4

Frente Final

2 3 4

Final

2 3 4 5

(a)

(b) Figura 16-4.

(c)

(d)

Implementacin de una Cola Circular en C


La implementacin de una cola circular slo requiere que se modifiquen las rutinas colaLlena(), insertarCola() y extraerCola(). Las rutinas inicializarCola() y colaVaca() permanecen iguales. Consideremos de nuevo el caso de una cola para almacenar enteros. La figura 16-4b muestra que una de las maneras en que una cola circular puede llenarse es cuando el frente de la cola est al inicio del arreglo y el final de la cola apunta a la ltima localidad del arreglo. La figura 16-d muestra otra de las situaciones en las que la cola se llena. Aqu el final de la cola se encuentra a una localidad detrs del frente de la cola. Por lo tanto la funcin colaLlena() debe verificar ambas situaciones:
int colaLlena(COLA *scola) { return scola->pfinal == scola->pfrente+scola->tamCola-1 || scola->pfrente == scola->pfinal + 1; }

Las modificaciones a las funciones insertarCola() y extraerCola() consisten en verificar si los apuntadores al final y al frente de la cola han llegado al final del arreglo, en cuyo caso se regresan al inicio del arreglo:
int insertarCola(COLA *scola, int dato) { if(colaLlena(scola)) return 0; *(scola->pfinal) = dato; scola->pfinal++; if(scola->pfinal == scola->pcola + scola->tamCola) scola->pfinal = scola->pcola;

ITSON

Manuel Domitsu Kono

402

Estructuras de Datos en C

return 1; } int extraerCola(COLA *scola, int *pdato) { if(colaVacia(scola)) return 0; *pdato = *(scola->pfrente); scola->pfrente++; if(scola->pfrente == scola->pcola + scola->tamCola) scola->pfrente = scola->pcola; return 1; }

Implementacin de una Cola Circular Generalizada en C


Al igual que como lo hicimos con las pilas, podemos modificar las funciones que implementan las operaciones de una cola circular para que sean independientes del tipo de dato almacenado en la cola. Estas nuevas funciones se implementan como un mdulo. El archivo de encabezados de este mdulo es:

COLA.H
#ifndef COLA_H #define COLA_H typedef struct { void *pcola; void *pfrente; void *pfinal; int tamCola; int tamElem; } COLA; void inicializarCola(COLA *scola); int colaVacia(COLA *scola); int colaLlena(COLA *scola); int insertarCola(COLA *scola, void *dato); int extraerCola(COLA *scola, void *dato); #endif

/* Apuntador al arreglo donde se implementa la cola. */ /* Apuntador al inicio de la cola (donde se extrae un elemento). */ /* Apuntador al final de la cola (donde se inserta un elemento). */ /* Tamao del arreglo donde implementa la cola. */ /* Tamao en bytes de cada dato a almacenarse en la cola. */

El cdigo de las funciones para el manejo de la cola genrica se encuentra en COLA.C. COLA.C.
/************************************************************* * COLA.C * * Este mdulo implementa las operaciones de una cola circular * generalizada, esto es, que opera con cualquier tipo de dato * La cola debe implementarse en un arreglo del tipo de dato

ITSON

Manuel Domitsu Kono

Captulo 16

Estructuras de Datos en C

403

* deseado. Todas las funciones reciben como parmetro un * apuntador a una estructura tipo COLA, que contiene la * informacin que requieren las funciones para manejar la cola. * La estructura esta definida en "COLA.H". *************************************************************/ #include <mem.h> #include "cola.h" /************************************************************* * void inicializarCola(COLA *scola) * * Esta funcin inicializa (vaca) una cola. scola es un * aputador a la estructura que contiene los datos que definen * la cola. *************************************************************/ void inicializarCola(COLA *scola) { scola->pfrente = scola->pfinal = scola->pcola; } /************************************************************* * int colaVacia(COLA *scola) * * Esta funcin regresa un 1 si la cola esta vaca, 0 en caso * contrario. scola es un apuntador a la estructura que * contiene los datos que definen la cola. *************************************************************/ int colaVacia(COLA *scola) { return scola->pfinal == scola->pfrente; } /************************************************************* * int colaLlena(COLA *scola) * * Esta funcin regresa un 1 si la cola esta llena, 0 en caso * contrario. scola es un apuntador a la estructura que * contiene los datos que definen la cola. *************************************************************/ int colaLlena(COLA *scola) { return scola->pfinal == (char *)scola->pfrente + (scola->tamCola - 1)* scola->tamElem || scola->pfrente == (char *)scola->pfinal + scola->tamElem; } /************************************************************* * int insertarCola(COLA *scola, void *dato) * * Esta funcin inserta un dato en la cola si no esta llena. * La funcin regresa un 1 si hay xito, 0 en caso contrario * scola es un apuntador a la estructura que contiene los * datos que definen la cola. dato es apuntador al dato a * insertar. *************************************************************/ int insertarCola(COLA *scola, void *dato) { if(colaLlena(scola)) return 0; memcpy(scola->pfinal, dato, scola->tamElem); scola->pfinal = (char *)scola->pfinal + scola->tamElem; if(scola->pfinal == (char *)scola->pcola + scola->tamCola * scola->tamElem) scola->pfinal = scola->pcola;

ITSON

Manuel Domitsu Kono

404

Estructuras de Datos en C

return 1; } /************************************************************* * int extraerCola(COLA *scola, void *dato) * * Esta funcin extrae un dato de la cola si no esta vaca. * La funcin regresa un 1 si hay xito, 0 en caso contrario * scola es un apuntador a la estructura que contiene los * datos que definen la cola. dato es apuntador a la variable * en que se almacena el dato a extraer. *************************************************************/ int extraerCola(COLA *scola, void *dato) { if(colaVacia(scola)) return 0; memcpy(dato, scola->pfrente, scola->tamElem); scola->pfrente = (char *)scola->pfrente + scola->tamElem; if(scola->pfrente == (char *)scola->pcola + scola->tamCola * scola->tamElem) scola->pfrente = scola->pcola; return 1; }

El siguiente listado, DEMCOLA.C, es un mdulo que implementa un programa de demostracin que muestra el comportamiento de una cola circular. Este mdulo debe ligarse a los mdulos: COLA y UTILS.

DEMCOLA.C
#include <stdio.h> #include <conio.h> #include "utils.h" #include "cola.h" const int TAMCOLA = 10; char ccola[10]; /* Tamao mximo de la cola */

/* Cola */

/* Estructura con los datos de la cola */ COLA scola = {ccola, ccola, ccola, TAMCOLA, 1}; void despliegaPantallaInicial(COLA *scola); void despliegaCola(COLA *scola); int main(void) { char c; despliegaPantallaInicial(&scola); while((c = getch()) != ESC) { /* Si se presion una tecla normal */ if(c) { /* Si la cola esta llena */ if(!insertarCola(&scola, &c)) msj_error(0,22,"Cola llena!, [Enter]", "\r", ATTR(RED, LIGHTGRAY,0));

ITSON

Manuel Domitsu Kono

Captulo 16

Estructuras de Datos en C

405

despliegaCola(&scola); } /* Si se presion la tecla [Supr] */ else if(getch() == DEL) { /* Si la cola no esta vaca */ if(extraerCola(&scola, &c)) { gotoxy(39,22); putchar(c); } else msj_error(0,22,"Cola vaca!, [Enter]", "\r", ATTR(RED, LIGHTGRAY,0)); despliegaCola(&scola); } } return 0; } void despliegaPantallaInicial(COLA *scola) { int i; clrscr(); gotoxy(15,3); printf("Este programa demuestra el comportamiento de una"); gotoxy(15,4); printf("cola. En esta cola se almacenan caracteres con"); gotoxy(15,5); printf("solo teclearlos. Para extraer un carcter de la"); gotoxy(15,6); printf("cola presiona la tecla [Supr]."); gotoxy(15,7); printf("Para terminar presiona la tecla [Esc]."); gotoxy(37, 9); printf("COLA"); gotoxy(37,10); printf("+---+"); gotoxy(22,11); printf("Inicio = Fin \x10"); for(i = 0; i < scola->tamCola; i++) { gotoxy(37,11+i); printf(" "); } gotoxy(37,11+i); printf("+---+"); gotoxy(39,11); } void despliegaCola(COLA *scola) { int i, t; for(i = 0; i < scola->tamCola; i++) { gotoxy(22,11+i); printf(" gotoxy(39,11+i); putchar(' '); }

");

if(scola->pfrente <= scola->pfinal) { for(i= 0; i < (char *)scola->pfrente - scola->pcola; i++) { gotoxy(39,11+i); putchar(' '); } if(scola->pfrente == scola->pfinal) { gotoxy(22,11+i); printf("Inicio =");

ITSON

Manuel Domitsu Kono

406

Estructuras de Datos en C

} else { gotoxy(28,11+i); printf("Inicio \x10"); } for(; i < (char *)scola->pfinal - scola->pcola; i++) { gotoxy(39,11+i); putchar(*((char *)(scola->pcola)+i)); } gotoxy(31,11+i); printf("Fin \x10"); gotoxy(39,11+i); } else /* scola->pfrente > scola->pfinal */ { for(i = 0; i < (char *)scola->pfinal - scola->pcola; i++ { gotoxy(39,11+i); putchar(*((char *)(scola->pcola)+i)); } t = i; gotoxy(31,11+i); printf("Fin \x10"); for(; i < (char *)scola->pfrente - scola->pcola; i++) { gotoxy(39,11+i); putchar(' '); } gotoxy(28,11+i); printf("Inicio \x10"); for(; i < scola->tamCola; i++) { gotoxy(39,11+i); putchar(*((char *)(scola->pcola)+i)); } gotoxy(39,11+t); } }

Ejercicio Sobre Colas


Escribe una funcin que nos regrese el nmero de elementos en una cola circular genrica. La sintaxis de la funcin es int longCola(COLA *scola ); donde scola es el apuntador a la estructura con los datos de la cola.

Aplicaciones de Colas
Las colas son eventos que ocurren en la vida diaria: Hacemos colas en los bancos, supermercados, oficinas de gobierno, etc. Una cola se forma cuando la tasa de llegada de los clientes es mayor que la velocidad con que son atendidos. En las ciencias computacionales, tambin ocurren colas ya que las

ITSON

Manuel Domitsu Kono

Captulo 16

Estructuras de Datos en C

407

diferentes componentes de una computadora operan a diferentes velocidades: Por ejemplo los caracteres que tecleamo s se van almacenando en una cola llamada buffer del teclado en espera de ser procesados por la computadora. Tambin la informacin que se desea mandar a un archivo o leer de un archivo es escrita o leda primero a una cola llamada buffer del archivo y luego enviada al archivo o al programa. Otra aplicacin de colas son las llamadas colas de impresin. Las colas de impresin por lo general se implementan en ambientes multiproceso, esto es, en los sistemas operativos que pueden ejecutar ms de un proceso simultneamente. Cuando un proceso desea imprimir algo se lo enva a un proceso llamado servidor de impresin. El servidor de impresin almacena las peticiones de impresin en una cola y las va procesando una por una. Otra rea de aplicacin de las colas es la de simulacin de eventos que generan colas como las de los bancos, supermercados, etc. En estas simulaciones se estudia el comportamiento de las colas con el fin de determinar el nmero adecuado de cajeras que deben estar atendiendo a los clientes. Si el nmero de cajeras es pequeo, las colas sern muy largas y probablemente los clientes busquen otro banco o supermercado. Si el nmero de cajeras es grande, el negocio incurrir en fuertes gastos por salarios y prestaciones. En el problema 2 al final del captulo se propone un programa que simule el comportamiento de un pequeo aeropuerto que consta de una sola pista para el despegue y aterrizaje de los aviones.

Asignacin Dinmica de Memoria


Cuando definimos variables, apartamos memoria para esas variables. Esa solicitud de memoria se dice que ocurre al tiempo de compilacin, ya que es el compilador quien genera el cdigo necesario para asignarnos la memoria requerida por las variables. Se dice que el compilador hace una asignacin esttica de memoria ya que una vez compilado el programa no podemos modificar el espacio asignado a las variables. Lo anterior presenta el inconveniente de que debemos conocer el tamao de nuestras variables al momento de estar diseando el programa. Por ejemplo si se define un arreglo de un tamao dado y al estar ejecutando el programa, resulta que el nmero de datos que se desea almacenar es mayor, lo nico que podemos hacer es modificar el cdigo y recompilar el programa. Lo ideal sera que al ejecutar el programa y al saber el nmero de datos que se desean almacenar en el arreglo, hiciramos la solicitud del espacio de memoria necesario. Esta solicitud de memoria al tiempo de ejecucin se conoce como asignacin dinmica de memoria. C posee un mecanismo mediante el cual podemos pedirle al sistema operativo bloques de memoria. Tambin podemos liberar los bloques una vez que ya no los ocupemos para que estn disponibles para futuras solicitudes. La cantidad de memoria disponible para esas solicitudes depende del hardware, sistema operativo, e implementacin del compilador. En un programa compilado en Turbo C para correr bajo el sistema operativo MSDOS en una computadora personal compatible con IBM, la memoria disponible para esas peticiones depende del modelo de memoria empleado (y por supuesto de la memoria fsica disponible en la computadora).

ITSON

Manuel Domitsu Kono

408

Estructuras de Datos en C

El Montculo
La memoria asignada a un programa se divide en cuatro partes: La memoria ocupada por el cdigo, la ocupada por las variables estticas, la ocupada por la pila y la memoria ocupada por el montculo (heap). Es del mpntculo que el sistema operativo nos asigna los bloques que solicitamos dinmicamente. En la figura 16-5 se muestra los tamaos mximos de esas porciones dependiendo del modelo de memoria empleado en la compilacin del programa. En el modelo pequeito, todo el programa no debe exceder los 64 KB. Por lo tanto el tamao mximo del montculo es: 64 KB - (cdigo + variables estticas + pila). En los modelos pequeo y mediano el cdigo ocupa sus propios segmentos, y los datos tambin tienen su propio segmento, el tamao mximo del montculo es: 64 KB - (variables estticas + pila). Los primeros 640 KB de la memoria de la computadora se conocen como la memoria convencional. Parte de la memoria convencional est ocupada por el sistema operativo, manejadores de dispositivos, programas residentes en memoria, etc. y el programa que estamos ejecutando. En un programa compilado con el modelo de memoria pequeo o mediano, el resto de la memoria se conoce como el montculo lejano y tambin puede ser accesado por nuestro programa.
Modelo pequeito (tiny) Un slo segmento ( 64KB) Cdigo Variables estticas Montculo Pila Modelo pequeo (small) Un segmento ( 64KB) Cdigo Un segmento ( 64KB) Variables estticas Montculo Pila Hasta el resto de memoria Montculo lejano Modelo mediano (medium) Uno o varios segmentos cada uno ( 64KB) Cdigo ... Un segmento ( 64KB) Variables estticas Montculo Pila Hasta el resto de memoria Montculo lejano

Figura 16-5 NOTA: En la figura 5, las flechas indican la direccin de crecimiento de la pila y el montculo, es decir el orden en que se va asignando la memoria conforme se va requiriendo.

En los modelos compacto, grande y gigante, el cdigo tambin ocupa sus propios segmentos. Las variables estticas tienen sus propios segmentos y la pila tambin tiene el suyo. El resto de la memoria convencional disponible es el montculo y est disponible para nuestro programa.

ITSON

Manuel Domitsu Kono

Captulo 16

Estructuras de Datos en C

409

Modelo compacto (compact) Un segmento ( 64KB) Cdigo Un segmento ( 64KB) Variables estticas Un segmento ( 64KB) Pila Hasta el resto de memoria Montculo

Modelo grande (large) Uno o varios segmentos cada uno ( 64KB) Cdigo ... Un segmento ( 64KB) Variables estticas Un segmento ( 64KB) Pila Hasta el resto de memoria Montculo

Modelo gigante (huge) Uno o varios segmentos cada uno ( 64KB) Cdigo ... Uno o varios segmentos cada uno ( 64KB) Variables estticas ... Un segmento ( 64KB) Pila Hasta el resto de memoria Montculo

Figura 16-5. Continuacin.

Funciones Estndar para el Manejo del Montculo


La biblioteca estndar de C, provee de cuatro funciones para el manejo del montculo. Las funciones calloc() y malloc() nos permiten pedirle al sistema operativo, bloques de la memoria. La funcin realloc() nos permite modificar el tamao de un bloque previamente asignado y la funcin free() nos permite liberar dichos bloques. Los prototipos de estas funciones se encuentran en los archivos de encabezados stdlib.h o alloc.h. La tabla 16-1 describe dichas funciones. Tabla 16-1. Funciones de la biblioteca estndar para el manejo del montculo.
void *calloc(size_t nElem, size_t tamao); Funcin: Regresa: Comentarios: Asigna un bloque de memoria de tamao nElem x tamao, del montculo, en forma dinmica. El bloque es inicializado a ceros. La funcin regresa un apuntador al bloque asignado. Si no hay memoria suficiente para el nuevo bloque o nbloques o tamao son cero, calloc() regresa NULL. El tamao mximo de un bloque no puede exceder los 64KB. size_t es un alias de unsigned y est definido en stdlib.h y alloc.h.

void free(void *bloque); Funcin: Libera un bloque de memoria asignado por las funciones calloc(), malloc() o realloc(). bloque es la direccin del bloque a liberar.

ITSON

Manuel Domitsu Kono

410

Estructuras de Datos en C

Tabla 16-1. Funciones de la biblioteca estndar para el manejo del montculo, Continuacin.
void *malloc(size_t tamao); Funcin: Regresa: Comentarios: Asigna un bloque de memoria de tamao tamao, del montculo, en forma dinmica. El bloque no es inicializado. La funcin regresa un apuntador al bloque asignado. Si no hay memoria suficiente para el nuevo bloque o tamao es cero, malloc() regresa NULL. El tamao mximo de un bloque no puede exceder los 64KB.

void *realloc(void * obloque, size_t ntamao); Funcin: Ajusta el tamao de un bloque de memoria en el montculo, expandindolo o encogindolo. obloque es la direccin de un bloque previamente asignado por las funciones calloc(), malloc() o realloc() y ntamao es el nuevo tamao del bloque. La funcin regresa un apuntador al bloque ajustado, el cual puede ser diferente al bloque original ya que de ser necesario, la funcin copia los el contenido del bloque a una nueva posicin. Si el bloque no puede ser ajustado o ntamao es cero, la funcin regresa NULL. Si obloque es un apuntador nulo, realloc() trabaja igual que malloc(). El nuevo tamao del bloque no puede exceder los 64KB.

Regresa:

Comentarios:

Adicionalmente a las cuatro funciones de la biblioteca estndar, Turbo C nos proporciona otro grupo de funciones: farcalloc() y farmalloc() para pedir bloques de la memoria, farrealloc() para ajustar el tamao de un bloque previamente asignado y farfree() para liberar bloques. Por ltimo podemos determinar la cantidad de memoria disponible en el montculo usando las funciones coreleft() y farcoreleft(). Los prototipos de esas funciones se encuentran en el archivo de encabezados alloc.h. La tabla 16-2 describe dichas funciones.

Tabla 16-2. Funciones adicionales de Turbo C para el manejo del montculo.


unsigned coreleft(void); unsigned long coreleft(void); Funcin: Comentarios: En los modelos pequeito, pequeo y mediano. En los modelos compacto, grande y gigante.

Regresa una medida de la memoria disponible en el montculo. El valor que regresa la funcin coreleft() depende del modelo de memoria empleado.

void far *farcalloc(unsigned long nElem, unsigned long tamao); Funcin: Regresa: Comentarios: Asigna un bloque de memoria de tamao nElem x tamao, del montculo lejano, en forma dinmica. La funcin regresa un apuntador al bloque asignado. Si no hay memoria suficiente para el nuevo bloque o nbloques o tamao son cero, farcalloc() regresa NULL. Un bloque puede exceder los 64KB. Se utilizan apuntadores lejanos para accesar al bloque asignado.

unsigned long farcoreleft(void); Funcin: Regresa una medida de la memoria disponible en el montculo lejano.

ITSON

Manuel Domitsu Kono

Captulo 16

Estructuras de Datos en C

411

Tabla 16-2. Funciones adicionales de Turbo C para el manejo del montculo. Continuacin.
void farfree(void far *bloque); Funcin: Comentarios: Libera un bloque de memoria asignado del montculo lejano. bloque es la direccin del bloque a liberar. En los modelos pequeo y mediano, los bloques asignados por farmalloc() no pueden liberarse con free() y los bloque asignados con malloc() no pueden liberarse con farfree().

void far *farmalloc(unsigned long tamao); Funcin: Regresa: Comentarios: Asigna un bloque de memoria de tamao tamao, del montculo lejano, en forma dinmica. La funcin regresa un apuntador al bloque asignado. Si no hay memoria suficiente para el nuevo bloque o tamao es cero, farmalloc() regresa NULL. Un bloque puede exceder los 64KB. Se utilizan apuntadores lejanos para accesar al bloque asignado.

void far *farrealloc(void far *obloque, unsigned long ntamao); Funcin: Regresa: Comentarios: Ajusta el tamao de un bloque de memoria en el montculo lejano. obloque es la direccin del bloque a ajustar y ntamao es el nuevo tamao del bloque. La funcin regresa un apuntador al bloque ajustado, el cual puede ser diferente al bloque original. Si el bloque no puede ser ajustado la funcin regresa NULL. Un bloque puede exceder los 64KB. Se utilizan apuntadores lejanos para accesar al bloque asignado.

Para ilustrar el uso de las funciones para el manejo del montculo, considere el siguiente programa que lee una serie de nmeros reales y construye un histograma de frecuencias de esos datos.

DEMOHEAP.C
/************************************************************* * DEMOHEAP.C * * Este programa lee una serie de nmeros reales y construye * un histograma de frecuencias de esos datos. Los arreglos * que contienen los datos y el histograma se crean en forma * dinmica. *************************************************************/ #include <stdio.h> #include <stdlib.h> typedef struct { float limInf; int frec; } HISTO;

/* Lmite inferior del intervalo */ /* No. de datos que caen en el intervalo */

float *leeDatos(int nDatos); HISTO *construyeHistograma(float *pDatos, int nDatos, int nIntervalos); void despliegaHistograma(HISTO *pHisto, int nIntervalos); int main(void) { int nDatos, nIntervalos; HISTO *pHisto; float *pDatos;

ITSON

Manuel Domitsu Kono

412

Estructuras de Datos en C

do { printf("\nNmero de datos a leer: "); scanf("%d", &nDatos); } while(nDatos <= 0); /* Si no se pudieron almacenar los datos ledos */ if(!(pDatos = leeDatos(nDatos))) return 1; printf("\nNmero de intervalos en el histograma: "); scanf("%d", &nIntervalos); /* Si no se pudo formar el histograma */ if(!(pHisto = construyeHistograma(pDatos, nDatos, nIntervalos))) return 2; despliegaHistograma(pHisto, nIntervalos); return 0; } /************************************************************* *float *leeDatos(int nDatos) * * Esta funcin lee nDatos nmeros reales y los almacena en un * arreglo creado en el montculo. Si hay xito la funcin * regresa un apuntador al arreglo, en caso contrario la * funcin regresa NULL. *************************************************************/ float *leeDatos(int nDatos) { int i; float dato, *pDatos; /* Solicita memoria para almacenar los datos */ if((pDatos = malloc(nDatos*sizeof(float))) == NULL) { puts("\nMemoria insuficiente"); return NULL; } /* Lee los datos */ for(i = 0; i < nDatos; i++) { printf("\nDato %d: ", i+1); scanf("%f", &dato); *(pDatos + i) = dato; } return pDatos; } /************************************************************* * HISTO *construyeHistograma(float *pDatos, int nDatos, * int nIntervalos) * * Esta funcin construye un histograma a partir de los datos * almacenados en el arreglo dado por pDatos. El nmero de * datos est dado por nDatos y el nmero de intervalos en el * histograma por nIntervalos. El histograma se almacena en * un arreglo creado en el montculo. Si hay xito la funcin * regresa un apuntador al arreglo, en caso contrario la * funcin regresa NULL. *************************************************************/ HISTO *construyeHistograma(float *pDatos, int nDatos, int nIntervalos) { int i, j; float menor, mayor, inc;

ITSON

Manuel Domitsu Kono

Captulo 16

Estructuras de Datos en C

413

HISTO *pHisto; /* Solicita memoria para el histograma */ if((pHisto = malloc(nIntervalos*sizeof(HISTO))) == NULL) { puts("\nMemoria insuficiente"); return NULL; } /* Encuentra el menor y mayor de los datos */ menor = mayor = *pDatos; for(i = 0; i < nDatos; i++) { if(menor > *(pDatos + i)) menor = *(pDatos + i); if(mayor < *(pDatos + i)) mayor = *(pDatos + i); } /* Encuentra el ancho de cada intervalo del histograma */ inc = (mayor - menor)/nIntervalos; /* Calcula los lmites de los intervalos e inicializa a ceros el contador de nmero de datos que caen en el intervalo */ for(j = 0; j <= nIntervalos; j++) { pHisto[j].limInf = menor + j * inc; pHisto[j].frec = 0; } /* Hace el lmite del ltimo intervalo un poco ms grande para que acepte el valor mayor */ pHisto[nIntervalos].limInf *= 1.001; /* Construye el histograma */ for(i = 0; i < nDatos; i++) { for(j = 1; j <= nIntervalos; j++) if(*(pDatos + i) < pHisto[j].limInf) { pHisto[j-1].frec++; break; } } /* regresa el histograma */ return pHisto; } /************************************************************** * void despliegaHistograma(HISTO *pHisto, int nIntervalos) * * Esta funcin despliega una tabla con el histograma de * frecuencias dado por pHisto. El nmero de intervalos en el * histograma esta dado por nIntervalos. **************************************************************/ void despliegaHistograma(HISTO *pHisto, int nIntervalos) { int j; printf("\n Histogramas de frecuencias\n"); printf("\n Intervalo Frecuencia"); printf("\n-------------------------------------"); for(j = 0; j < nIntervalos; j++) printf("\n[%10.4f, %10.4f)%9d", pHisto[j].limInf, pHisto[j+1].limInf, pHisto[j].frec); }

ITSON

Manuel Domitsu Kono

414

Estructuras de Datos en C

Apuntadores a Funciones
Aunque una funcin no es una variable, el cdigo de la funcin est almacenado en localidades de memoria. Para ejecutar las instrucciones de la funcin, el microprocesador direcciona y lee la localidad de memoria que contiene cada instruccin. En C pueden definirse apuntadores a funciones. Un apuntador a una funcin es una localidad de memoria que contiene la direccin de la primera instruccin de una funcin y por lo tanto es equivalente a una llamada a funcin. La sintaxis de la declaracin de un apuntador a una funcin es tipo (* nomApuntFunc)([lista de parmetros]); donde nomApuntFunc es un apuntador a una funcin de tipo tipo y que recibe como parmetros lista de parmetros. Note los parntesis alrededor de * nomApuntFunc. La razn de estos parntesis es la siguiente: En una declaracin de una funcin, tipo nomFunc([lista de parmetros]); los parntesis que encierran la lista de parmetros es un operador que le informa al compilador que el identificador nomFunc es el nombre de una funcin. Este operador tiene una mayor precedencia que el operador de indireccin, *, y por lo tanto la declaracin tipo * nomApuntFunc([lista de parmetros]); es interpretada por el compilador como una funcin, por los parntesis, que regresa un apuntador a tipo, por el operador de indireccin. En cambio, en la declaracin tipo (* nomApuntFunc )([lista de parmetros]); los parntesis alrededor de (* nomApuntFunc) hacen que el compilador interprete a nomApuntFunc como un apuntador, ya que lo evalan primero y luego al encontrar a ([lista de parmetros]) determinan que el apuntador es un apuntador a una funcin. Por ejemplo las declaraciones
void (* pBorraPantalla)(void); double (* pSqrt)(double x);

declara a pBorraPantalla como un apuntador a una funcin de tipo void y que no tiene parmetros y a pSqrt como un apuntador a una funcin de tipo doble que recibe como parmetro un doble. A fin de emplear los apuntadores declarados anteriormente debemos asignarles la direccin de una funcin que corresponda en tipo y nmero y tipos de parmetros al apuntador a funcin. Dichas funciones pueden formar parte de la biblioteca estndar o ser funciones definidas por el usuario. La sintaxis de dicha asignacin es

ITSON

Manuel Domitsu Kono

Captulo 16

Estructuras de Datos en C

415

nomApuntFunc = nomFunc; Podemos ver que C interpreta el nombre de la funcin por s solo como la direccin de la funcin. De modo que
pBorraPantalla = clrscr; pSqrt = sqrt;

inicializa pBorraPantalla y pSqrt a las direcciones donde empiezan los cdigos de las funciones clrscr() e sqrt(), respectivamente. Una vez que un apuntador a funcin tiene asignado la direccin de una funcin, es decir, apunta a una funcin, podemos utilizar ste como una forma alterna de la llamada a la funcin. La sintaxis de la llamada a la funcin usando el apuntador a la funcin es nomApuntFunc ([lista de parmetros]); Podemos observar que la llamada a la funcin usando el apuntador a la funcin es igual a una llamada normal a la funcin, reemplazando el nombre de la funcin por el nombre del apuntador. Como ejemplos de llamadas a las funciones de los ejemplos anteriores, tenemos
pBorraPantalla(); pSqrt(2.0);

Un ejemplo completo se muestra a continuacin:

#include <stdio.h> #include <conio.h> #include <math.h> /* Declara dos apuntadores a funciones */ void(* pBorraPantalla)(void); double (* pSqrt)(double x); int main(void) { double x = 2.0, y; /* Inicializa los apuntadores a funciones */ pBorraPantalla = clrscr; pSqrt = sqrt; /* Utiliza los apuntadores para llamar a las funciones */ pBorraPantalla(); y = pSqrt(x); printf("\nLa raiz de %lf es %lf", x, y); return 0; }

Los apuntadores a funciones pueden almacenarse en arreglos, pasarse a funciones como parmetros y ser regresados por funciones. Como un ejemplo de una funcin que recibe como parmetro un apuntador a una funcin, considere una funcin que implemente el algoritmo de quicksort para ordenar

ITSON

Manuel Domitsu Kono

416

Estructuras de Datos en C

un arreglo de datos generalizado, es decir, un arreglo en el que los datos puedan ser de cualquier tipo. Para lograr esto, debemos modificar la funcin quicksort() vista en el Captulo 15 y que nos permite ordenar enteros. Para permitir que la funcin opere con cualquier tipo de datos debemos cambiar la declaracin del parmetro que representa la direccin del arreglo a ordenar de int a void, para eliminar la informacin sobre el tamao de los datos. El tamao de los datos deber proveerse mediante otro parmetro. Por ltimo dado que diferentes tipos de datos se comparan de diferentes formas, debemos proporcionarle a la funcin quicksort() de una funcin que compare dos datos del tipo de los datos a ordenar. Esto lo hacemos pasndole a la funcin quicksort un apuntador a la funcin a comparar. Una implementacin de la funcin quicksort () generalizada se muestra en el mdulo QS_BS que se lista a continuacin.

QS_BS.H
#ifndef QS_BS_H #define QS_BS_H void quicksort(void *base, int principio, int fin, int tamElem, int (* fcmp)(const void *pdato1, const void *pdato2)); #endif

QS_BS.C
#include <mem.h> #include <stdlib.h> #include "qs_bs.h" /************************************************************* * void quicksort(void *base, int principio, int fin, * int tamElem, int (* fcmp)(const void *pdato1, * const void *pdato2)) * * Esta funcin recursiva implementa el algoritmo quicksort * para ordenar el arreglo genrico, cuya direccin base est * dada por base. principio y fin corresponden a los ndices * del primer y ltimo elemento del arreglo. tamElem es el * tamao de cada uno de los elementos del arreglo en bytes y * fcmp es un apuntador a la funcin utilizada para comparar * los elementos del arreglo. La funcin para comparar es * suministrada por el usuario y es de tipo entero y recibe * como parmetros dos apuntadores a los elementos a comparar: * pdato1 y pdato2. La funcin debe regresar: * * 0: si el dato1 == dato2 * > 0 si el dato 1 > dato 2 * < 0 si el dato 1 < dato 2 *************************************************************/ void quicksort(void *base, int principio, int fin, int tamElem, int (* fcmp)(const void *pdato1, const void *pdato2)) { int pivote, i; /* Apuntador a bloque de memoria usado para intercambiar los elementos al estar ordenando */ void *temp; /* Si hay ms de un elemento en el arreglo, encuentra el elemento pivote del arreglo */ if(principio < fin) {

ITSON

Manuel Domitsu Kono

Captulo 16

Estructuras de Datos en C

417

/* Reserva espacio para un bloque de memoria usado para intercambiar los elementos al estar ordenando */ if((temp = malloc(tamElem)) == NULL) return; /* Selecciona el elemento pivote en forma aleatoria. Para ello se utiliza un generador de nmeros pseudoaleatorios que genera el valor a seleccionar como pivote */ pivote = principio + rand() % (fin - principio); /* Particiona el arreglo en dos arreglos uno con los elementos de principio a pivote-1 y otro con los elementos de pivote a fin */ /* intercambia(base[principio], base[pivote]) */ memcpy(temp, (char *)base+principio*tamElem, tamElem); memcpy((char *)base+principio*tamElem, (char *)base+pivote*tamElem), tamElem); memcpy((char *)base+pivote*tamElem), temp, tamElem); pivote = principio; for(i = principio+1; i <= fin; i++) { /* Utiliza la funcin proporcionada por el usuario para comparar los elementos */ if(fcmp((char *)base + i*tamElem, (char *)base + principio*tamElem) < 0) { pivote++; /* intercambia(base[pivote], base[i]) */ memcpy(temp, (char *)base+pivote*tamElem, tamElem); memcpy((char *)base+pivote*tamElem, (char *)base+i*tamElem), tamElem); memcpy((char *)base+i*tamElem), temp, tamElem); } } /* intercambia(base[pivote], base[principio]) */ memcpy(temp, (char *)base+pivote*tamElem, tamElem); memcpy((char *)base+pivote*tamElem, (char *)base+principio*tamElem), tamElem); memcpy((char *)base+principio*tamElem), temp, tamElem); free(temp); /* Llama recursivamente a la funcin quicksort con cada uno de los subarreglos */ quicksort(base, principio, pivote-1, tamElem, fcmp); quicksort(base, pivote+1, fin, tamElem, fcmp); } }

Los cambios que se le hicieron a esta versin de la funcin quicksort () con respecto a la vista en el Captulo 15: Recursividad son: Para intercambiar el contenido de dos datos del arreglo, se utiliza la funcin memcpy() para mover un bloque de bytes de tamao tamElem de una localidad a otra y la comparacin de los datos que se hace a travs de la funcin fcmp() proporcionada por el usuario. Para ejemplificar el uso de la funcin quicksort() generalizada consideremos el siguiente programa que despliega el directorio de un disco, es una versin simplificada del comando dir del sistema operativo, que slo permite desplegar el directorio sin ordenar, ordenado por el nombre de los archivos y ordenado por el nombre de los archivos en forma inversa, es decir de la Z a la A. Este programa puede descomponerse en cuatro pasos que son:

ITSON

Manuel Domitsu Kono

418

Estructuras de Datos en C

Obtener el modo de ordenamiento Obtener el directorio Ordenar el directorio Desplegar el directorio Cada uno de esos pasos se implementa como una funcin. Estas funciones son llamadas por la funcin main().

DIRSORT.C
/************************************************************* * DIRSORT.C * * Este programa despliega una lista de archivos de un * directorio. La lista puede ordenarse alfabticamente por el * nombre ya sea en forma directa o inversa. Para ordenar al * directorio utiliza una versin generalizada del algoritmo * QUICKSORT. Este programa recibe en la lnea de comando el * (los) nombre(s) del (los) archivos a listar (puede incluir * el nombre de la unidad de disco y la ruta. Adems pueden * utilizarse los caracteres comodn ? y *. * * Para ejecutar teclear en la lnea de comando: * * dirsort [<directorio>][\<nomArch>] [/o[orden]] * * La opcin /o[orden]puede ser * * /o produce un ordenamiento directo por el nombre * /on produce un ordenamiento directo por el nombre * /o-n produce un ordenamiento inverso por el nombre *************************************************************/ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <dir.h> #include <dos.h> #include "qs_bs.h" /* Estructura regresada por las funciones findfirst() y findnext()y que contienen el directorio de un archivo */ typedef struct ffblk FFBLK; /* Enumeracin que define los tipos typedef enum { NO_VAL = -1, /* Orden invlido NO_ORD, /* No ordenado */ DIRNOM, /* Directo por el INVNOM, /* Inverso por el } ORDEN; de ordenamiento */ */ nombre */ nombre */

ORDEN obtenOrden(char *s); int obtenDir(char *ruta, FFBLK **pdir); void despliegaDir(FFBLK *dir, int n); int dircmp_dn(const void *pdato1, const void *pdato2); int dircmp_in(const void *pdato1, const void *pdato2); int main(int argc, char *argv[]) { FFBLK *dir; /* Apuntador al bloque donde se va a almacenar el

ITSON

Manuel Domitsu Kono

Captulo 16

Estructuras de Datos en C

419

int n; ORDEN orden = NO_ORD;

directorio. Se pide memoria en forma dinmica */ /* Nmero de archivos en el directorio */ /* Tipo de ordenamiento */

/* Si se reciben demasiados argumentos en la lnea de comando, aborta programa */ if(argc > 3) { puts("\nUso: dirsort [<directorio><nomArch>] [/o[orden]]"); puts("\n\nLa opcion /o[orden]puede ser:"); puts("\n /o ordenado directo por el nombre"); puts("\n /on ordenado directo por el nombre"); puts("\n /o-n ordenado inverso por el nombre"); return 1; } /* Obtiene el directorio y el tipo de ordenamiento */ /* Si no hay argumentos en la lnea de comando */ if(argc == 1) n = obtenDir("*.*", &dir); /* Si slo hay un argumento en la lnea de comando */ else if(argc == 2) { /* Si el argumento es la opcin de ordenamiento */ if(argv[1][0] == '/') { n = obtenDir("*.*", &dir); orden = obtenOrden(argv[1]+1); } /* Si el argumento es el nombre de archivo */ else n = obtenDir(argv[1], &dir); } /* Si se provee nombre de archivo y opcin de ordenamiento */ else { n = obtenDir(argv[1], &dir); if(argv[2][0] == '/') orden = obtenOrden(argv[2]+1); else { puts("\nParametro incorrecto"); return 1; } } if(!n) { puts("\nNo se encontro archivos o directorio"); return 0; } if(n == -1) { puts("\nMemoria insuficiente"); return 1; } if(orden == NO_VAL) { puts("\nOpcion invalida"); return 2; } /* Ordena el directorio */ switch(orden)

ITSON

Manuel Domitsu Kono

420

Estructuras de Datos en C

{ case DIRNOM: quicksort(dir, 0, n-1, sizeof(FFBLK), dircmp_dn); break; case INVNOM: quicksort(dir, 0, n-1, sizeof(FFBLK), dircmp_in); break; } /* Muestra el directorio */ despliegaDir(dir, n); /*Libera la memoria empleada para almacenar el directorio */ free(dir); return 0; } /************************************************************* * ORDEN obtenOrden(char *s) * * Esta funcin regresa el tipo de ordenamiento deseado. El * tipo de ordenamiento viene en la cadena s. *************************************************************/ ORDEN obtenOrden(char *s) { if(!strcmp(s, "o")) return DIRNOM; if(!strcmp(s, "on")) return DIRNOM; if(!strcmp(s, "o-n")) return INVNOM; return NO_VAL; } int obtenDir(char *nomDir, FFBLK **pdir) /************************************************************* * int obtenDir(char *nomDir, FFBLK **pdir) * * Esta funcin obtiene la lista de archivos del directorio * dado por nomDir y los almacena en el arreglo pdir. nomDir * es una cadena que puede contener el nombre de la unidad de * disco, ruta y nombre del (los) archivo(s) de los que se * quiere el directorio. Pueden usarse los caracteres comodn * ? y *. La funcin regresa el nmero de archivos en el * directorio. *************************************************************/ int obtenDir(char *nomDir, FFBLK **pdir) { int i, fin; FFBLK f; /* Determina el nmero de archivos en el directorio */ fin = findfirst(nomDir, &f, FA_DIREC); for(i = 0; !fin; i++) fin = findnext(&f); /* Si no hay archivos, termina */ if(!i) return(i); /* Pide un bloque de memoria para almacenar el directorio */ *pdir = (FFBLK *)malloc(i*sizeof(FFBLK)); if(*pdir == NULL) return(-1); /* Lee el directorio y lo guarda en pdir */ fin = findfirst(nomDir, &f, FA_DIREC); for(i = 0; !fin; i++) { *(*pdir + i) = f; fin = findnext(&f); } return(i); }

ITSON

Manuel Domitsu Kono

Captulo 16

Estructuras de Datos en C

421

La funcin obtenOrden() determina el tipo de ordenamiento deseado a partir de la cadena de la lnea de comando que empieza con el carcter '/' y que se le pasa a la funcin en el parmetro s. Para obtener el directorio de un disco se utilizan un par de funciones de la biblioteca de funciones de Turbo C: findfirst () y findnext() cuyos prototipos se encuentran en el archivo de encabezados dir.h. Las sintaxis de esas funciones son: int findfirst(const char *pathname, struct ffblk *ffblk , int attrib); int findnext(struct ffblk * ffblk ); La funcin findfirst() inicia la bsqueda del directorio de disco. pathname es una cadena que contiene el nombre de la unidad de disco, la ruta y el nombre del archivo a buscar con el siguiente formato: [unidad:][ruta\]nomArch El nombre del archivo puede contener caracteres comodn ? y *. Si se encuentra un archivo que corresponda a pathname, la funcin llena la estructura ffblk con la informacin del directorio para ese archivo. Si se emplean caracteres comodn la funcin slo regresa el primer nombre de archivo. La definicin de esta estructura se encuentra en el archivo dir.h y se describe ms adelante. attrib es el byte de atributo de archivo de DOS y se utiliza para determinar qu archivos son tomados en cuenta en la bsqueda. Los atributos de archivo estn definidos como macros en el archivo de encabezados dos.h y se muestran en la tabla 16-3. Tabla 16-3. Atributos de archivo
Valor 0x00 0x01 0x02 0x04 0x08 0x10 0x20 FA_RDONLY FA_HIDDEN FA_SYSTEM FA_LABEL FA_DIREC FA_ARCH Macro Descripcin Archivos ordinarios Atributo de solo lectura Archivo oculto Archivo de sistema Etiqueta de Volumen Directorio Archivo

Si attrib vale 0, slo se encuentran archivos ordinarios (aquellos que no estn marcados como de slo lectura, ocultos, de sistema, etiquetas de volumen o directorios). Si se especifica el atributo de etiqueta de volumen, slo se regresar etiquetas de volumen (si estn presentes). Cualquier otro atributo o combinacin de atributos (oculto, de sistema o directorio) har que se encuentren los archivos con esos atributos adems de los archivos ordinarios. Por ejemplo si attrib toma el valor de FA_DIREC se desplegarn tanto los nombres de los subdirectorios como los nombres de los archivos ordinarios que concuerden con pathname. En cambio si attrib toma el valor de FA_HIDDEN | FA_SYSTEM se

ITSON

Manuel Domitsu Kono

422

Estructuras de Datos en C

desplegarn tanto los nombres de los archivos ordinarios como los ocultos y del sistema que concuerden con pathname. Suponiendo que pathname contiene caracteres comodines y que la llamada a la funcin findfirst() tuvo xito, los siguientes archivos que concuerdan con pathname pueden encontrarse llamando repetidamente a la funcin findnext(). ffblk debe apuntar a la misma estructura con la que se llam a findfirst () ya que esta estructura contiene la informacin necesaria para continuar la bsqueda. findfirst () y findnext() regresan 0 si tienen xito en encontrar un archivo que concuerde con pathname. Cuando no se puedan encontrar ms archivos o hay un error en el nombre del archivo las funciones regresan -1 y la variable global errno toma el valor de: ENOENT (o ENOFILE) ENMFILE No existe archivo o directorio No hay ms archivos

La estructura tipo ffblk en la que las funciones findfirst() y findnext() colocan la informacin sobre el directorio de un archivo est definida en el directorio de encabezados dir.h de la siguiente forma:
struct ffblk { char char unsigned unsigned long char };

ff_reserved[21]; ff_attrib; ff_ftime; ff_fdate; ff_fsize; ff_name[13];

/* /* /* /* /*

Atributo de archivo */ Hora codificada */ Fecha codificada */ Tamao en bytes */ Nombre */

La tablas 16-4 y 16-5 muestran cmo estn codificadas la fecha y hora de creacin o ltima modificacin de un archivo: Tabla 16-4. Codificacin de la fecha de un archivo.
Bits 0- 4 5- 8 9 - 15 Da (0 - 31) Mes (0 - 12) Ao, relativo a 1980. Un valor de 0 significa 1980, 1 representa 1981, etc. Contenido

Tabla 16-5. Codificacin de la hora de un archivo.


Bits 0- 4 5 - 10 11 - 15 Contenido Segundos, en incrementos de 2 segundos (0 - 29) Minutos (0 - 12) Horas (0 - 23)

ITSON

Manuel Domitsu Kono

Captulo 16

Estructuras de Datos en C

423

DIRSORT.C. Continuacin.
/************************************************************* * void despliegaDir(FFBLK *dir, int n) * * Esta funcin despliega la lista de archivos del directorio * en el formato: * * nombre.ext <DIR> dd-mm-aa hh:mm[a|p] * * en el caso de directorios y * * nombre.ext tamao dd-mm-aa hh:mm[a|p] * * en el caso de archivos. *************************************************************/ void despliegaDir(FFBLK *dir, int n) { int i; for(i = 0; i < n; i++) { /* Si es un directorio */ if(dir[i].ff_attrib == FA_DIREC) printf("\n%-12s <DIR> %02u-%02u-%02u%02u:%02u%c", dir[i].ff_name, dir[i].ff_fdate & 0x1F, (dir[i].ff_fdate >> 5) & 0xF, ((dir[i].ff_fdate >> 9) & 0x7F) + 80, ((dir[i].ff_ftime >> 11) & 0x1F) % 12, ((dir[i].ff_ftime >> 5) & 0x3F), (((dir[i].ff_ftime >> 11) & 0x1F)/12? 'p': 'a')); /* Si es un archivo */ else printf("\n%-12s%14ld %02u-%02u-%02u %02u:%02u%c", dir[i].ff_name, dir[i].ff_fsize, dir[i].ff_fdate & 0x1F, (dir[i].ff_fdate >> 5) & 0xF, ((dir[i].ff_fdate >> 9) & 0x7F) + 80, ((dir[i].ff_ftime >> 11) & 0x1F) % 12, ((dir[i].ff_ftime >> 5) & 0x3F), (((dir[i].ff_ftime >> 11) & 0x1F)/12? 'p': 'a')); } printf("\n"); } /************************************************************* * int dircmp_dn(const void *pdato1, const void *pdato2) * * Esta funcin compara los nombres de dos archivos. Los * nombres de los archivos se encuentran en las estructuras * apuntadas por pdato1 y pdato2. La funcin regresa: * * 0 si nomArch1 == nomArch2 * (+) si nomArch1 > nomArch2 * (-) si nomArch1 < nomArch2 *************************************************************/ int dircmp_dn(const void *pdato1, const void *pdato2) { FFBLK *pd1 = (FFBLK *)pdato1, *pd2 = (FFBLK *)pdato2; return strcmp(pd1->ff_name, pd2->ff_name); }

ITSON

Manuel Domitsu Kono

424

Estructuras de Datos en C

/************************************************************* * int dircmp_in(const void *pdato1, const void *pdato2) * * Esta funcin compara los nombres de dos archivos. Los * nombres de los archivos se encuentran en las estructuras * apuntadas por pdato1 y pdato2. La funcin regresa: * * 0 si nomArch1 == nomArch2 * (-) si nomArch1 > nomArch2 * (+) si nomArch1 < nomArch2 *************************************************************/ int dircmp_in(const void *pdato1, const void *pdato2) { FFBLK *pd1 = (FFBLK *)pdato1, *pd2 = (FFBLK *)pdato2; return strcmp(pd2->ff_name, pd1->ff_name); }

Note que el formato para desplegar los datos de un directorio difiere del formato para desplegar los datos de un archivo ordinario. Las funciones dircmp_dn() y dircmp_in() son las que se le pasan a la funcin quicksort() como parmetro para ordenar el directorio en forma directa e inversa, respectivamente. Note que las funciones reciben como parmetros apuntadores de tipo void para que coincidan con la declaracin de la funcin quicksort(). Ya dentro de la funcin esos apuntadores se convierten en apuntadores a estructuras de tipo ffblk para poder accesar al contenido de esas estructuras. Por ltimo se utiliza la funcin strcmp() para comparar los nombre de archivo.

Ejercicio Sobre Apuntadores a Funciones


Modifique la funcin busquedaBinaria() desarrollada en el Captulo 15: Recursividad para que sea una funcin generalizada, es decir que trabaje con cualquier tipo de datos.

Listas Ligadas
Las listas son colecciones ordenadas de objetos, el trmino ordenado aqu no significa ordenado de mayor a menor o de menor a mayor, sino que cada miembro a excepcin del primero tiene un predecesor y cada miembro a excepcin del ltimo tienen un sucesor. Adems si agregamos un objeto en determinada posicin, el objeto que estaba en esa posicin y todos los que le siguen se desplazan una posicin. Las listas tienen las siguientes propiedades: Una lista puede tener cero o ms elementos. Podemos agregar un elemento a la lista, en cualquier posicin. Podemos borrar a cualquier elemento de la lista.

ITSON

Manuel Domitsu Kono

Captulo 16

Estructuras de Datos en C

425

Podemos accesar a cualquier elemento de la lista. Podemos recorrer toda la lista visitando a cada elemento de ella.

Operaciones Primitivas con las Listas


Las operaciones primitivas definidas para una lista son: Inicializar: Vaca: Insertar: Extraer: Visitar: Buscar: Vaca la lista. Regresa verdadero si la lista est vaca. Inserta un elemento en la lista en determinada posicin. Extrae el elemento de la lista que se encuentra en determinada posicin. Recorre la lista efectuando una operacin sobre cada elemento de la lista. Busca en la lista el primer elemento que cumpla con cierto criterio.

Implementacin de una Lista en C


La forma ms sencilla de implementar una lista es mediante un arreglo. S embargo, esto presenta in varios inconvenientes: Primero, los arreglos tienen un tamao fijo y en muchos casos no sabemos de antemano el tamao que tendr una lista. Segundo, al extraer un elemento de la lista es necesario recorrer los dems elementos para ocupar el hueco dejado por el elemento que se extrajo, este proceso puede ser costoso en trminos de tiempo de ejecucin si la lista tiene muchos elementos y/o se extraen muchos elementos de la lista. Lo que deseamos es un mecanismo que nos permita obtener un bloque de memoria para un nuevo elemento slo cuando sea necesario y regresar ese bloque de memoria cuando ya no lo necesitemos. Esto se logra con una lista ligada. Una lista ligada est compuesta de un conjunto de nodos, donde cada nodo tiene dos campos. El primero es el campo de informacin que contiene el o los datos reales y el segundo es el campo siguiente que es un apuntador al siguiente nodo de la lista. A diferencia de un arreglo, los nodos de una lista ligada no estn necesariamente almacenados en localidades de memoria contigua por lo que para accesar a un determinado nodo de la lista debemos empezar al inicio de la lista y usar la informacin almacenada en los nodos de la lista para localizar el siguiente nodo. este proceso se repite hasta lo calizar el nodo deseado. Una lista ligada puede definirse formalmente como: Una lista ligada es un apuntador a un nodo. Un nodo de una lista ligada tiene dos campos: 1. El campo de informacin, que es el dato del elemento. 2. El campo siguiente , el cual es una lista ligada.

ITSON

Manuel Domitsu Kono

426

Estructuras de Datos en C

La definicin de una lista ligada es una definicin recursiva. Una lista ligada es un apuntador a un nodo el cual a su vez apunta a una lista ligada. El caso base esta definicin es una lista ligada nula. Una lista ligada nula se representa como un apuntador nulo como se muestra en la figura 16-6a. En la figura 166b se muestra una lista ligada con tres nodos. Note que el campo siguiente del ltimo elemento de una lista ligada es un apuntador nulo. Esto nos permite identificar al ltimo elemento de la lista ligada.

Figura 16-6 A continuacin implementaremos una lista ligada en C en la que el campo de informacin de cada nodo es un entero. Posteriormente se generalizar para que contenga cualquier tipo de dato. Primero se define el tipo de dato que contendr a un nodo.
struct nodo { int info; struct nodo *pSig; };

/* Campo de informacin */ /* Campo siguiente */

En la definicin anterior podramos pensar que hay un error pues uno de los miembros de la estructura nodo, pSig hace referencia a la estructura que no acabamos de definir y por lo tanto no existe informacin sobre el tipo nodo. Sin embargo hay que notar que pSig no representa a una estructura tipo nodo sino un apuntador a la estructura y esto si se permite. Para simplificar las declaraciones de las variables y apuntadores a la estructura nodo, definiremos el siguiente alias:
typedef struct nodo NODO; /* Alias de la estructura nodo */

Adems para declarar el apuntador a la lista creamos el siguiente alias:


typedef NODO *LISTA; /* Alias del apuntador a NODO */

Todas las funciones para el manejo de listas ligadas reciben como parmetros apuntadores del tipo LISTA o apuntadores a NODO. La operacin para inicializar la lista est implementada por la funcin inicializarLista() cuyo cdigo es

ITSON

Manuel Domitsu Kono

Captulo 16

Estructuras de Datos en C

427

void inicializarLista(LISTA *pLista) { LISTA pTemp; /* Mientras haya nodos en la lista */ while(*pLista) { pTemp = *pLista; *pLista = (*pLista)->pSig; destruyeNodo(pTemp); } }

La funcin inicializarLista() elimina los nodos de la lista cuya direccin esta almacenada en la localidad dada por pLista, eliminando en forma repetitiva al nodo al inicio de la lista. En cada iteracin realiza los siguientes tres pasos, mismos que se ilustran en la figura 16-7.

Figura 16-7 1. Guarda temporalmente la direccin del inicio de la lista en pTemp. Figuras 16-7a y 16-7d. 2. Mueve el apuntador de la lista para que apunte al siguiente nodo de la lista (o a NULL si se trata del ltimo nodo de la lista). Figuras 16-7b y 16-7e. 3. Elimina el nodo llamando a la funcin destruyeNodo() la cual libera el bloque de memoria ocupado por el nodo. Figuras 16-7c y 16-7f. La funcin destruyeNodo() en este ejemplo slo contiene una llamada a la funcin free() pero como veremos ms adelante al implementar una lista generalizada, puede contener ms instrucciones.
void destruyeNodo(NODO *pNodo) { free(pNodo); }

ITSON

Manuel Domitsu Kono

428

Estructuras de Datos en C

Es importante notar que la funcin inicializarLista() recibe como parmetro un apuntador a LISTA, pero como el tipo LISTA es un apuntador, el parmetro pLista constituye la direccin de una variable de tipo apuntador. La funcin requiere la direccin de la variable que contiene la direccin de la lista para cambiar su valor a NULL para indicar que la lista est vaca. La operacin vaca implementada por la funcin listaVacia() regresa un 1 si la lista dada por lista se encuentra vaca, 0 en caso contrario.
int listaVacia(LISTA lista) { return lista == NULL; }

La operacin insertar est implementada por la funcin insertarLista(). Esta funcin inserta el nodo apuntado por pNodo en la lista cuya direccin est almacenada en la localidad apuntada por pLista. pPos es un apuntador al nodo despus del cual se va insertar el nodo. Si se desea insertar el dato al inicio de la lista, pPos debe valer NULL. La funcin no verifica que pPos sea una direccin de un nodo de la lista. El usuario debe asegurarse que pPos apunte a un nodo de la lista. El cdigo de esta funcin se muestra a continuacin
void insertarLista(LISTA *pLista, NODO *pPos, NODO *pNodo) { /* Si la lista est vaca */ if(listaVacia(*pLista)) { *pLista = pNodo; return; } /* Si el nodo se va a insertar al inicio de la lista */ if(!pPos) { pNodo->pSig = *pLista; *pLista = pNodo; return; } /* Si se va a insertar despus del nodo pPos */ pNodo->pSig = pPos->pSig; pPos->pSig = pNodo; }

Al querer insertar un nodo se pueden presentar tres casos. Caso I: La lista est vaca. En este caso solo es necesario hacer que el apuntador a la lista apunte al nodo a insertar. Figura 16-8. Caso II: El nodo se desea insertar al inicio de la lista. Figura 16-9a. Aqu se requieren dos pasos. 1. Hacer que el apuntador siguiente del nodo a insertar apunte al nodo al inicio de la lista. Figura 16-9b. 2. Hacer que el apuntador al inicio de la lista apunte al nodo a insertar. Figura 16-9c.

ITSON

Manuel Domitsu Kono

Captulo 16

Estructuras de Datos en C

429

Figura 16-8

Figura 16-9 Caso III: El nodo se desea insertar despus del nodo pPos. Figura 16-10a. Aqu, tambin se requieren dos pasos. 1. Hacer que el apuntador siguiente del nodo que se va a insertar, pNodo apunte al nodo en donde se desea insertar el nodo, el nodo siguiente a pPos. Figura 16-10b. 2. Hacer que el apuntador siguiente de pPos apunte al nodo a insertar. Figura 16-10c. Note que al igual que la funcin inicializarLista(), la funcin insertarLista() recibe como primer parmetro un apuntador a LISTA, esto es necesario para permitir que la funcin modifique la direccin de inicio de la lista en los casos en que la lista est vaca o el nodo se inserte al inicio de la lista.

ITSON

Manuel Domitsu Kono

430

Estructuras de Datos en C

Figura 16-10 El nodo a insertar ya ha sido creado y la funcin insertarLista() recibe un apuntador a ese nodo, pNodo. Una implementacin de la funcin para crear un nodo est dada por creaNodo(), la cual hace una peticin dinmica de memoria llamando a la funcin malloc(), e inicializa su campo de informacin con el valor del dato y el campo siguiente con un apuntador nulo. Si hay xito la funcin regresa un apuntador al nodo creado, NULL en caso contrario.
NODO *creaNodo(int dato) { NODO *pNodo; /* Pide un bloque de memoria en forma dinmica */ if((pNodo = malloc(sizeof(NODO)))) { /* Almacena en el campo de informacin el dato */ pNodo->info = dato; /* Almacena en el campo siguiente un apuntador nulo */ pNodo->pSig = NULL; } return pNodo; }

La operacin extraer se implementa mediante la funcin extraerLista(). Esta funcin extrae un nodo de la lista si no est vaca. pLista es la direccin de la localidad en la que est almacenada la direccin del inicio de la lista. pPos es un apuntador al nodo despus del cual esta el nodo que se va extraer. Si se desea extraer el dato al inicio de la lista pPos debe valer NULL. La funcin no verifica que pPos sea una direccin de un nodo de la lista. El usuario debe asegurarse que pPos apunte a un nodo de la lista. El cdigo de esta funcin se muestra a continuacin

ITSON

Manuel Domitsu Kono

Captulo 16

Estructuras de Datos en C

431

int extraerLista(LISTA *pLista, NODO *pPos, int *pDato) { NODO *pTemp; /* Si la lista esta vaca */ if(listaVacia(*pLista)) return 0; /* Si se va a extraer el primer elemento */ if(!pPos) { pTemp = *pLista; *pLista = (*pLista)->pSig; } /* Si se va a extraer un elemento intermedio */ else { pTemp = pPos->pSig; pPos->pSig = pTemp->pSig; } /* extrae el dato */ *pDato = pTemp->info; /* Libera el nodo */ destruyeNodo(pTemp); return 1; }

Al querer extraer un nodo se pueden presentar tres casos. Caso I: La lista est vaca. En este caso la funcin slo regresa 0 indicando su fracaso. Caso II: El nodo se desea extraer est al inicio de la lista. Figura 16-11a. Aqu se requieren cuatro pasos.

Figura 16-11

ITSON

Manuel Domitsu Kono

432

Estructuras de Datos en C

1. Hacer que el apuntador pTemp apunte al nodo al inicio de la lista. Figura 16-11b. 2. Hacer que el apuntador al inicio de la lista apunte al siguiente nodo. Figura 16-11c. 3. Copia el campo de informacin del nodo a extraer a la localidad de memoria apuntada por pDato. Figura 16-11d. 4. Elimina el nodo llamando a la funcin destruyeNodo(), figura 16-11e, la cual libera el bloque de memoria ocupado por el nodo, figura 16-11f. Caso III: El nodo que se desea extraer est despus del nodo pPos. Figura 16-12a. Aqu, tambin se requieren cuatro pasos.

Figura 16-12 1. Hacer que el apuntador pTemp apunte al nodo a extraer, el siguiente del apuntado por pPos. Figura 16-12b. 2. Hacer que el apuntador siguiente de pPos apunte al nodo siguiente del nodo apuntado por pTemp. Figura 16-12c.

ITSON

Manuel Domitsu Kono

Captulo 16

Estructuras de Datos en C

433

3. Copia el campo de informacin del nodo a extraer a la localidad de memoria apuntada por pDato. Figura 16-12d. 4. Elimina el nodo apuntado por pTemp, llamando a la funcin destruyeNodo(), figura 1612e, la cual libera el bloque de memoria ocupado por el nodo, figura 16-12f. Note que al igual que las funciones inicializarLista() e insertarLista(), la funcin extraerLista() recibe como primer parmetro un apuntador a LISTA, esto es necesario para permitir que la funcin modifique la direccin de inicio de la lista en el caso en que el nodo a extraer est al inicio de la lista. La operacin visitar se implementa mediante la funcin visitarLista() la cual recorre la lista dada por lista. En cada uno de los nodos de la lista la funcin ejecuta la operacin dada por la funcin fvisita().
void visitarLista(LISTA lista, void (* fvisita)(NODO *pNodo)) { /* Mientras no se llegue al final de la lista */ while(lista) { /* Visita al nodo. Ejecuta la funcin fvisita() sobre los datos del nodo */ fvisita(lista); /* Ve al siguiente nodo */ lista = lista->pSig; } }

La funcin fvisita(), proporcionada por el usuario, es una funcin de tipo void y que recibe como parmetro un apuntador al nodo sobre el que va a actuar. Por ejemplo supongamos que deseamos desplegar para cada nodo de la lista ligada, su direccin, el valor de su campo de informacin y la direccin del siguiente nodo, podramos emplear la siguiente funcin:
void despliegaNodo(NODO *pNodo) { printf("\nDir nodo: %p dato: %d sig: %p", pNodo, pNodo->info, pNodo->pSig); }

y la llamada a la funcin visitarLista() quedara de la forma:


visitarLista(lista, despliegaNodo);

La operacin buscar se implementa mediante la funcin buscaLista() la cual busca en la lista dada por lista la primera ocurrencia de un nodo cuyo campo de informacin al compararse con llave cumple la condicin establecida por la funcin fcmp(). La funcin regresa la direccin del nodo que contiene esa primera ocurrencia, NULL en caso de no encontrar un nodo que cumpla con la condicin. pAnt apunta al nodo anterior al nodo con la primera ocurrencia. Si el nodo con la primera ocurrencia es el primer nodo de la lista, pAnt apunta a NULL. fcmp es un apuntador a la funcin utilizada para comparar la llave con los nodos de la lista. La funcin para comparar es suministrada por el usuario y es de tipo entero y recibe como parmetros: info, el campo de informacin de un nodo y la llave. La funcin fcmp() debe regresar 0 si el campo de informacin y la llave cumplen con la condicin establecida por la funcin, diferente de cero en caso contrario.

ITSON

Manuel Domitsu Kono

434

Estructuras de Datos en C

NODO *buscaLista(LISTA lista, LISTA *pAnt, int llave, int (* fcmp)(int info, int llave)) { *pAnt = NULL; /* Mientras no se llegue al final de la lista */ while(lista) { /* Si la encontr */ if(!fcmp(llave, lista->info)) break; /* Avanza al siguiente nodo */ *pAnt = lista; lista = lista->pSig; } return lista; }

Implementacin de una Lista Ligada Generalizada en C


Al igual que como lo hicimos con las pilas y las colas, podemos modificar las funciones que implementan las operaciones de una lista ligada para que sean independientes del tipo de dato almacenado en la lista. Para lograr esto modificaremos el contenido de los nodos que forman la lista ligada para que el campo de informacin en lugar de estar formada por uno o ms datos, sea un apuntador a un bloque con los datos, figura 16-13. Este apuntador ser de tipo void y la informacin sobre el tamao de los bloques de datos ser proporcionado a las funciones que la requieran mediante un parmetro adicional.

Figura 16-13 Las funciones que implementan las operaciones para una lista ligada generalizada se desarrollan como un mdulo. En el archivo de encabezados de este mdulo, se presenta en el listado LISTA.H. Podemos ver en la definicin de la estructura tipo nodo que el campo de informacin, pInfo, es un apuntador al bloque de datos. Tambin podemos ver que los prototipos de algunas de las funciones se modificaron para reflejar el hecho de que ahora se va a manejar un apuntador void a los datos y un parmetro extra para el tamao de los datos.

ITSON

Manuel Domitsu Kono

Captulo 16

Estructuras de Datos en C

435

LISTA.H
#ifndef LISTA_H #define LISTA_H struct nodo { void *pInfo; struct nodo *pSig; }; typedef struct nodo NODO; typedef struct nodo *LISTA; void inicializarLista(LISTA *pLista); int listaVacia(LISTA lista); void insertarLista(LISTA *pLista, NODO *pPos, NODO *pNodo); int extraerLista(LISTA *pLista, NODO *pPos, void *pDato, int tamDato); void visitarLista(LISTA lista, void (* fVisita)(NODO *pNodo)); NODO *buscaLista(LISTA lista, LISTA *pAnt, void *pLlave, int (* fcmp)(void *pInfo, void *pLlave)); NODO *creaNodo(void *pDato, int tamDato); void destruyeNodo(NODO *pNodo); #endif

El cdigo de las funciones para la lista ligada generalizada est en LISTA.C. Las funciones inicializarLista(), listaVacia() e insertarLista() no sufren modificaciones de sus versiones que almacenan enteros.

LISTA.C
/************************************************************* * LISTA.C * * Este mdulo implementa las operaciones de una lista ligada * generalizada, esto es, que opera con cualquier tipo de dato. * Las funciones que implementan las operaciones de la lista * reciben como parmetros datos o apuntadores de tipo LISTA y * NODO definidos como: * * struct nodo * { * void pInfo; /* Campo de informacin */ * struct nodo *pSig; /* Campo siguiente */ * }; * * typedef struct nodo NODO; * typedef struct nodo *LISTA; * * Aunque una variable y un apuntador de tipo LISTA declaradas * como: * * LISTA lista; * LISTA *pLista; * * podran haberse declarado como: * * NODO *lista; * NODO **pLista; * * sin necesidad de crear el tipo LISTA, el definir el tipo * LISTA permite utilizar a LISTA par hacer referencia a la

ITSON

Manuel Domitsu Kono

436

Estructuras de Datos en C

* lista y a NODO para hacer referencia a un nodo de la lista. * El uso del tipo LISTA simplifica las declaraciones como * la de NODO **plista a LISTA *pLista. *************************************************************/ #include <alloc.h> #include <mem.h> #include "lista.h" /************************************************************* *void inicializarLista(LISTA *pLista) * * Esta funcin inicializa (vaca) una lista. pLista es un * apuntador al apuntador a la lista. *************************************************************/ void inicializarLista(LISTA *pLista) { LISTA pTemp; /* Mientras haya nodos en la lista */ while(*pLista) { pTemp = *pLista; *pLista = (*pLista)->pSig; destruyeNodo(pTemp); } } /************************************************************* * int listaVacia(LISTA lista) * * Esta funcin regresa un 1 si la lista est vaca, 0 en * caso contrario. lista es un apuntador a la lista. *************************************************************/ int listaVacia(LISTA lista) { return lista == NULL; } /************************************************************* * void insertarLista(LISTA *pLista, NODO *pPos, NODO *pNodo) * * Esta funcin inserta el nodo apuntado por pNodo en la lista * apuntada por el apuntador pLista. pPos es un apuntador al * nodo despus del cual se va insertar el nodo. Si se desea * insertar el dato al inicio de la lista pPos debe valer NULL. * La funcin no verifica que pPos sea una direccin de un * nodo de la lista. *************************************************************/ void insertarLista(LISTA *pLista, NODO *pPos, NODO *pNodo) { /* Si la lista est vaca */ if(listaVacia(*pLista)) { *pLista = pNodo; return; } /* Si se va a insertar al inicio de la lista */ if(!pPos) { pNodo->pSig = *pLista; *pLista = pNodo; return; }

ITSON

Manuel Domitsu Kono

Captulo 16

Estructuras de Datos en C

437

/* Si se va a insertar despus del nodo pPos */ pNodo->pSig = pPos->pSig; pPos->pSig = pNodo; }

La funcin extraerLista() s se modifica ya que en lugar de recibir un apuntador a entero, en donde se va a almacenar el dato extrado de la lista, recibe por separado la direccin de la localidad donde se va al almacenar el dato y el tamao del dato en los parmetros pDato y tamDato. En el cdigo de la funcin podemos ver que para copiar el dato del nodo a la localidad dada por pDato se utiliza la funcin memcpy() que nos copia el dato byte por byte.

LISTA.C. Continuacin.
/************************************************************* * int extraerLista(LISTA *pLista, NODO *pPos, void *pDato, * int tamDato) * * Esta funcin extrae un nodo de la lista si no est vaca. * pPos es un apuntador al nodo despus del cual esta el nodo * que se va extraer. Si se desea extraer el dato al inicio de * la lista pPos debe valer NULL. pDato es la localidad de * memoria en la que se almacena el campo de informacin del * nodo extrado. tamDato es el tamao en bytes del campo de * informacin del nodo. La funcin no verifica que nPos sea * una direccin de un nodo de la lista. *************************************************************/ int extraerLista(LISTA *pLista, NODO *pPos, void *pDato, int tamDato) { LISTA pTemp; /* Si la lista esta vaca */ if(listaVacia(*pLista)) return 0; /* Si se va a extraer el primer elemento */ if(!pPos) { pTemp = *pLista; *pLista = (*pLista)->pSig; } /* Si se va a extraer un elemento intermedio */ else { pTemp = pPos->pSig; pPos->pSig = pTemp->pSig; } /* Extrae el dato */ memcpy(pDato, pTemp->pInfo, tamDato); /* Libera el nodo */ destruyeNodo(pTemp); return 1; }

ITSON

Manuel Domitsu Kono

438

Estructuras de Datos en C

La funcin visitarLista() tampoco se modifica de su versin previa. La funcin buscaLista(), en cambio, requiere que el parmetro que representan la llave de bsqueda y los parmetros de la funcin empleada para comparar la llave con los nodos sean apuntadores void.

LISTA.C. Continuacin.
/************************************************************* * void visitarLista(LISTA lista, void (* fVisitar)(NODO *pNodo)) * * Esta funcin recorre la lista dada por lista. En cada uno * de los nodos de la lista la funcin ejecuta la operacin * dada por la funcin fVisitar(). La funcin fVisitar() es * suministrada por el Usuario y es de tipo void y recibe como * parmetro un apuntador a NODO *************************************************************/ void visitarLista(LISTA lista, void (* fVisitar)(NODO *pNodo)) { /* Mientras no se llegue al final de la lista */ while(lista) { /* Opera sobre el nodo */ fVisitar(lista); /* Va al siguiente nodo */ lista = lista->pSig; } } /************************************************************* * NODO *buscaLista(LISTA lista, LISTA *pAnt, void *pLlave, * int (* fcmp)(void *pInfo, void *pLlave)) * * Esta funcin busca en la lista dada por lista la primera * ocurrencia de un nodo cuyo campo de informacin al * compararse con llave cumpla la condicin establecida por la * funcin fcmp(). La funcin regresa la direccin del nodo * que contiene esa primera ocurrencia, NULL en caso de no * encontrar un nodo que cumpla con la condicin. pAnt apunta * al nodo anterior al nodo con la primera ocurrencia. Si el * nodo con la primera ocurrencia es el primer nodo de la * lista, pAnt apunta a NULL. fcmp es un apuntador a la funcin * utilizada para comparar la llave con los nodos de la lista. * La funcin para comparar es suministrada por el usuario y es * de tipo entero y recibe como parmetros dos apuntadores void * a los datos a comparar: pInfo y pLlave que corresponden al * campo de informacin y a la llave. La funcin fcmp() debe * regresar 0 si el campo de informacin y la llave cumplen con * la condicin establecida por la funcin, diferente de cero * en caso contrario. * *************************************************************/ NODO *buscaLista(LISTA lista, LISTA *pAnt, void *pLlave, int (* fcmp)(void *pInfo, void *pLlave)) { *pAnt = NULL; /* Mientras no se llegue al final de la lista */ while(lista) { /* Si la encontr */ if(!fcmp(lista->pInfo, pLlave)) break; /* avanza al siguiente nodo */ *pAnt = lista; lista = lista->pSig;

ITSON

Manuel Domitsu Kono

Captulo 16

Estructuras de Datos en C

439

} return lista; } /************************************************************* * NODO *creaNodo(void *pDato, int tamDato) * * Esta funcin crea un nodo haciendo una peticin dinmica de * memoria e inicializa su campo de informacin con el valor * de info y el campo siguiente con un apuntador nulo. *************************************************************/ NODO *creaNodo(void *pDato, int tamDato) { NODO *pNodo; /* Pide un bloque de memoria para el nodo, en forma dinmica */ if((pNodo = malloc(sizeof(NODO)))) { /* Pide un bloque de memoria para el dato, en forma dinmica */ if((pNodo->pInfo = malloc(tamDato))) /* Almacena en el campo de informacin el dato */ memcpy(pNodo->pInfo, pDato, tamDato); else return NULL; /* Almacena en el campo siguiente un apuntador nulo */ pNodo->pSig = NULL; } return pNodo; } /************************************************************* * void destruyeNodo(NODO *pNodo) * * Esta funcin libera el bloque de memoria ocupada por el * nodo. *************************************************************/ void destruyeNodo(NODO *pNodo) { /* Libera el bloque de memoria ocupada por el dato */ free(pNodo->pInfo); /* Libera el bloque de memoria ocupada por el nodo */ free(pNodo); }

Una de las funciones que ms se ve modificada es la funcin creaNodo() ya que ya que en lugar de recibir un entero, el dato a almacenar en el nodo, recibe por separado la direccin de la localidad donde est el dato y el tamao del dato en los parmetros pDato y tamDato. En el cdigo de la funcin podemos ver que se requiere pedir dos bloques de memoria en forma dinmica: Una para almacenar el nodo y otra para almacenar el dato. Por ltimo la funcin destruyeNodo() requiere liberar los dos bloques de memoria pedidos por la funcin creaNodo(). El siguiente listado, DEMO_LL.C, es un mdulo que implementa un programa de demostracin que muestra el comportamiento de una lista ligada. Este mdulo debe ligarse a los mdulos: LISTA y UTILS.

ITSON

Manuel Domitsu Kono

440

Estructuras de Datos en C

DEMO_LL.C
/************************************************************* * DEMO_LL.C * * Este programa ilustra el comportamiento de una lista ligada * de enteros. *************************************************************/ #include #include #include #include <alloc.h> <stdio.h> <conio.h> <stdlib.h>

#include "utils.h" #include "lista.h" void despliegaPantallaInicial(void); void borraLista(void); void despliegaNodo(NODO *pNodo); int igual(void *pInfo, void *pLlave); int mayor(void *pInfo, void *pLlave); LISTA lista = NULL; int main(void) { NODO *pNodo, *pPos, *pAnt; int dato; char operador, sdato[6]; clrscr(); inicializarLista(&lista); despliegaPantallaInicial(); while(1) { gotoxy(28,10); printf(" "); gotoxy(51,10); printf(" "); gotoxy(28,10); if((operador = getch()) == ESC) break; if(!operador) { switch(operador = getch()) { case INS: printf("Insertar"); gotoxy(51,10); gets(sdato); dato = atoi(sdato); pNodo = creaNodo(&dato, sizeof(int)); if(listaVacia(lista)) pAnt = NULL; else pPos = buscaLista(lista, &pAnt, &dato, mayor); insertarLista(&lista, pAnt, pNodo); break; case DEL: printf("Extraer "); if(listaVacia(lista)) msj_error(0,25,"Lista vaca!, [Enter]", "\r",ATTR(RED, LIGHTGRAY,0)); else { gotoxy(51,10); gets(sdato); dato = atoi(sdato); if(!(pPos = buscaLista(lista, &pAnt, &dato, igual))) msj_error(0,25,"Valor inexistente!,\ [Enter]", "\r", ATTR(RED, LIGHTGRAY,0)); else extraerLista(&lista, pAnt, &dato, sizeof(int)); }

ITSON

Manuel Domitsu Kono

Captulo 16

Estructuras de Datos en C

441

break; } } borraLista(); visitarLista(lista, despliegaNodo); putchar('\n'); } inicializarLista(&lista); visitarLista(lista, despliegaNodo); putchar('\n'); return 0; } /************************************************************* * void despliegaPantallaInicial(void) * * Esta funcin despliega la explicacin de lo que hace el * programa y las instrucciones de su uso. *************************************************************/ void despliegaPantallaInicial(void) { clrscr(); gotoxy(5,3); printf("Este programa demuestra el comportamiento de una"); printf(" lista ligada. En esta"); gotoxy(5,4); printf("lista se almacenan enteros los cuales son"); printf(" acomodados en orden ascendente."); gotoxy(5,5); printf("Hay dos operaciones: Para insertar un numero a "); printf("la lista presiona la tecla"); gotoxy(5,6); printf("[Insert], luego el numero a insertar. Para "); printf("extraer un numero de la lista"); gotoxy(5,7); printf("presiona la tecla [Supr], luego el numero a extraer."); printf(" Para terminar presiona"); gotoxy(5,8); printf("la tecla [Esc]."); gotoxy(15,10); printf("Operacion [ ] Valor [ ]"); } /************************************************************* * void despliegaNodo(NODO *pNodo) * * Esta funcin despliega para un nodo, su campo de informacin * y su campo siguiente. *************************************************************/ void despliegaNodo(NODO *pNodo) { static x = 10, y = 13; /* Si es el primer nodo de la lista */ if(pNodo == lista) { x = 10; y = 13; } /* Si se va a saltar de rengln */ if(x == 74) { x = 10; y += 3; } /* Despliega el contenido del nodo en un recuadro */

ITSON

Manuel Domitsu Kono

442

Estructuras de Datos en C

gotoxy(x,y); putch(0x10); gotoxy(x+1,y-1); printf("+-------------+"); gotoxy(x+1,y); printf("%6d%6p", *(int *)(pNodo->pInfo),pNodo->pSig); gotoxy(x+1,y+1); printf("+-------------+"); x += 16; /* Si va a desplegar otra pantalla */ if(pNodo->pSig && y == 22 && x == 74) { msj_error(0,25,"[Enter] para continuar", "\r", ATTR(RED, LIGHTGRAY,0)); x = 10; y = 13; borraLista(); } } /************************************************************* * void borraLista(void) * * Borra de la pantalla la lista. *************************************************************/ void borraLista(void) { int i; for(i = 12; i < 25; i++) { gotoxy(1,i); clreol(); } } /************************************************************* * int igual(void *pInfo, void *pLlave) * * Esta funcin regresa 0 si info y llave son iguales, diferente * de cero en caso contrario. *************************************************************/ int igual(void *pInfo, void *pLlave) { return *(int *)pInfo - *(int *)pLlave; } /************************************************************* * int mayor(void *pInfo, void *pLlave) * * Esta funcin regresa 0 si info > llave, diferente de cero * en caso contrario. *************************************************************/ int mayor(void *pInfo, void *pLlave) { return *(int *)pInfo <= *(int *)pLlave; }

Ejercicio Sobre Listas Ligadas


Escribe una funcin que nos regrese el nmero de nodos en una lista ligada genrica. La sintaxis de la funcin es int longLista(LISTA lista ); donde lista es el apuntador a la lista ligada.

ITSON

Manuel Domitsu Kono

Captulo 16

Estructuras de Datos en C

443

Aplicaciones de Listas Ligadas


Las aplicaciones de las listas ligadas son mucho ms variadas que las de las pilas y colas. De hecho las primeras aplicaciones que podemos mencionar de la listas ligadas son las de implementar pilas y colas que no estn limitadas a un tamao mximo (siempre y cuando haya memoria disponible). Como una segunda aplicacin mencionaremos un asignador de memoria que es el mecanismo mediante el cual el sistema operativo nos asigna memoria en forma dinmica y la libera cuando ya no la necesitamos. Describiremos en forma simplificada este mecanismo. En [Kernighan y Ritchie, 1991] se muestra una implementacin de las funciones malloc() y free() que se emplean para solicitar y liberar memoria respectivamente. Ya se mencion anteriormente que el espacio de memoria disponible para asignaciones dinmicas se conoce como el montculo. Conforme se van atendiendo peticiones de memoria, el montculo se va dividiendo en bloques asignados (los otorgados como respuesta a una peticin de memoria) y bloques libres (disponibles para ser asignados). Para llevar el control del espacio de memoria disponible, el asignador de memoria utiliza una lista ligada circular en la que cada nodo de la lista representa un bloque de memoria libre. El campo de informacin, tb, contiene el tamao del bloque y el campo siguiente, ps, apunta al siguiente bloque libre. Una lista ligada circular es similar a la lista ligada que estudiamos slo que el ltimo nodo de la lista en lugar de apuntar a NULL, apunta al primer nodo de la lista. La figura 16-14a ilustra la situacin de ese montculo antes de que se hayan hecho solicitudes de memoria. En este caso la lista contiene un slo nodo, con tb igual al tamao del montculo menos el tamao del nodo. En este primer momento ps apunta a si mismo. El apuntador pLL apunta a la lista ligada. Cuando se hace una solicitud, el asignador de memoria rastrea la lista de bloques libres hasta que encuentra un bloque suficientemente grande para cubrir la solicitud. Si el bloque es exactamente del tamao requerido, se extrae de la lista y se le entrega al usuario. Si el bloque es mayor se divide y se le entrega al usuario la cantidad requerida y el resto se deja en la lista libre. En la figura 16-14b se muestra el estado del montculo y de la lista ligada despus de haberse asignado un bloque de memoria, como respuesta, tal vez, a la funcin malloc(). Note que el bloque asignado contiene un encabezado con la misma informacin que un nodo de la lista. El campo de informacin de este encabezado es el tamao del bloque asignado y el campo siguiente apunta al siguiente bloque libre. El apuntador a bloque l asignado, pA, que nos regresa la funcin malloc() apunta a la localidad enseguida del encabezado. En las figuras 16-14c y 16-14d se muestran los estados del montculo y de la lista ligada despus de haberse asignado un bloque adicional de memoria en cada caso. Al liberar un bloque, se realiza una bsqueda en la lista libre, para encontrar el lugar apropiado para insertar el bloque que se est liberando. Si el bloque liberado es adyacente a un bloque libre, se une con l en un bloque nico ms grande. En la figura 16-14e se muestra el estado del montculo y de la lista ligada despus de haberse liberado el segundo bloque de memoria asignado mediante una llamada a la funcin free().

ITSON

Manuel Domitsu Kono

444

Estructuras de Datos en C

Figura 16-14

rboles Binarios
Un rbol binario puede definirse formalmente como: Un rbol binario es un apuntador a un nodo llamado la raz. La raz de un rbol binario tiene tres campos: 1. El campo de informacin, que es el dato del elemento. 2. El campo subrbol izquierdo, el cual es un rbol binario. 3. El campo subrbol derecho , el cual es un rbol binario. La definicin de rbol binario es una definicin recursiva. Un rbol binario es un apuntador a un nodo el cual a su vez apunta a dos rboles binarios. El caso base de esta definicin es un rbol binario nulo. Un rbol binario nulo se representa como un apuntador nulo como se muestra en la figura 16-15a. En la figura 16-15b se muestra un rbol binario con un slo nodo, su nodo raz. Note que los campos subrbol i quierdo y subrbol derecho son nulos. Hay dos posibles rboles binarios con dos nodos, z figuras 16-15c y 16-15d. Note que uno de los nodos es la raz. Luego uno de los subrboles ya sea el izquierdo o el derecho es vaco y el otro contendr exactamente un nodo. Con tres nodos podemos formar tres rboles binarios, los cuales se ilustran en la figura 16-15e, 16-15f y 16-15g.

ITSON

Manuel Domitsu Kono

Captulo 16

Estructuras de Datos en C

445

Figura 16-15 A los nodos que se encuentran en la raz de los subrboles de un nodo se les llama los hijos de ese nodo. Cada nodo tiene a lo sumo dos hijos llamados hijo izquierdo e hijo derecho . Los nodos que no tienen hijos se llaman hojas . En los rboles binarios los nodos estn ordenados. La forma en que los nodos se ordenan depende de la aplicacin que se le de al rbol. Uno de los usos de los rboles binarios es la bsqueda de informacin. A los rboles empleados en esta aplicacin se les llama rboles binarios de bsqueda y se pueden definir de la siguiente manera: Un rbol binario de bsqueda es un rbol binario vaco o uno en el que el campo de informacin de cada uno de sus nodos contiene una llave que satisface las siguientes condiciones:
ITSON Manuel Domitsu Kono

446

Estructuras de Datos en C

1. Todas las llaves (si hay) en el subrbol izquierdo preceden a la llave en la raz. 2. La llave en la raz precede a todas las llaves en el subrbol derecho. 3. Los subrboles izquierdo y derecho son rboles binarios de bsqueda. 4. No hay dos nodos con la misma llave. En este curso slo se estudiarn rboles binarios de bsqueda y por simplicidad nos referiremos a ellos como rboles binarios. En la figura 16-16a se muestra un rbol binario de bsqueda en la que la llave es todo el campo de informacin y est formada por enteros.

Figura 16-16 En la figura 16-16b se muestra una representacin simplificada del mismo rbol binario de la figura 1616a. Los crculos representan los campos de informacin de cada nodo y los apuntadores a los subrboles izquierdo y derecho se representan mediante las flechas, apuntando hacia abajo y a la izquierda en el caso de pIzq y hacia abajo y a la derecha en el caso de pDer. Los apuntadores a subrboles nulos se omiten por lo general.

ITSON

Manuel Domitsu Kono

Captulo 16

Estructuras de Datos en C

447

Operaciones Primitivas con los rboles Binarios


Las operaciones primitivas definidas para un rbol binario son: Inicializar: Vaca: Insertar: Extraer: Visitar: Buscar: Vaca el rbol Regresa verdadero si el rbol est vaco. Inserta un elemento en el rbol. Extrae el elemento del rbol que se encuentra en determinada posicin. Recorre el rbol efectuando una operacin sobre cada elemento del rbol. Busca en el rbol el primer elemento que cumpla con cierto criterio.

Implementacin de un rbol Binario en C


A continuacin implementaremos un rbol binario en C en el que el campo de informacin de cada nodo es un entero. Posteriormente se generalizar para que contenga cualquier tipo de dato. Primero se define el tipo de dato que contendr a un nodo.
struct hoja { int info; struct hoja *pIzq; struct hoja *pDer; };

/* Campo de informacin */ /* Apuntador al subrbol izquierdo */ /* Apuntador al subrbol derecho */

Para simplificar las declaraciones de las variables y apuntadores a la estructura nodo, definiremos el siguiente alias:
typedef struct hoja HOJA; /* Alias de la estructura hoja */

Adems para declarar el apuntador a la raz del rbol creamos el siguiente alias:
typedef struct hoja *ARBOL; /* Alias del apuntador a la raz */

Todas las funciones para el manejo de rboles binarios reciben como parmetros apuntadores del tipo ARBOL o apuntadores a HOJA. La operacin para inicializar el rbol est implementada por la funcin inicializarArbol() cuyo cdigo es
ARBOL inicializarArbol(ARBOL raiz) { /* Si el rbol no est vaco */ if(raiz) { /* Inicializa (vaca) el subrbol izquierdo */ raiz->pIzq = inicializarArbol(raiz->pIzq);

ITSON

Manuel Domitsu Kono

448

Estructuras de Datos en C

/* Inicializa (vaca) el subrbol derecho */ raiz->pDer = inicializarArbol(raiz->pDer); /* Libera el nodo ocupado por la raz */ destruyeHoja(raiz); } /* Hace el rbol nulo */ return NULL; }

La funcin elimina los nodos del rbol binario cuya direccin est dada por raz. Al terminar la funcin regresa NULL, valor que se debe asignar al apuntador al rbol para indicar que est vaco, esto se puede lograr mediante la siguiente llamada a la funcin:
raiz = inicializarArbol(raiz);

La funcin es recursiva y para entender su funcionamiento hay que recordar que un rbol binario es un nodo que apunta a dos subrboles, esto es cada nodo de un rbol es la raz del rbol formado por sus dos subrboles. La definicin recursiva de la funcin inicializarArbol() es la siguiente: Para cada nodo del rbol, empezando por su raz: Caso base: Si el nodo es nulo, regresa NULL. Caso inductivo: Elimina el subrbol izquierdo del nodo. Elimina el subrbol derecho del nodo. Elimina el nodo. Podemos ver que para eliminar un rbol, la funcin inicializarArbol(), debe eliminar primero su subrbol izquierdo, luego su subrbol derecho y luego su raz. Como el subrbol izquierdo tambin es un rbol, primero debe eliminar su subrbol izquierdo, luego su subrbol derecho y luego su raz, etc. El proceso anterior termina hasta que la funcin llega a la hoja que se encuentra ms a la izquierda del rbol y como sta ya no tiene subrboles, puede ser eliminada, a continuacin elimina la siguiente hoja ms a la izquierda y as consecutivamente, como se ilustra en la secuencia mostrada en la figura 16-17. Para eliminar un nodo del rbol, la funcin inicializarArbol() llama a la funcin destruyeHoja() que en el caso de un rbol que almacena enteros slo requiere a una llamada a la funcin free(). En el caso de un rbol generalizado, como veremos ms adelante, la funcin destruyeHoja() tendr ms instrucciones.
void destruyeHoja(HOJA *pHoja) { free(pHoja); }

ITSON

Manuel Domitsu Kono

Captulo 16

Estructuras de Datos en C

449

Figura 16-17 La operacin vaca se implementa mediante la funcin arbolVacio() la cual regresa un 1 si el rbol dado por raiz est vaco, 0 en caso contrario. El cdigo de la funcin arbolVacio() es el siguiente.
int arbolVacio(ARBOL raiz) { return raiz == NULL; }

La operacin insertar se implementa mediante la funcin insertarArbol(), cuyo cdigo se muestra a continuacin:

ITSON

Manuel Domitsu Kono

450

Estructuras de Datos en C

ARBOL insertarArbol(ARBOL raiz, HOJA *pHoja, int (* fcmp)(int info1, int info2)) { int rc; /* Si el rbol/subrbol no est vaco */ if(raiz) { /* Compara el nodo a insertar con raz */ rc = fcmp(raiz->info, pHoja->info); /* Si el nodo a insertar es menor que la raz, inserta en el subrbol izquierdo */ if(rc > 0) raiz->pIzq = insertarArbol(raiz->pIzq, pHoja, fcmp); /* Si el nodo a insertar es mayor que la raz, inserta en el subrbol derecho */ else if(rc < 0) raiz->pDer = insertarArbol(raiz->pDer, pHoja, fcmp); return(raiz); } /* Si el rbol/subrbol est vaco */ return pHoja; }

La funcin insertarArbol() inserta el nodo apuntado por pHoja en el rbol apuntado por raz. El parmetro fcmp es un apuntador a la funcin utilizada para comparar el nodo a insertar y la raz del rbol. Esta funcin es necesaria ya que hay que recordar que en un rbol binario los nodos estn ordenados. La funcin para comparar es suministrada por el usuario y es de tipo entero y recibe como parmetros los campos de informacin de los nodos a comparar: info1 e info2 que corresponden a los de la raz y del nodo a insertar, respectivamente. La funcin para comparar debe regresar: 0 si info1 == info2 (+) si info1 > info2 (-) si info1 < info2 Al terminar la funcin regresa un apuntador al rbol, valor que puede ser diferente al valor del parmetro raz, si inicialmente el rbol estaba vaco. La llamada a la funcin tiene la forma:
raiz = insertarArbol(raiz, pNodo, fcmp);

La funcin insertarArbol() tambin es una funcin recursiva y puede definirse recursivamente como: Caso base: Si el subrbol es nulo, inserta ah el nodo. Caso inductivo: Si el nodo a insertar es menor que la raz, inserta el nodo en el subrbol izquierdo.

ITSON

Manuel Domitsu Kono

Captulo 16

Estructuras de Datos en C

451

Si el nodo a insertar es mayor que la raz, inserta el nodo en el subrbol derecho. De la definicin anterior, caso base, podemos notar que el nodo siempre se inserta en un subrbol nulo, esto es, en la parte inferior del rbol como una hoja y nunca entre dos nodos. El nodo a insertar ya ha sido creado y la funcin insertarArbol() recibe un apuntador a ese nodo, pHoja. Una implementacin de la funcin para crear un nodo est dada por creaHoja(), la cual hace una peticin dinmica de memoria llamando a la funcin malloc(), e inicializa su campo de informacin con el valor de dato y los apuntadores a los subrboles izquierdo y derecho a apuntadores nulos. Si hay xito la funcin regresa un apuntador al nodo creado, NULL en caso contrario.
HOJA *creaHoja(int dato) { HOJA *pHoja; /* Pide un bloque de memoria en forma dinmica */ if((pHoja = malloc(sizeof(HOJA)))) { /* Almacena en el campo de informacin el dato */ pHoja->info = dato; /* Almacena en los apuntadores a los subrboles izquierdo y derecho apuntadores nulos */ pHoja->pIzq = pHoja->pDer = NULL; } return pHoja; }

La operacin extraer se implementa mediante la funcin extraerArbol() la cual extrae el nodo cuya direccin se encuentra almacenada en la direccin dada por pPos. pDato es la localidad de memoria en la que se almacena el campo de informacin del nodo extrado. La funcin regresa 1 si hubo xito, 0 en caso de que el nodo a extraer sea un nodo nulo. La direccin almacenada en pPos debe ser la direccin de un nodo del rbol ya que la funcin no verifica esto. El cdigo de la funcin es
int extraerArbol(ARBOL *pPos, int *pDato) { HOJA *pTemp; /* Si el nodo a extraer es nulo */ if(!(*pPos)) return 0; /* Si el subrbol derecho del nodo a extraer es nulo */ if(!(*pPos)->pDer) { pTemp = *pPos; /* Conecta el subrbol izquierdo del nodo a extraer */ *pPos = (*pPos)->pIzq; } /* Si el subrbol izquierdo del nodo a extraer es nulo */ else if(!(*pPos)->pIzq) { pTemp = *pPos; /* Conecta el subrbol derecho del nodo a extraer */ *pPos = (*pPos)->pDer; }

ITSON

Manuel Domitsu Kono

452

Estructuras de Datos en C

/* Si ninguno de los subrboles del nodo a extraer son nulos */ else { /* Encuentra el nodo ms a la izquierda del subrbol derecho */ for(pTemp = (*pPos)->pDer; pTemp->pIzq; pTemp = pTemp->pIzq); /* Conecta el subrbol izquierdo del nodo a extraer a la hoja ms a la izquierda del subrbol derecho */ pTemp->pIzq = (*pPos)->pIzq; pTemp = *pPos; /* Conecta el subrbol derecho del nodo a extraer */ *pPos = (*pPos)->pDer; } /* Extrae el dato */ *pDato = pTemp->info; /* Libera el nodo */ destruyeHoja(pTemp); return 1; }

La sintaxis para llamar a la funcin anterior es:


HOJA **pPos; int dato; extraerArbol(pPos, &dato);

Al querer extraer un nodo del rbol se pueden presentar cuatro casos. Caso I: Caso II: El nodo a extraer es un nodo nulo. En este caso la funcin slo regresa 0 indicando su fracaso. El subrbol derecho del nodo a extraer es un subrbol nulo, figura 16-18a. Aqu se requieren cuatro pasos: 1. Hacer que el apuntador pTemp apunte al nodo a eliminar. Figura 16-18b. 2. Hacer que el apuntador al nodo a eliminar apunte a su subrbol izquierdo. Figura 16-18c. 3. Copia el campo de informacin del nodo a extraer a la localidad apuntada por pDato. Figura 16-18d. 4. Elimina el nodo llamando a la funcin destruyeHoja(), figura 16-18e, la cual libera el bloque de memoria ocupado por el nodo, figura 16-18f.

ITSON

Manuel Domitsu Kono

Captulo 16

Estructuras de Datos en C

453

Figura 16-18 Caso III: El subrbol izquierdo del nodo a extraer es un subrbol nulo, figura 16-19a. Aqu se requieren cuatro pasos:

ITSON

Manuel Domitsu Kono

454

Estructuras de Datos en C

Figura 16-19 1. Hacer que el apuntador pTemp apunte al nodo a eliminar. Figura 16-19b. 2. Hacer que el apuntador al nodo a eliminar apunte a su subrbol derecho. Figura 1619c. 3. Copia el campo de informacin del nodo a extraer a la localidad apuntada por pDato. Figura 16-19d. 4. Elimina el nodo llamando a la funcin destruyeHoja(), figura 16-19e, la cual libera el bloque de memoria ocupado por el nodo, figura 16-19f.

ITSON

Manuel Domitsu Kono

Captulo 16

Estructuras de Datos en C

455

Caso IV:

Ninguno de los subrboles del nodo a extraer son nulos, figura 16-20a. Aqu se requieren cuatro pasos:

Figura 16-20 1. Hacer que el apuntador pTemp apunte al nodo ms izquierdo del subrbol derecho del nodo a eliminar. Figura 16-20b. 2. Conectar el subrbol izquierdo del nodo a eliminar al nodo ms izquierdo del subrbol derecho del nodo a eliminar. Figura 16-20c.

ITSON

Manuel Domitsu Kono

456

Estructuras de Datos en C

3. Hacer que el apuntador pTemp apunte al nodo a eliminar. Figura 16-20d. 4. Hacer que el apuntador al nodo a eliminar apunte a su subrbol derecho. Figura 1620e. 5. Copia el campo de informacin del nodo a extraer a la localidad apuntada por pDato. Figura 16-20f. 6. Elimina el nodo llamando a la funcin destruyeHoja(), figura 16-20g, la cual libera el bloque de memoria ocupado por el nodo, figura 16-20h. La operacin visitar un rbol implica recorrer el rbol efectuando una operacin sobre cada elemento del rbol. Por lo general se desea recorrer los nodos siguiendo un patrn regular. En cada nodo hay tres tareas a realizar en un determinado orden. Visitar el nodo, visitar el subrbol izquierdo y visitar el subrbol derecho. Si imponemos la restriccin de visitar primero el subrbol izquierdo y luego el derecho tenemos tres formas de recorrer el rbol: I Visitar el rbol en preorden en la que las tareas se realizan en el siguiente orden:
1. 2. 3.

Visita el nodo Visita el subrbol izquierdo. Visita el subrbol derecho.

II Visitar el rbol en orden en la que las tareas se realizan en el siguiente orden:


1. 2. 3.

Visita el subrbol izquierdo. Visita el nodo Visita el subrbol derecho.

III Visitar el rbol en postorden en la que las tareas se realizan en el siguiente orden:
1. 2. 3.

Visita el subrbol izquierdo. Visita el subrbol derecho. Visita el nodo

El cdigo de las funciones que implementan las tres formas de visitar un rbol dado por raiz se muestran a continuacin. En cada uno de los nodos del rbol la funcin ejecuta la operacin dada por la funcin fVisita().
void visitarArbolPreOrden(ARBOL raiz, void (* fVisita)(HOJA *pHoja)) { /* Si el rbol no est vaco */ if(raiz) { /* Visita la raz */ fVisita(raiz); /* Visita el subrbol izquierdo */ visitarArbolPreOrden(raiz->pIzq, fVisita);

ITSON

Manuel Domitsu Kono

Captulo 16

Estructuras de Datos en C

457

/* Visita el subrbol derecho */ visitarArbolPreOrden(raiz->pDer, fVisita); } } void visitarArbolEnOrden(ARBOL raiz, void (* fVisita)(HOJA *pHoja)) { /* Si el rbol est vaco */ if(raiz) { /* Visita el subrbol izquierdo */ visitarArbolEnOrden(raiz->pIzq, fVisita); /* Visita la raz */ fVisita(raiz); /* Visita el subrbol derecho */ visitarArbolEnOrden(raiz->pDer, fVisita); } } void visitarArbolPostOrden(ARBOL raiz, void (* fVisita)(HOJA *pHoja)) { /* Si el arbol est vaco */ if(raiz) { /* Visita el subrbol izquierdo */ visitarArbolPostOrden(raiz->pIzq, fVisita); /* Visita el subrbol derecho */ visitarArbolPostOrden(raiz->pDer, fVisita); /* Visita la raz */ fVisita(raiz); } }

Las tres funciones anteriores son recursivas. Por ejemplo, la definicin recursiva de la funcin visitarArbolPreOrden() es: Caso base: Si el rbol es nulo, termina. Caso inductivo: Visita el nodo Visita el subrbol izquierdo en preorden. Visita el subrbol derecho en preorden. Las otras dos definiciones recursivas son similares. La funcin fvisita(), proporcionada por el usuario, es una funcin de tipo void y q recibe como ue parmetro un apuntador al nodo sobre el que va a actuar. Por ejemplo supongamos que deseamos desplegar para cada nodo del rbol, su direccin, el valor de su campo de informacin y las direcciones de sus subrboles izquierdo y derecho, podramos emplear la siguiente funcin:

ITSON

Manuel Domitsu Kono

458

Estructuras de Datos en C

void despliegaHoja(HOJA *pHoja) { printf("\nDir nodo: %p dato: %d izq: %p der: %p", pHoja, pHoja->info, pHoja->pIzq, pHoja->pDer); }

La operacin buscar se implementa mediante la funcin buscaArbol() la cual busca en el rbol cuya direccin est almacenada en la localidad apuntada por pRaiz, el nodo cuyo campo de informacin es igual a llave. La funcin regresa la direccin de la localidad de memoria donde est almacenada la direccin del nodo, si existe, NULL en caso contrario. fcmp es un apuntador a la funcin utilizada para comparar el campo de informacin de la raz con la llave. La funcin para comparar es suministrada por el usuario y es de tipo entero y recibe como parmetros: info, el campo de informacin de la raz y llave, la llave. La funcin para comparar debe regresar: 0 si info == llave (+) si info > llave (-) si info < llave El cdigo de la funcin es:
HOJA **buscaArbol(ARBOL *pRaiz, int llave, int (* fcmp)(int info, int llave)) { int rc; HOJA **ppPos = pRaiz; /* Si el rbol no est vaco */ if(*pRaiz) { /* Compara llave con nodo */ rc = fcmp((*pRaiz)->info, llave); /* Si no, busca en el subrbol izquierdo */ if(rc > 0) ppPos = buscaArbol(&((*pRaiz)->pIzq), llave, fcmp); /* si no, busca el subrbol derecho */ else if(rc < 0) ppPos = buscaArbol(&((*pRaiz)->pDer), llave, fcmp); } return ppPos; }

La sintaxis para llamar a la funcin anterior es:


HOJA **pPos; int dato; pPos = buscaArbol(&raiz, 3, fcmp); extraerArbol(pPos, &dato);

La funcin anterior es una funcin recursiva. La definicin recursiva de la funcin es:

ITSON

Manuel Domitsu Kono

Captulo 16

Estructuras de Datos en C

459

Caso base: Si la raz del rbol/subrbol es nula, regresa NULL. Si info == llave regresa la posicin de la raz. Caso inductivo: Si info > llave busca en el subrbol izquierdo. Si info < llave busca en el subrbol derecho.

Implementacin de un rbol Binario de Bsqueda Generalizado en C


Al igual que como lo hicimos con la lista ligada, podemos modificar las funciones que implementan las operaciones de un rbol binario de bsqueda para que sean independientes del tipo de dato almacenado en el rbol. Para lograr esto modificaremos el contenido de los nodos que forman el rbol para que el campo de informacin en lugar de estar formada por uno o ms datos, sea un apuntador a un bloque con los datos. Este a puntador ser de tipo void y la informacin sobre el tamao de los bloques de datos ser proporcionado a las funciones que la requieran mediante un parmetro adicional. Las funciones que implementan las operaciones para un rbol binario de bsqueda generalizado se desarrollan como un mdulo. En el archivo de encabezados de este mdulo, se presenta en el listado ARBOL.H. Podemos ver en la definicin de la estructura tipo hoja que el campo de informacin, pInfo, es un apuntador al bloque de datos. Tambin podemos ver que los prototipos de algunas de las funciones se modificaron para reflejar el hecho de que ahora se va a manejar un apuntador void a los datos y un parmetro extra para el tamao de los datos.

ARBOL.H
#ifndef ARBOL_H #define ARBOL_H struct hoja { void *pInfo; /* Campo de (apuntador) informacin */ struct hoja *pIzq; /* Apuntador al subrbol izquierdo */ struct hoja *pDer; /* Apuntador al subrbol derecho */ }; typedef struct hoja HOJA; /* Alias de la estructura hoja */ typedef struct hoja *ARBOL;/* Alias del apuntador a la raz del rbol */ ARBOL inicializarArbol(ARBOL raiz); int arbolVacio(ARBOL raiz); ARBOL insertarArbol(ARBOL raiz, HOJA *pHoja, int (* fcmp)(void *pInfo, void *pLlave)); int extraerArbol(ARBOL *pPos, void *pDato, int tamDato); void visitarArbolPreOrden(ARBOL raiz, void (* fVisita)(HOJA *pHoja)); void visitarArbolEnOrden(ARBOL raiz, void (* fVisita)(HOJA *pHoja)); void visitarArbolPostOrden(ARBOL raiz, void (* fVisita)(HOJA *pHoja)); HOJA **buscaArbol(ARBOL *pRaiz, void *pLlave, int (* fcmp)(void *pInfo, void *pLlave));

ITSON

Manuel Domitsu Kono

460

Estructuras de Datos en C

HOJA *creaHoja(void *pInfo, int tamDato); void destruyeHoja(HOJA *pHoja); #endif

El cdigo de las funciones para un rbol binario de bsqueda generalizado est en ARBOL.C. Las funciones inicializarArbol() y arbolVacio() no sufren modificaciones de sus versiones que almacenan enteros. En cambio, la funcin insertarArbol() generalizada difiere de su correspondiente para enteros en los parmetros de la funcin utilizada para comparar los nodos, ya que en este caso los parmetros son apuntadores a void para permitir que apunten a cualquier tipo de dato.

ARBOL.C.
/************************************************************* * ARBOL.C * * Este mdulo implementa las operaciones de un rbol binario * generalizado, esto es, que opera con cualquier tipo de dato. * Las funciones que implementan las operaciones del rbol * reciben como parmetros datos o apuntadores de tipo ARBOL y * HOJA definidos como: * * struct hoja * { * void *pInfo; Apuntador al campo de informacin * struct hoja *pIzq; Apuntador al subrbol izquierdo * struct hoja *pIzq; Apuntador al subrbol derecho * }; * * typedef struct hoja HOJA; Alias de la estructura hoja * typedef struct hoja *ARBOL; Alias del apuntador a la * raz del arbol *************************************************************/ #include <alloc.h> #include <mem.h> #include "arbol.h" /************************************************************* * ARBOL inicializarArbol(ARBOL raiz) * * Esta funcin inicializa (vaca) un arbol. raz es un * apuntador al rbol *************************************************************/ ARBOL inicializarArbol(ARBOL raiz) { /* Si el rbol no est vaco */ if(raiz) { /* Inicializa (vaca) el subrbol izquierdo */ raiz->pIzq = inicializarArbol(raiz->pIzq); /* Inicializa (vaca) el subrbol izquierdo */ raiz->pDer = inicializarArbol(raiz->pDer); /* Libera el nodo ocupado por la raz */ destruyeHoja(raiz); } /* Hace que el apuntador al rbol sea nulo */ return NULL; }

ITSON

Manuel Domitsu Kono

Captulo 16

Estructuras de Datos en C

461

/************************************************************* *int arbolVacio(ARBOL raiz) * * Esta funcin regresa un 1 si el rbol est vaco, 0 en caso * contrario. raz es un apuntador al rbol. *************************************************************/ int arbolVacio(ARBOL raiz) { return raiz == NULL; } /************************************************************* * ARBOL insertarArbol(ARBOL raiz, HOJA *pHoja, * int (* fcmp)(void *pInfo1, void *pInfo2)) * * Esta funcin inserta el nodo apuntado por pHoja en el rbol * apuntado por raz. fcmp es un apuntador a la funcin * utilizada para comparar el nodo a insertar y la raz del * rbol. La funcin para comparar es suministrada por el * usuario y es de tipo entero y recibe como parmetros * apuntadores a los campos de informacin comparar: pInfo1 e * pInfo2 que corresponden a los de la raz y del nodo a * insertar, respectivamente. La funcin para comparar debe * regresar: * * 0 si info1 == info2 * (+) si info1 > info2 * (-) si info1 < info2 *************************************************************/ ARBOL insertarArbol(ARBOL raiz, HOJA *pHoja, int (* fcmp)(void *pInfo1, void *pInfo2)) { /* Si el rbol no est vaco */ if(raiz) { /* Si el nodo a insertar es menor que la raz inserta en el subrbol izquierdo */ if(fcmp(raiz->pInfo, pHoja->pInfo) > 0) raiz->pIzq = insertarArbol(raiz->pIzq, pHoja, fcmp); /* Si no inserta en el subrbol derecho */ else raiz->pDer = insertarArbol(raiz->pDer, pHoja, fcmp); return(raiz); } return pHoja; }

La funcin extraerArbol() tambin se modifica ya que en lugar de recibir un apuntador a entero, en donde se va a almacenar el dato extrado del rbol, recibe por separado la direccin de la localidad donde se va al almacenar el dato y el tamao del dato en los parmetros pDato y tamDato. En el cdigo de la funcin podemos ver que para copiar el dato del nodo a la localidad dada por pDato se utiliza la funcin memcpy() que nos copia el dato byte por byte.

ARBOL.C. Continuacin.
/************************************************************* * int extraerArbol(ARBOL *pPos, void *pDato, int tamDato) *

ITSON

Manuel Domitsu Kono

462

Estructuras de Datos en C

* Esta funcin extrae un nodo del rbol si el nodo no est * vaco. pPos es un apuntador al nodo que se va extraer. * pDato es la localidad de memoria en la que se almacena el * campo de informacin del nodo extrado. La funcin no * verifica que pPos sea una direccin de un nodo del rbol. *************************************************************/ int extraerArbol(ARBOL *pPos, void *pDato, int tamDato) { HOJA *pTemp; /* Si el nodo est vaco */ if(!(*pPos)) return 0; /* Si el subrbol derecho del nodo a extraer est vaco */ if(!(*pPos)->pDer) { pTemp = *pPos; /* Conecta el subrbol izquierdo del nodo a extraer */ *pPos = (*pPos)->pIzq; } /* Si el subrbol izquierdo del nodo a extraer est vaco */ else if(!(*pPos)->pIzq) { pTemp = *pPos; /* Conecta el subrbol derecho del nodo a extraer */ *pPos = (*pPos)->pDer; } /* Si ninguno de los subrboles del nodo a extraer est vaco */ else { /* Encuentra la hoja ms a la izquierda del subrbol derecho */ for(pTemp = (*pPos)->pDer; pTemp->pIzq; pTemp = pTemp->pIzq); /* Conecta el subrbol izquierdo del nodo a extraer a la hoja ms a la izquierda del subrbol derecho */ pTemp->pIzq = (*pPos)->pIzq; pTemp = *pPos; /* Conecta el subrbol derecho del nodo a extraer */ *pPos = (*pPos)->pDer; } /* Extrae el dato */ memcpy(pDato, pTemp->pInfo, tamDato); /* Libera el nodo */ destruyeHoja(pTemp); return 1; }

La funciones visitarArbolPreOrden(), visitarArbolEnOrden() y visitarArbolPostOrden(), tampoco se modifican de sus versiones para enteros.

ITSON

Manuel Domitsu Kono

Captulo 16

Estructuras de Datos en C

463

ARBOL.C. Continuacin.
/************************************************************* * void visitarArbolPreOrden(ARBOL raiz, * void (* fVisita)(HOJA *pHoja)) * * Esta funcin recorre el rbol binario dado por raz en * preorden, esto es, primero la raz, luego el subrbol * izquierdo y por ltimo el subrbol derecho. En cada uno de * los nodos del rbol la funcin ejecuta la operacin dada * por la funcin fVisita() proporcionada por el usuario. *************************************************************/ void visitarArbolPreOrden(ARBOL raiz, void (* fVisita)(HOJA *pHoja)) { /* Si el rbol no est vaco */ if(raiz) { /* Visita la raz */ fVisita(raiz); /* Visita el subrbol izquierdo */ visitarArbolPreOrden(raiz->pIzq, fVisita); /* Visita el subrbol derecho */ visitarArbolPreOrden(raiz->pDer, fVisita); } } /************************************************************* * void visitarArbolEnOrden(ARBOL raiz, * void (* fVisita)(HOJA *pHoja)) * * Esta funcin recorre el rbol binario dado por raz en * orden, esto es, primero el subrbol izquierdo, luego la * raz y por ltimo el subrbol derecho. En cada uno de los * nodos del rbol la funcin ejecuta la operacin dada por * funcin fVisita() proporcionada por el usuario. *************************************************************/ void visitarArbolEnOrden(ARBOL raiz, void (* fVisita)(HOJA *pHoja)) { /* Si el rbol no est vaco */ if(raiz) { /* Visita el subrbol izquierdo */ visitarArbolEnOrden(raiz->pIzq, fVisita); /* Visita la raz */ fVisita(raiz); /* Visita el subrbol derecho */ visitarArbolEnOrden(raiz->pDer, fVisita); } } /************************************************************* * void visitarArbolPostOrden(ARBOL raiz, * void (* fVisita)(HOJA *pHoja)) * * Esta funcin recorre el rbol binario dado por raz en * orden, esto es, primero el subrbol izquierdo, luego el * subrbol derecho y por ltimo la raz. En cada uno de los * nodos del rbol la funcin ejecuta la operacin dada por * la funcin fVisita() proporcionada por el usuario. *************************************************************/ void visitarArbolPostOrden(ARBOL raiz, void (* fVisita)(HOJA *pHoja)) {

ITSON

Manuel Domitsu Kono

464

Estructuras de Datos en C

/* Si el rbol est vaco */ if(raiz) { /* Visita el subrbol izquierdo */ visitarArbolPostOrden(raiz->pIzq, fVisita); /* Visita el subrbol derecho */ visitarArbolPostOrden(raiz->pDer, fVisita); /* Visita la raz */ fVisita(raiz); } }

La funcin buscaArbol(), en cambio, requiere que el parmetro que representan la llave de bsqueda y los parmetros de la funcin empleada para comparar la llave con los nodos sean apuntadores void.

ARBOL.C. Continuacin.
/************************************************************* * HOJA **buscaArbol(ARBOL *pRaiz, void *pLlave, * int (* fcmp)(void *pInfo, void *pLlave)) * * Esta funcin busca en el rbol cuya direccin est * almacenada en la localidad de memoria dada por pRaiz el nodo * cuyo campo de informacin tenga una llave igual a la llave * apuntada por pLlave. La funcin regresa la direccin de la * localidad de memoria donde est almacenada la direccin del * nodo buscado, NULL en caso de no encontrar un nodo. fcmp() * es un apuntador a la funcin utilizada para comparar la * llave con los nodos de la lista. La funcin para comparar es * suministrada por el usuario y es de tipo entero y recibe * como parmetros apuntadores a los datos a comparar: pinfo y * pLlave que corresponden al campo de informacin y a la llave. * La funcin fcmp() debe regresar: * * 0 si campo de informacin == llave * (+) si campo de informacin > llave * (-) si campo de informacin < llave *************************************************************/ HOJA **buscaArbol(ARBOL *pRaiz, void *pLlave, int (* fcmp)(void *pInfo, void *pLlave)) { int rc; HOJA **ppPos = pRaiz; /* Si el rbol no est vaco */ if(*pRaiz) { /* Compara llave con nodo */ rc = fcmp((*pRaiz)->pInfo, pLlave); /* Si no es igual, busca en el subrbol izquierdo */ if(rc > 0) ppPos = buscaArbol(&((*pRaiz)->pIzq), pLlave, fcmp); /* si no est, busca el subrbol derecho */ else if(rc < 0) ppPos = buscaArbol(&((*pRaiz)->pDer), pLlave, fcmp); } return ppPos; }

ITSON

Manuel Domitsu Kono

Captulo 16

Estructuras de Datos en C

465

La funcin creaHoja() tambin se modifica ya que ya que en lugar de recibir un entero, el dato a almacenar en el nodo, recibe por separado la direccin de la localidad donde est el dato y el tamao del dato en los parmetros pDato y tamDato. En el cdigo de la funcin podemos ver que se requiere pedir dos bloques de memoria en forma dinmica: Una para almacenar el nodo y otra para almacenar el dato. Por ltimo la funcin destruyeNodo() requiere liberar los dos bloques de memoria pedidos por la funcin creaNodo().

ARBOL.C. Continuacin.
/************************************************************* * HOJA *creaHoja(void *pDato, int tamDato) * * Esta funcin crea un nodo de un rbol haciendo una peticin * dinmica de memoria e inicializa su campo de informacin * con el valor de info y los apuntadores a los subrboles * izquierdo y derecho con apuntadores nulos. *************************************************************/ HOJA *creaHoja(void *pDato, int tamDato) { HOJA *pHoja; /* Pide un bloque de memoria para el nodo, en forma dinmica */ if(pHoja = malloc(sizeof(HOJA))) { /* Pide un bloque de memoria para el dato, en forma dinmica */ if((pHoja->pInfo = malloc(tamDato))) /* Almacena en el campo de informacin el dato */ memcpy(pHoja->pInfo, pDato, tamDato); else return NULL; /* Almacena en los apuntadores a los subrboles izquierdo y derecho apuntadores nulos. */ pHoja->pIzq = pHoja->pDer = NULL; } return pHoja; } /************************************************************* * void destruyeHoja(HOJA *pHoja) * * Esta funcin libera el bloque de memoria ocupada por el * nodo de un rbol. *************************************************************/ void destruyeHoja(HOJA *pHoja) { free(pHoja->pInfo); free(pHoja); }

El siguiente listado, DEMO_AB.C, es un mdulo que implementa un programa de demostracin que muestra el comportamiento de un rbol binario de bsqueda. Este mdulo debe ligarse a los mdulos: ARBOL y UTILS.

ITSON

Manuel Domitsu Kono

466

Estructuras de Datos en C

DEMO_AB.C.
/************************************************************* * DEMO_AB.C * * Este programa ilustra el comportamiento de un rbol binario * de enteros. *************************************************************/ #include #include #include #include <alloc.h> <stdio.h> <conio.h> <stdlib.h>

#include "utils.h" #include "arbol.h" void despliegaPantallaInicial(void); void borraArbol(void); void despliegaHoja(HOJA *pHoja); int fcmp(void *pInfo, void * pLlave); int x, y; ARBOL raiz = NULL; int main(void) { HOJA *pHoja, **pPos; int dato; char operador, sdato[6]; clrscr(); raiz = inicializarArbol(raiz); despliegaPantallaInicial(); while(1) { gotoxy(28,9); printf(" "); gotoxy(51,9); printf(" "); gotoxy(28,9); if((operador = getch()) == ESC) break; if(!operador) { switch(operador = getch()) { case INS: printf("Insertar"); gotoxy(51,9); gets(sdato); dato = atoi(sdato); pHoja = creaHoja(&dato, sizeof(int)); raiz = insertarArbol(raiz, pHoja, fcmp); break; case DEL: printf("Extraer "); if(arbolVacio(raiz)) msj_error(0,25, "Arbol vaco!, [Enter]", "\r", ATTR(RED, LIGHTGRAY,0)); else { gotoxy(51,9); gets(sdato); dato = atoi(sdato); pPos = buscaArbol(&raiz, &dato, fcmp); if(!(*pPos)) msj_error(0,25, "Valor inexistente!, [Enter]", "\r", ATTR(RED, LIGHTGRAY,0));

ITSON

Manuel Domitsu Kono

Captulo 16

Estructuras de Datos en C

467

else extraerArbol(pPos, &dato, sizeof(int)); } break; } } borraArbol(); x = 28; y = 12; visitarArbolEnOrden(raiz, despliegaHoja); putchar('\n'); } raiz = inicializarArbol(raiz); borraArbol(); x = 28; y = 12; visitarArbolEnOrden(raiz, despliegaHoja); putchar('\n'); return 0; } /************************************************************* * void despliegaPantallaInicial(void) * * Esta funcin despliega la explicacin de lo que hace el * programa y las instrucciones de su uso. *************************************************************/ void despliegaPantallaInicial(void) { clrscr(); gotoxy(5,3); printf("Este programa demuestra el comportamiento de un"); printf(" arbol binario. En este"); gotoxy(5,4); printf("arbol se almacenan enteros. Hay dos operaciones:"); printf(" Para insertar un numero"); gotoxy(5,5); printf("en el arbol presiona la tecla [Insert], luego el "); printf(" numero a insertar."); gotoxy(5,6); printf("Para extraer un numero del arbol presiona la "); printf("tecla [Supr], luego el numero"); gotoxy(5,7); printf("a extraer. Para terminar presiona la tecla [Esc]."); gotoxy(15,9); printf("Operacion [ ] Valor [ ]"); } /************************************************************* * void despliegaHoja(HOJA *pHoja) * * Esta funcin despliega la direccin de un nodo del rbol, * as como el contenido de su campo de informacin y las * direcciones de sus subrboles izquierdo y derecho. *************************************************************/ void despliegaHoja(HOJA *pHoja) { /* Despliega la direccin del nodo y su contenido */ gotoxy(x+1,y-1); printf(" +--------------------+"); gotoxy(x+1,y); printf("%6p \x10%6p%6d%6p", pHoja, pHoja->pIzq, *(int *)(pHoja->pInfo), pHoja->pDer); gotoxy(x+1,y+1); printf(" +--------------------+"); y += 3; /* Si va a desplegar otra pantalla */ if((pHoja->pIzq || pHoja->pDer) && y == 24) { msj_error(0,25,"[Enter] para continuar", "\r", ATTR(RED, LIGHTGRAY,0));

ITSON

Manuel Domitsu Kono

468

Estructuras de Datos en C

x = 28; y = 12; borraArbol(); } } /************************************************************* * void borraArbol(void) * * Borra de la pantalla el rbol *************************************************************/ void borraArbol(void) { int i; for(i = 10; i < 25; i++) { gotoxy(1,i); clreol(); } } /************************************************************* * int fcmp(void *pInfo, void * pLlave) * * Esta funcin regresa: * * 0 si *pInfo == *pLlave * (+) si *pInfo > *pLlave * (-) si *pInfo < *pLlave *************************************************************/ int fcmp(void *pInfo, void * pLlave) { return *(int *)pInfo - *(int *)pLlave; }

Ejercicio Sobre rboles Binarios


Escribe una funcin que nos regrese el nmero de nodos en un rbol binario genrico. La sintaxis de la funcin es int longArbl(Arbol raiz); donde raiz es el apuntador al rbol binario.

Aplicaciones de rboles Binarios


En las secciones anteriores se ha descrito el concepto de rbol binario, particularizando en el de rbol de bsqueda binaria. El uso de rboles binarios para ordenar datos nos permiten realizar bsquedas en forma muy eficiente. Hay que notar que la operacin de buscar implementada por la funcin buscaArbol() no tiene que buscar en todos los nodos del rbol sino slo en el subrbol apropiado: en el izquierdo si la llave del dato a buscar es menor que la raz del rbol o en el derecho si es mayor. Si el rbol binario est balanceado, esto es, todas las hojas se encuentran en el mismo nivel, la eficiencia de est funcin es similar a la de una bsqueda binaria, estudiada en el captulo anterior. Sin embargo esa

ITSON

Manuel Domitsu Kono

Captulo 16

Estructuras de Datos en C

469

eficiencia disminuye conforme el rbol se desbalancea y en el peor de los casos degenera en una lista ligada, con lo que la eficiencia se reduce a la de una bsqueda lineal. Aparte de la aplicacin de rboles de bsqueda binarios, los rboles binarios tienen un gran nmero de aplicaciones. En [Tenenbaum, 1993] se desarrollan otras aplicaciones como evaluadores de expresiones, rboles de decisiones para teora de juegos, codificacin y compresin de datos.

Bibliografa
1. Dale, Nell y Lilly, Susan C., Pascal y Estructuras de Datos, Segunda edicin, McGraw-Hill, Madrid, 1989. 2. Esakov, J effrey, Weiss, Tom, Data Structures: An Advanced Approach Using C, Prentice Hall, Inc., New Jersey, 1989. 3. Kernighan, Brian W. y Ritchie, Dennis M., El Lenguaje de Programacin C, Segunda Edicin, Prentice Hall, Mxico, 1991. 4. Kruse, Robert L., Leung, Bruce P., Tondo, Clovis L., Data Structures and Program Design in C, Prentice Hall, Inc., New Jersey, 1991. 5. Schildt, Herbert, Turbo C. Programacin Avanzada, Segunda edicin, McGraw-Hill, Madrid, 1990. 6. Tenenbaum, Aaron M., Langsam Yedidyah, Augenstein Moshe A., Estructuras de Datos en C, Prentice Hall Hispanoamercana S. A., Mxico, 1993. 7. Weiss, Mark A., Data Structures and Algorithm Analysis, The Benjamin/Cummings Publishing Company, Inc., Redwood City, California, 1992.

Problemas
1. Implementar una calculadora RPN, esto es, que evale expresiones en notacin sufija, utilizando una pila generalizada para almacenar los datos. El tamao de la pila es de cuatro. En las calculadoras HP, podemos visualizar el contenido de cada uno de los elementos del arreglo que constituyen la pila mediante cuatro "ventanas" llamadas registros. Esos registros tienen los nombres: X, Y, Z y T. Si slo hay un dato almacenado en la pila, su valor se muestra en el registro X. Si hay dos datos almacenados en la pila, el dato que se almacen primero se muestra en el registro Y y el ltimo en el registro X. Si hay tres datos almacenados en la pila, el dato que se almacen primero se muestra en el registro Z, el segundo en el registro Y y el ltimo en el registro X, etc. La calculadora deber mostrar tanto el contenido de los registros como el resultado, tal como se muestra en la figura 16-21.
ITSON Manuel Domitsu Kono

470

Estructuras de Datos en C

Figura 16-21 La ventana Resultado (R:) despliega el resultado de las operaciones que es el valor almacenado en el registro X. Tambin es en esta ventana donde se leen los nmeros. Las operaciones a implementar se muestran en la figura 16-21. 2. Se desea simular el comportamiento de un aeropuerto pequeo que slo tiene una pista. En cada unidad de tiempo un avin puede aterrizar o despegar pero no puede haber un aterrizaje y un despegue en la misma unidad de tiempo. Los aviones arriban para aterrizar o despegar en tiempos aleatorios. De tal manera que en cualquier unidad de tiempo la pista puede estar ociosa, un avin puede estar despegando, un avin puede estar aterrizando y puede haber varios aviones en espera de aterrizar o despegar. Este programa utiliza dos colas una para los aviones a aterrizar y otra para los aviones a despegar. Es preferible mantener esperando a un avin en tierra que en el aire, as que slo se permite que un avin despegue si no hay aviones esperando aterrizar. Cada avin se representa mediante una estructura con la siguiente informacin
typedef struct { int identAvion; int tArribo; } AVION;

/* Nmero de identificacin del avin */ /* Tiempo de arribo del avin a la cola */

El pseudocdigo del programa se muestra a continuacin


int main(void) { nt tActual; int tFinal;

/* Unidad de tiempo actual de la simulacin */ /* Nmero de unidades de tiempo que dura la simulacin */

ITSON

Manuel Domitsu Kono

Captulo 16

Estructuras de Datos en C

471

int i, nAviones; AVION avion /* Estructura con los datos de un avin */ leeDatos(&tFinal, &vEspAvionesAterr, &vEspAvionesDesp); inicializarCola(&colaAterrizar); inicializarCola(&colaDespegar); for(tActual = 1; tActual <= tFinal; tActual++) { despliegaTiempoActual(); /* Llegada de nuevos aviones para aterrizar */ nAviones = generaAviones(vEspAvionesAterr); for(i = 1; i <= nAviones; i++) { creaAvion(&avion); if(!insertaCola(&colaAterrizar, &avion)) rechazaAvion(&avion); } /* Llegada de nuevos aviones para despegar */ nAviones = generaAviones(vEspAvionesDesp); for(i = 1; i <= nAviones; i++) { creaAvion(&avion); if(!insertaCola(&coladespegar, &avion)) rechazaAvion(&avion); } /* Aterriza avin */ if(extraeCola(&colaAterrizar, &avion)) aterrizaAvion(&avion); /* Despega avin */ else if(extraeCola(&coladespegar, &avion)) despegaAvion(&avion); /* Pista ociosa */ else pistaOciosa(); } reporte(); }

La funcin leeDatos() despliega las instrucciones del programa y lee los siguientes datos a) Tiempo de corrida de la simulacin en unidades de tiempo. b) Nmero esperado de aviones que desean aterrizar por unidad de tiempo. c) Nmero esperado de aviones que desean despegar por unidad de tiempo. Los nmeros esperados de aviones deben ser nmeros dobles positivos y su suma no debe exceder a 1.0 de otra manera el aeropuerto se saturar. La funcin despliegaTiempoActual() despliega el tiempo actual de la simulacin. La funcin generaAviones() es una funcin que genera un nmero entero pseudoaleatorio con una distribucin de probabilidad de Poisson. este nmero representa el nmero de aviones que desean aterrizar/despegar por unidad de tiempo. La funcin recibe como parmetro un doble que representa el nmero esperado de aviones que desean aterrizar por unidad de tiempo. El cdigo de esta funcin es:

ITSON

Manuel Domitsu Kono

472

Estructuras de Datos en C

int generaAviones(double vEsperadoAviones) { int n = 0; /* Nmero de aviones */ double em, x; em = exp(- vEsperadoAviones); x = (double)rand()/MAX INT; while(x > em) { n++; x *= (double)rand()/MAX INT; } return n; }

La funcin creaAvion() inicializa la estructura que representa el avin y despliega el mensaje que el avin se encuentra listo para aterrizar/despegar. La funcin rechazaAvion() despliega el mensaje de que el avin ha sido redirigido a otro aeropuerto en caso de querer aterrizar o que se le ha pedido que lo intente ms tarde en caso de querer despegar. Tambin incrementa el contador de nmero de aviones rechazados. La funcin aterrizaAvion() despliega el mensaje de que el avin ha aterrizado y el tiempo que dur en la cola. Tambin incrementa el contador de nmero de aviones que han aterrizado y el tiempo de espera acumulado por todos los aviones que aterrizaron. La funcin despegaAvion() despliega el mensaje de que el avin ha despegado y el tiempo que dur en la cola. Tambin incrementa el contador de nmero de aviones que han despegado y el tiempo de espera acumulado por todos los aviones que despegaron. La funcin pistaOciosa() despliega el mensaje de que la pista est ociosa e incrementa el contador de tiempo ocioso de la pista. La funcin reporte() despliega al final de la simulacin un reporte con la siguiente informacin: a) b) c) d) e) f) g) h) i) j) Tiempo de simulacin Total de aviones creados Nmero de aviones que aterrizaron Nmero de aviones que despegaron Nmero de aviones rechazados Nmero de aviones que quedaron sin aterrizar Nmero de aviones que quedaron sin despegar Porcentaje de tiempo ocioso de la pista Tiempo promedio de espera para aterrizar Tiempo promedio de espera para despegar

3. Se desea una funcin que genere una lista ligada a partir del archivo de entradas o salidas del sistema de inventarios del departamento de laboratorios del ITSON. La lista est ordenada en orden ascendente y las llaves de ordenamiento son los miembros de la estructura ENTSAL: clave, campus y almacn. Los datos se almacenan en una estructura, de la siguiente manera:

ITSON

Manuel Domitsu Kono

Captulo 16

Estructuras de Datos en C

473

typedef struct { char clave[8]; char campus[2]; char almacen[2]; int reg; } CILES;

/* /* /* /* /* /*

1a Llave de ordenamiento 2a Llave de ordenamiento 3a Llave de ordenamiento Nmero del registro en el archivo ENTSAL donde se encuentran las llaves

*/ */ */ */ */ */

El prototipo de la funcin es: LISTA generaLista(char es); La funcin regresa un apuntador a la lista ligada. El parmetro es es un carcter que indica si se va a generar una lista de entradas, 'E' o salidas, 'S'. Si hay xito, la funcin regresa un apuntador al inicio de la lista, NULL en caso contrario. Implementa esta funcin en un mdulo llamado LISTA_ES . Para probar la funcin anterior haz un programa que despliegue la lista ligada generada por la funcin generaLista(). Llama a este programa DEMO_LES . El programa deber preguntar si se desea la lista ligada del archivo de entradas o salidas. 4. Se desea una funcin que genere un rbol binario de bsqueda a partir del archivo de inventarios del sistema de inventarios del departamento de laboratorios del ITSON. La informacin del archivo de inventarios est estructurada de la misma forma que los archivos de entradas y salidas. El archivo de inventarios se llama INVENTAR.DAT. Las llaves de ordenamiento son las mismas que las de las listas ligadas del problema 3. Los datos para almacenar en el rbol estn en una estructura del tipo CILES, tambin descrita en el problema 3. El prototipo de la funcin es: ARBOL generaArbol(void); La funcin regresa un apuntador al rbol binario. Si hay xito, la funcin regresa un apuntador al inicio del rbol, NULL en caso contrario. Implementa esta funcin en un mdulo llamado ARBOLINV. Para probar la funcin anterior haz un programa que despliegue la lista ligada generada por la funcin generaArbol(). Llama a este programa DEMO_AIN . 5. Se desea un incluir en el Sistema de Inventarios de los Laboratorios del ITSON un mdulo que permita actualizar el inventario de acuerdo a las entradas y salidas ocurridas. Las entradas incrementarn el inventario y las salidas lo decrementarn. El archivo que contiene el inventario se llamar INVENTAR.DAT y las rutinas se implementan en el mdulo INVENTAR.H. El programa entrar a este mdulo cuando el usuario seleccione la opcin Inventario , del men principal, el cual se muestra en la figura 16-22:

ITSON

Manuel Domitsu Kono

474

Estructuras de Datos en C

Figura 16-22 Al seleccionar la opcin Inventario , el programa desplegar el men de la figura 16-23:

Figura 16-23 La opcin Actualizar actualiza el inventario con las entradas y salidas. Al seleccionar esta opcin, el programa desplegar el men de la figura 16-24:

ITSON

Manuel Domitsu Kono

Captulo 16

Estructuras de Datos en C

475

Figura 16-24 Este men permite seleccionar si se va a actualizar el inventario con la lista de entradas o con la lista de salidas. En estas opciones, se genera una lista ligada con la informacin contenida ya sea en el archivo ENTRADAS.DAT o SALIDAS.DAT para que queden ordenadas por clave, campus y almacn, adems se genera un rbol binario con la informacin del archivo INVENTAR.DAT para que tambin quede ordenado por las mismas llaves y luego se actualiza el inventario. Esta actualizacin puede hacerse mediante una funcin llamada actualizaInventario() cuyo pseudocdigo tiene la forma:
void actualizaInventario() { Abre el archivo de claves Abre el archivo de entradas o salidas Genera la lista ligada de entradas o salidas Abre el archivo de inventarios Genera el rbol binario de bsqueda a partir del inventario Mientras haya nodos en la lista, extrae el primero { Busca el registro correspondiente al nodo en el archivo de entradas o salidas Lee el registro del archivo de entradas o salidas Busca el registro correspondiente a la clave de la entrada o salida del archivo de claves Busca el nodo del rbol que tenga la misma llave que el nodo extrado de la lista Si lo hall { Extrae el campo de informacin del nodo sin

ITSON

Manuel Domitsu Kono

476

Estructuras de Datos en C

extraer el nodo del rbol Busca el registro correspondiente al nodo del rbol en el archivo de inventarios Lee el registro del archivo de inventarios Si es entrada se le suma al inventario en caso contrario si es salida se le resta Posicinate en el archivo de inventarios para reescribir el registro modificado Reescribe el registro modificado } en caso contrario y si se trata de una entrada { Posicionate al final del archivo de inventarios Escribe el registro de entsal en el inventario Escribe el numero del registro escrito en el archivo de inventarios en el campo de informacin de un nuevo nodo del rbol Crea ese nuevo nodo del rbol Insrtalo en el rbol Incrementa el contador de registros del archivo de inventarios } } }

La opcin Iniciar borra el contenido del archivo INVENTAR.DAT. La opcin Listar lista el inventario ya sea por pantalla o por impresora. En esta opcin, la informacin contenida en el archivo INVENTAR.DAT es leda en un rbol binario para que quede ordenada por clave, campus y almacen y luego se lista. Al entrar a la opcin de Listar, el programa deber preguntar si el listado se desea en pantalla o en impresora. El listado por pantalla tendr la forma mostrada en la figura 16-25. La lista deber aparecer pgina a pgina, haciendo una pausa entre ellas y desplegando el mensaje: "Presione [Enter] para continuar".

ITSON

Manuel Domitsu Kono

Captulo 16

Estructuras de Datos en C

477

Figura 16-25 El listado por impresora deber tener un formato similar al de la figura 16-26:
dd/mm/aa INSTITUTO TECNOLGICO DE SONORA DEPARTAMENTO DE LABORATORIOS SISTEMA DE INVENTARIOS DE MATERIALES Y REACTIVOS Reg 1 2 3 Clave MTU0001 SCA0001 SCA0001 LISTA DE ENTRADAS Nombre Campus Tubo de ensayo 13 x 100 A Carbon Vegetal A Carbon Vegetal A Alm Cantidad B 200 A 125.5000 B 12.5000 Uds Pz Gr Gr Pag xx

Figura 16-26 En cada pgina el programa deber imprimir el encabezado mostrado, seguido de como mximo 50 registros. a continuacin har salto de pgina. El programa fuente deber estar formado por catorce mdulos: Los mdulos UTILS , MENU, UTCLAVES, UENTSALS tal como se desarrollaron en el Captulo 13. El mdulo VERIMP contendr la funcin verImpresora() desarrollada en el Captulo 13.

ITSON

Manuel Domitsu Kono

478

Estructuras de Datos en C

Los mdulos UTILARCH, CLAVES y ENTSALS tal como se desarrollaron en el Captulo 14. El mdulo LISTA que implementa las operaciones de una lista ligada generalizada. El mdulo LISTA_ES que implementa la funcin generaLista() del problema 3. El mdulo ARBOL que implementa la operaciones de un rbol binario generalizado. El mdulo ARBOLINV que implementa las funcin generaArbol() del problema 4. El mdulo INVENTAR que implementa las funciones que ejecutan las opciones del men de inventarios: Actualizar inventario, Iniciar inventario y Listar inventario. El ltimo mdulo llamado INV4 tendr a la funcin main() y slo se encargar de presentar el men principal y llamar a las funciones opcionClaves(), opcionES() y opcionInv() que estn en los mdulos CLAVES, ENTSALS e INVENTAR , respectivamente. Cada uno de los mdulos debe contar, si as lo requiere de archivo de encabezados y archivo con las definiciones de las funciones. El programa ejecutable deber llamarse INV4.EXE.

ITSON

Manuel Domitsu Kono