capítulo 15 – estructuras de datos en c

Upload: julio-amaiquema

Post on 22-Jul-2015

171 views

Category:

Documents


0 download

TRANSCRIPT

CAPTULO 16ESTRUCTURAS DE DATOS EN CLos 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.

PilasUna 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 PilasLas 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 CA 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 esint 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 esint 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 CLas 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 voidPara 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 unoint *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;

opu = (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 #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 #include #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 Pilas1. 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 PilasLas 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

Instruccionesint main(void) { int a, b;

Estado de la pilaFigura 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)

ColasColas linealesUna 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

1

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

4

(h)

Figura 16-3.

Operaciones Primitivas de las ColasLas 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 CA 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 colatypedef 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 esint 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 esint 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 CircularesLa 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 CLa 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 CAl 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 #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 #include #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 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 ColasEscribe 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 ColasLas 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 MemoriaCuando 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 MontculoLa 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 MontculoLa 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 #include 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 *(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 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 3) { puts("\nUso: dirsort [] [/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 archivoValor 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 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 %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 FuncionesModifique 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 LigadasLas 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 ListasLas 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 CLa 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 continuacinvoid 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 CAl 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 #include #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;