Sunteți pe pagina 1din 27

Antecedentes

Apuntadores

En el lenguaje C, un apuntador se define como una variable que contiene como valor una direccin
de memoria. Cuando una variable contiene un valor especfico, se dice que el nombre de la
variable se refiere directamente al valor. Mientras tanto, si un apuntador contiene la direccin de
una variable que contiene un valor especfico, se dice que el apuntador se refiere indirectamente
al valor. Para declarar apuntadores se utiliza la siguiente sintaxis:

Tipo de dato (nombre de apuntador) *

Se pueden crear apuntadores a cualquier tipo de dato, incluso los definidos por el usuario (como
se mencionar ms adelante). Es una buena costumbre siempre inicializar los apuntadores al valor
nulo (NULL) como se muestra a continuacin:

int ap*=NULL;

Donde ap es un apuntador a entero que estamos inicializando en el valor nulo. Existen dos
operadores que son frecuentemente usados al trabajar con apuntadores:

&.-Operador de direccin: Regresa la direccin de un operando.


*.-Operador de in-direccin o des-referencia: Regresa el valor del objeto hacia el cual su
operando apunta.

Si se trabaja con un arreglo de datos, todos los elementos de un arreglo son guardados
secuencialmente en la memoria. Por lo tanto, si se trabajar con apuntadores, se pueden utilizar
operaciones aritmticas para recorrer los arreglos. Esto se hace con la diferencia de que si a un
apuntador se le suma o resta una cantidad, esa cantidad se multiplica por el tamao del tipo de
dato al que apunta el apuntador. Por ejemplo, si ap es un apuntador a entero (que digamos que
ocupa 4 bytes en memoria) y est apuntando a la direccin 102, si se realiza la instruccin

Ap+=2;

Ap ahora tendr el valor de 102+4*2=110. Esto hace que si se utilizan apuntadores para recorrer
arreglos, el recorrerlos sea mucho menos tedioso, ya que no se debe tomar en cuenta el tamao
del tipo de dato para recorrerlo.
Manejo de archivos

Existen diversas formas de manipular archivos en C, pero la forma ms sencilla (abriendo el


archivo de forma secuencial) se hace, a grandes rasgos, de la siguiente manera:

1) Se importa la biblioteca de entrada y salida estndar (stdio.h).


2) Se crea un apuntador del tipo FILE * y se iguala este apuntador al valor de retorno de la
funcin fopen, cuya firma es FILE * fopen(const char * nombre, const char * modo). Como
nombre se pone la ruta (relativo o absoluta) del archivo que se desea abrir y en modo se
especifica cmo se desea abrir el archivo. Existen tres modos bsicos:
a) r: Slo lectura
b) w: Slo escritura (sobre-escribiendo la informacin ya existente)
c) a: Slo escritura agregando contenido al contenido ya existente
3) Se lee o escribe la informacin deseada a o desde el archivo (se sugieren las funciones fprintf y
fscanf para esto)
4) Se cierra el archivo mediante la funcin fclose, especificndole el nombre del apuntador de
tipo FILE *

Ejemplo:

#include <stdio.h>

Int main(void)

FILE * archivo;

archivo=fopen(bitacora.txt,a);

fprintf(archive,Se ha ejecutado la funcion main);

fclose(archivo);

return 0;

En el ejemplo, se le agrega al archivo bitcora.txt la lnea Se ha ejecutado la funcin main, y si


no existe, se crea este archivo y se escribe la lnea en l.
Estructuras

Las estructuras de datos en C son colecciones de variables relacionadas bajo un mismo nombre.
Estas variables pueden ser de muchos tipos diferentes de datos. Se les considera tipos de datos
derivados porque estn hechas de otros objetos de diferentes tipos. Se definen de la siguiente
manera:

struct nombre{

tipo1 (nombre1);

tipo2 (nombre2);

tipo3 (nombre3);

};

Es importante notar que una estructura no se puede contener a si misma (por ejemplo una
estructura casa no puede contener un tipo de dato casa), pero una estructura s puede contener
un apuntador a su mismo tipo (como una persona que tiene como datos una edad, un nombre y
un apuntador a otra persona). A estas ltimas se les conoce como estructuras auto-referenciadas y
sern muy utilizadas en este curso.

Ejemplo:

struct persona{

int edad;

char nombre[10];

char apellido[20];

};

Crea una estructura de tipo persona con tres variables: una edad, un nombre y un apellido. Para
declarar una variable del tipo de una estructura, se puede declarar de dos maneras:

struct (tipo de la estructura) (nombre de la variable);


En cuyo caso todos sus valores no estaran inicializados o se puede declarar mediante una lista de
inicializacin. En el caso de nuestra estructura persona se hara de la siguiente manera:

struct persona juan={23, Juan, Lara};

y as se creara una persona de nombre Juan, con 23 aos de edad y apellido Lara. Para acceder a
los miembros de una estructura existen dos operadores a nuestra disposicin:

1) El operador de miembro de estructura, u operador punto ( . ): Accede a un miembro de la


estructura mediante su nombre.
2) El operador de apuntador de estructura, u operador flecha (->): Accede a un miembro de la
estructura por medio de un apuntador a la estructura.

Existe un operador muy til, especialmente al manejar estructuras, que se llama el operador
typedef. El operador typedef se utiliza para definir un tipo de datos, y en vez de estar escribiendo
struct persona cada vez que se quiera hacer referencia a la estructura persona, se pueda acceder a
ella creando un nuevo tipo de datos que simplemente se llame persona. Se coloca el operador
typedef antes de la palabra struct y se pone justo antes del punto y coma (;) el nombre con el cul
se har referencia al nuevo tipo de dato.

Ejemplo:

typedef struct perro{

char [10] nombre;

int edad;

struct perro *madre;

} perro;

Si definimos de esta manera a la estructura perro, podramos declarar nuevas variables de ese tipo
simplemente escribiendo:

perro Rex;

en vez de

struct perro Rex:

Si creramos una variable perro:

perro Dolly {Dolly,10,NULL};

y despus otra

perro Rex {Rex,3,&Dolly};


Indicaramos dentro de la variable Rex que la variable Dolly es su madre. Al ingresar

Rex.nombre

nos devolvera Rex, el valor de la variable nombre de nuestra estructura. Si en cambio,


ingresramos

(Rex.madre)->nombre

nos devolvera Dolly, el valor de la variable nombre dentro de la estructura a la que hace
referencia el apuntador madre de la variable Rex.
Listas ligadas
Introduccin

Aun cuando los arreglos son estructuras muy tiles dentro de los diversos lenguajes de
programacin (no slo C), estos tienen ciertas limitaciones:

Para cambiar el tamao del arreglo, se debe crear un nuevo arreglo y copiar todo el
contenido del arreglo previo al nuevo arreglo.
Los datos en los arreglos estn ordenados secuencialmente en memoria, por lo que si se
desea insertar un elemento antes de otro, se requiere mover ste y todos los que estn
despus para poder colocar el nuevo elemento.

Estas limitaciones se pueden superar mediante el uso de listas ligadas. Una lista ligada es una
coleccin de nodos que almacenan datos y ligas a otros nodos. Estos se pueden colocar en
cualquier parte de la memoria y el paso de un nodo a otro se hace mediante las referencias que se
guardan dentro de estos.

Listas simplemente ligadas

Las listas simplemente ligadas son la versin ms sencilla de las listas. Tienen una estructura lineal
y cada uno de los nodos contiene solamente una referencia a otro nodo, el nodo que le sigue en la
lista. Por lo tanto, uno puede tener acceso a cualquier nodo de la lista si se tiene acceso al primer
nodo de la lista. Tomando eso en cuenta, es muy til tener un apuntador que hace referencia al
primer nodo de la lista. Para indicar que un nodo es el ltimo de la lista, se pone un valor nulo en
su referencia al siguiente nodo y, para algunas operaciones, es til tambin tener otro apuntador
al final de la lista.

Ejemplo:

typedef struct nodo{

int valor;

struct nodo* siguiente;

}nodo;

Nos define una estructura de nodos donde cada nodo guardar un valor del tipo entero y adems
una referencia al siguiente nodo en la lista.
inicio

int main (void){


3
nodo *inicio=NULL;

nodo *fin=NULL;

inicio=(nodo *)malloc(sizeof(nodo));

inicio->valor=3;

inicio->siguiente=NULL;

inicio->siguiente=malloc(sizeof(nodo));
\ (NULL)
(inicio->siguiente)->valor=5;

(inicio->siguiente)->siguiente=NULL;
5
fin=inicio->siguiente;

fin

En este ejemplo se crea una lista de dos nodos, con valores 3 y 5. El nodo con valor 3 es el inicio de
la lista (ya que el apuntador inicio hace referencia a ste) y el nodo con valor 5 es el final de la lista
(ya que su apuntador al nodo siguiente es igual a nulo).

Las operaciones bsicas dentro de las listas ligadas son las siguientes:

Insercin de un nodo
Despliegue de la informacin en la lista
Remocin de un nodo
Bsqueda/Modificacin de un nodo

Estas operaciones se vern dentro de las listas doblemente ligadas debido a que algunas son ms
fciles de implementar en stas.

Listas doblemente ligadas

Las listas doblemente ligadas funcionan de la misma manera que las simplemente ligadas con una
peculiaridad: adems de tener un apuntador al siguiente nodo en la lista, contienen un apuntador
al nodo anterior de la lista. Se puede ver a las listas simplemente ligadas como listas de un
sentido y a las doblemente ligadas como listas de doble sentido.
Se puede definir la estructura de la siguiente manera:

typedef struct nodo{

int valor;

struct nodo* siguiente;

struct nodo* anterior;

}nodo;

Insercin de nodos

Dentro de las operaciones de insercin sobre la lista, la insercin al final es la ms sencilla. Uno
simplemente asigna el nodo nuevo como el nodo siguiente del ltimo nodo y se cambia el
apuntador del fin de la lista al nuevo nodo. Si la lista est vaca (algo muy importante que se debe
verificar), se crea un nuevo nodo que ser tanto el final como el principio de la lista.

Ejemplo:

void insertarAlFinal(int valor,nodo** inicio,nodo** final)

if (*inicio==NULL)

*inicio=(nodo *)malloc(sizeof(nodo));

(*inicio)->valor=valor;

(*inicio)->siguiente=NULL;

(*inicio)->anterior=NULL;

*final=*inicio;

else{

(*final)->siguiente=(nodo *)malloc(sizeof(nodo));

((*final)->siguiente)->valor=valor;

((*final)->siguiente)->siguiente=NULL;
((*final)->siguiente)->anterior=*final;

*final=(*final)->siguiente;

La insercin al principio de la lista es muy similar y se deja como ejercicio al lector.

La insercin en alguna posicin especfica es un poco ms complicada, puesto que se deben de


cambiar los apuntadores de anterior y siguiente de los nodos entre los cuales se va insertar el
nuevo nodo. Se crea el nuevo nodo y se mueve el apuntador de siguiente del nodo anterior al
nuevo nodo al igual que el apuntador de anterior del nodo siguiente al nuevo nodo.
Posteriormente se colocan los apuntadores de anterior y siguiente del nuevo nodo a los dos nodos
anteriores.

Nota: es muy importante el comprobar que la posicin en la que se quiere insertar el nuevo nodo
en la lista exista.

Ejemplo:

void insertarEnPosicion(int valor,int posicion,nodo** inicio,nodo** final)

nodo *temporal=NULL;

nodo *apu=*inicio;

int i=0;

while (i!=posicion&&apu!=NULL)

apu=apu->siguiente;

i++;

if (i==posicion)

temporal=(nodo *)malloc(sizeof(nodo));

temporal->valor=valor;
temporal->siguiente=apu->siguiente;

temporal->anterior=apu;

apu->siguiente->anterior=temporal;

apu->siguiente=temporal;

En este ejemplo, la funcin inserta el nodo despus de la posicin indicada. Se pueden hacer
ligeras modificaciones para insertar el nodo antes de la posicin indicada.

Despliegue de la informacin en la lista

El despliegue de la informacin en la lista se puede hacer de manera muy sencilla utilizando un


apuntador temporal. Se iguala ste al inicio de la lista y mientras no llegue al final, se imprime el
valor del nodo al que hace referencia el apuntador temporal y despus se avanza al siguiente nodo
hasta que se llegue al final de la lista.

Ejemplo:

apu=inicio;

while (apu!=NULL)

printf("%d\n",(*apu).valor);

apu=apu->siguiente;

En las listas doblemente ligadas, se puede mostrar la informacin hacia atrs de la siguiente
manera:

apu=final;

while (apu!=NULL) {

printf("%d\n",(*apu).valor);

apu=apu->anterior;

}
Remocin/Bsqueda de un nodo de la lista

La operacin ms fcil de remocin sobre la lista es remover el ltimo o el primer nodo. Como en
la insercin se trabaj con el final de la lista, ahora se ver cmo remover el primer nodo de la
lista. Remover el primer nodo de la lista es sencillo, lo nico que se debe de hacer es hacer que el
apuntador anterior de segundo nodo de la lista haga referencia al valor nulo, cambiar el apuntador
inicio al segundo nodo de la lista y finalmente liberar la memoria que ocupaba el nodo removido.

Nota: el liberar la memoria del nodo eliminado es esencial, ya que al ocupar memoria con la
funcin malloc, uno debe de liberarla cuando termine de utilizarla. El no hacerlo puede causar
inestabilidad y errores en los programas. Adems, es buena prctica de programacin el quitar
todas las referencias (igualar stas a nulo) que apunten a un rea de memoria antes de liberarla.

Ejemplo:

void removerAlPrincipio(nodo **inicio, nodo **final) {

nodo *apu=NULL;

apu=*inicio;

(*inicio)->siguiente->anterior=NULL;

(*inicio)=(*inicio)->siguiente;

free(apu);

Para remover un nodo ubicado en alguna posicin especfica de la lista, lo primero que se debe de
hacer es tratar con cuidado los casos en los que el nodo a remover sea el primero o el ltimo de la
lista. Despus, se cambia el apuntador anterior del nodo que le sigue al nodo a remover al nodo
anterior al nodo a remover y similarmente el apuntador siguiente del nodo que precede al nodo a
remover se cambia a el nodo que le sigue al nodo a remover.

void removerEnMedio(int posicion,nodo **inicio, nodo **final) {

int i=0;

nodo *apu=NULL;

apu=*inicio;

while (i!=posicion&&apu!=NULL)
{

apu=apu->siguiente;

i++;

if (i==posicion)

if ((apu->anterior)!=NULL)

(apu->siguiente)->anterior=apu->anterior;

if ((apu->siguiente)!=NULL)

(apu->anterior)->siguiente=apu->siguiente;

free(apu);

Bsqueda de un valor

Para buscar un valor especfico en la lista, se puede realizar un procedimiento muy parecido al
despliegue de todos los elementos de la lista, a excepcin de que uno se detiene en el momento
en el que llega al valor deseado (o llega al final de la lista si el valor no est presente en ella).

Ejemplo:

nodo * buscarValor(int valor, nodo **inicio,nodo **final)

int i=0;

nodo *apu=NULL;

apu=*inicio;

while (apu!=NULL&&(apu->valor)!=valor)

apu=apu->siguiente;
i++;

if (apu!=NULL)

printf("El valor %d esta en la posicion %d de la lista\n",valor,i);

return apu;

printf("El valor %d no esta en la lista\n",valor);

return NULL;

Para la modificacin de un nodo se puede utilizar esta misma funcin, cambiando el valor del nodo
encontrado en vez de regresarlo e imprimir su posicin.

Listas circulares

Una lista circular es idntica a una lista normal (sea simplemente o doblemente ligada) a
excepcin de que el apuntador siguiente del ltimo nodo apunta al primer nodo (y en las
doblemente ligadas, el apuntador anterior del primer nodo apunta al ltimo nodo). Las listas
circulares facilitan algunas operaciones en las listas, y slo se necesita un apuntador de inicio (ya
que el inicio realmente es el final de la lista tambin).
3. Colas
3.1. Teora de colas
3.2. Operaciones sobre una cola
3.3. Caso de estudio: Simulacin de un restaurante

3.1 Teora de colas

Una cola es una estructura de datos en la cual la informacin entra al final de una lista y es
removida del principio de la misma. Las colas son usadas para guardar elementos en el orden en el
que fueron ocurriendo. Las colas son un ejemplo de las estructuras First-In, First-Out, primero que
entra, primero que sale (FIFO, por sus siglas en ingls). Esto significa que el primer elemento
colocado en la cola es el primero en ser removido de ella. En la vida prctica, las colas son usadas
para ordenar los procesos a ejecutarse en un sistema operativo o para decidir el orden en el que
diversos archivos sern atendidos en una impresora, e incluso para crear aplicaciones que simulen
el modelo de personas esperando a ser atendidas.

Un claro ejemplo de las colas en la vida diaria, puede ser un restaurante universitario.
Supongamos que un estudiante imaginario llamado Sergio llega a la barra del restaurante y ordena
lo que desea comer. El encargado de la barra introduce su pedido y le entrega un ticket con un
nmero que indica una posicin en una lista de espera, esta lista de espera es una cola. En esta
cola, todas las rdenes esperan su turno para que el cocinero las pueda atender, de una en una y
conforme fueron llegando; esto quiere decir que el estudiante que lleg antes que Sergio, tendr
su comida antes (el primero que entra, el primero que sale), y el estudiante que lleg despus
deber esperar un turno ms para poder disfrutar de su comida. Este es el funcionamiento bsico
de una cola.

En la figura 1 de la imagen anterior se puede observar una cola con algunos elementos ya
formados. En la figura 2 se observa la misma cola despus de haber colocado (formado) los
elementos 8, 9 y 2. En la figura 3 de muestra la cola despus de haber removido (atendido) a los
elementos 5 y 3.

La forma ms sencilla de implementar una cola, consiste en almacenar sus elementos dentro de
un arreglo, colocando el primer elemento de la cola en la primera posicin del arreglo, es decir, en
el ndice cero. Si fin representa la posicin del ltimo elemento de la cola, entonces para insertar
un elemento no tenemos ms que incrementar fin, insertando el elemento en esa posicin. Sin
embargo, la operacin quitar es muy costosa debido a la exigencia de posicionar los elementos de
la cola desde el principio del arreglo, con lo que se fuerza a la rutina quitar a desplazar una
posicin todos los elementos del vector, una vez eliminado el primero.

Este problema se resuelve incrementando el apuntador inicio cuando se realiza una operacin
quitar, en lugar de desplazar todos los elementos. Entonces cuando la cola tiene un nico
elemento fin e inicio representan la misma posicin del elemento en dicho arreglo.
Consistentemente, en una cola vaca, fin debe inicializarse a inicio 1.

La implementacin anterior hace que tanto insertar como quitar se ejecuten en tiempo
constante. El principal problema que representa esta codificacin es que si consideramos un
arreglo de 5 elementos, despus de tres operaciones quitar no se podran aadir ms elementos,
aunque la cola (arreglo) no est realmente llena.

fin

inicio

fin

a
inicio

fin

a b
inicio

fin

b
inicio

fin

inicio

Implementacin de las colas basada en un arreglo.


fin

c d e
inicio

Ya no hay ms lugar para insertar otro elemento.

Tal como se muestra en la figura anterior, a pesar de que ya no es posible insertar ms elementos
en la cola, el arreglo an tiene espacio, todas las posiciones anteriores a inicio estn vacas, por lo
que se podran reciclar. Con tal objetivo haremos uso de la circularidad: cuando inicio o fin rebasan
la ltima posicin del arreglo, se reposicionan al principio del mismo. Esta implementacin recibe
el nombre de implementacin circular.

fin

c d e
inicio

fin

f c d e
inicio

fin

f e
inicio

fin

f
inicio

Implementacin de las colas empleando la circularidad.

3.2 Operaciones sobre una cola

Son cinco las operaciones necesarias para manejar adecuadamente una cola, las cuales se listan a
continuacin, seguidas de una breve descripcin:

Vaciar la cola (clear) Como su nombre lo indica, esta operacin se encarga de remover
todos los elementos de la cola.
Contar elementos (count) Con esta operacin podemos saber cuntos elementos hay en
la cola con lo que se puede determinar si est o no vaca.

Insertar elementos (enqueue) Esta operacin es la encargada de insertar cada nuevo


elemento al final de la cola.

Quitar elementos (dequeue) Con esta operacin se toma y se elimina al primer elemento
de la cola al mismo tiempo.

Obtener primer elemento (peek) Esta operacin se encarga de obtener el primer


elemento de la cola sin eliminarlo de la misma.

Con base en lo anterior, a continuacin se muestra el cdigo de la implementacin de una cola


circular basada en un arreglo de 5 elementos. Este cdigo est dividido en tres mdulos (archivos).
El primero de ellos se llama Cola.h y contiene las declaraciones de las funciones para una cola. Un
segundo archivo es llamado Cola.c y en l podemos encontrar las definiciones de las funciones
que implementa una cola. Por ltimo, en el tercer archivo, main.c, se implementan las funciones
definidas en los dos archivos anteriores a fin de ver la funcionalidad de esta estructura de datos.

Cola.h
/*Cola.h archivo de encabezado para Cola.c*/

/*********************************FUNCIONES************************************
void clear() ---> Elimina todos los elementos
void enqueue(x) ---> Inserta un elemento
char dequeue() ---> Quita y devuelve el elemento situado al inicio de la cola
char peek() ---> Devuelve el elemento situado al principio, sin quitarlo
int count() ---> Cuenta el nmero de elementos de la cola
*******************************************************************************/
#if !defined (EXIT_SUCCESS)
#define EXIT_SUCCESS 0
#endif

#if !defined (EXIT_FAILURE)


#define EXIT_FAILURE 1
#endif

/*Tamao de la cola*/
#define ELEMENTOS 5
#include <stdio.h>

void clear(char *);


void enqueue(char *, char);
char dequeue(char *);
char peek(char *);
int count(char *);

/*Apuntadores de inicio y fin*/


static int inicio = 0, fin = -1;

Cola.c
/*Modulo que contiene las implementaciones para las funciones de Cola.h*/
#include "Cola.h"

/*Limpia la cola poniedo todo a NULL y reiniciando los valores de inicio y fin*/
void clear(char * cola){

int i;

for(i = 0; i < ELEMENTOS; i++)


cola[i] = NULL;

inicio = 0;

fin = -1;

/*Cuenta el nmero de elementos no nulos dentro de la cola*/


int count(char * cola){

int elementos = 0, i;

for(i = 0; i < ELEMENTOS; i++)


if(cola[i] != NULL)
elementos++;

return elementos;

/*Obtiene, sin eliminar, el primer elemento de la cola*/


char peek(char * cola){

if (count(cola) != 0)
return cola[inicio];
else
return NULL;

}
/*Inserta un elemento al final de la cola*/
void enqueue(char * cola, char elemento){
extern int fin;

if (count(cola) < ELEMENTOS){

fin = increment(fin);

cola[fin] = elemento;
}

/*Obtiene y elimina el primer elemento de la cola*/

char dequeue(char * cola){

extern int inicio;


char primerElemento;

if(count(cola) != 0){

primerElemento = peek(cola);

cola[inicio] = NULL;

inicio = increment(inicio);

return primerElemento;

}else{

return NULL;
}

/*Incrementa los apuntadores de los extremos de la cola para implementar la circularidad*/


int increment(int extremo){

if(extremo >= ELEMENTOS)


extremo = 0;
else
extremo ++;

return extremo;
}
main.c
#include <stdlib.h>
#include "Cola.h" /*Librera donde estn las funciones para una cola*/

void ejecuta(char *, int);

int main()
{

char elemento;

/*Se crea la cola de n ELEMENTOS*/


char colaVec[ELEMENTOS];
int opc;

do{

system("cls");
printf("Elije una opcion:\n ");
printf("1. Agregar elemento\n");
printf("2. Contar elementos\n");
printf("3. Obtener primer elemento\n");
printf("4. Quitar primer elemento\n");
printf("5. Vaciar\n");
printf("6. Imprimir\n");
printf("7. Salir\n");
printf("\nOpcion: ");
scanf("%d", &opc);

ejecuta(colaVec, opc);

}while(opc != 7);

getchar();

return EXIT_SUCCESS;

void ejecuta(char * colaVec, int opcion){

char elemento;
int i;

switch(opcion){

case 1:
printf("\nAgreagar elemento a la cola: \n");
fflush(stdin);
scanf("%c", &elemento);

enqueue(colaVec, elemento); /*Se agrega un element a la cola*/

break;

case 2:
printf("\n%d", count(colaVec)); /*Contamos el nmero de elementos en la cola*/

printf("\n");
system("pause");

break;

case 3:
printf("\n%c ", peek(colaVec)); /*Obtenemos, sin eliminar, el primer elemento*/

printf("\n");
system("pause");

break;

case 4:
printf("\n%c ", dequeue(colaVec)); /*Se obtiene y elimina el primer elemento*/

printf("\n");
system("pause");

break;

case 5:
clear(colaVec); /*Se vacia la cola*/
break;

case 6:
printf("\n\n");
for (i = 0; i < ELEMENTOS; i++)
printf("%c ", colaVec[i]);

printf("\n");
system("pause");
break;

}
}
Una vez hecha la implementacin podemos obtener las siguientes conclusiones:

1. Una cola est vaca si inicio apunta al primer elemento del arreglo y fin apunta a tierra o
nulo.

2. Una cola est llena si el primer elemento de la cola est inmediatamente precedido por el
ltimo.

3. Si la cola tiene un nico elemento, inicio y fin apuntan al mismo elemento del arreglo.

Como vemos en el cdigo fuente, las funciones queue() y dequeue() deben considerar el
desbordamiento de los elementos a la hora de actuar para implementar la circularidad, de lo cual
se encarga la funcin increment():

/*Llamada a la funcin increment en queue()*/


fin = increment(fin);

/*Llamada a la funcin increment de dequeue()*/


inicio = increment(inicio);

Adicionalmente las funciones queue() y dequeue() deben preguntar si la cola no est llena o vaca,
respectivamente.

/*Se checa si la cola est llena antes de insertar un elemento:


queue()*/
if (count(cola) < ELEMENTOS)

/*Se checa si la cola est vaca antes de quitar un elemento:


dequeue()*/
if(count(cola) != 0)

Una implementacin ms eficiente de una cola est dada por una lista simplemente enlazada,
puesto que como sabemos, en lenguaje C, un arreglo no puede incrementar su tamao (aadir
nuevos elementos) en tiempo de ejecucin, por lo que si quisiramos agregar ms elementos a la
cola, sera imposible con la implementacin anterior.
4. Pilas
4.1 Introduccin

Una pila es una estructura de datos lineal a la cual slo se puede tener acceso en uno de sus
extremos para almacenar y recuperar datos. Podemos imaginarnos a una pila como un montculo
de platos sucios que uno debe lavar. Uno slo puede sacar el plato de hasta arriba del montculo,
ya que no se puede retirar un plato que tenga otros platos sobre de l. De igual manera, uno slo
puede colocar nuevos platos sobre el ltimo plato de la pila. Visto de otro modo, el ltimo plato
colocado en el montculo es el primero que se puede remover de ste. A este tipo de estructuras
se les denomina LIFO (Last in-First out), o primero en entrar, ltimo en salir.

Las pilas se pueden crear de dos maneras principales, con arreglos o con listas.

Dentro de las pilas se definen las siguientes operaciones bsicas:

Borrar la pila
Revisar si la pila est vaca
Insertar un nuevo elemento en la pila
Extraer el ltimo elemento de la pila
Revisar el ltimo elemento de la pila sin removerlo
6. Tablas de dispersion (Hash Tables)
6.1 Introduccin
6.2 Funciones hash
6.3 Manejo de colisiones
6.4 Operaciones sobre una tabla de dispersin

6.1 Introduccin

La principal operacin usada en los mtodos de bsqueda descritos en los captulos anteriores fue
a travs de comparacin de valores. En una bsqueda secuencial, la tabla que guarda los
elementos es consultada sucesivamente para determinar cul celda de la tabla coincide con la
informacin buscada y saber si el elemento est o no dentro de la estructura de datos.

Las tablas de dispersin permiten consultar o eliminar cualquier elemento conociendo su nombre,
de modo que lo que estamos implementando es un diccionario. Desearamos ser capaces de
ejecutar las operaciones bsicas en tiempo constante, tal y como se ha conseguido en los casos de
las pilas y de las colas. Debido a que el tipo de accesos est ahora mucho menos restringido, esto
parece un objetivo imposible. Parece razonable que cuando el diccionario aumenta de tamao, las
bsquedas tardarn ms tiempo, sin embargo, esto no tiene por qu ser necesariamente as.

Una estructura de datos del tipo hash table est diseada tomando como base un arreglo. El
arreglo consiste de elementos que van desde el 0 hasta un tamao predeterminado. Cada
elemento es guardado en el arreglo basndonos en alguna parte de la informacin para generar
una llave (la direccin del arreglo donde se guardar la informacin). Por tanto, para guardar un
elemento dentro de la tabla de dispersin, la llave debe estar dentro del rango de 0 hasta el
tamao del arreglo, lo cual se obtiene implementando una funcin de mapeo, o tambin llamada
funcin hash.

En lenguaje de programacin C, una tabla de dispersin consiste fundamentalmente en un arreglo


de listas enlazadas. En cada una de estas listas se colocan los elementos correspondientes a una
posicin especfica dentro del arreglo. Para insertar un elemento, primero debemos pasara la
llave a una funcin hash a fin de obtener la posicin en la que se debe de colocar la informacin.
Entonces, el elemento se inserta al inicio de la lista enlazada apropiada. Si queremos obtener o
remover algn elemento, primero debemos para la llave por la funcin hash para encontrar la lista
adecuada, luego, recorrer dicha lista hasta encontrar el elemento adecuado.
Hash table como arreglo de listas.

6.2 Funciones hash

Existen diferentes implementaciones para crear funciones hash que devuelvan una direccin
dentro del arreglo para determinado elemento. Escoger una funcin hash depende del tipo de
dato de la llave que utilizaremos. Una funcin hash ideal es aquella que transforma diferentes
llaves en diferentes nmeros. Generalmente, las funciones hash perfectas son difciles de expresar
con una frmula. Es por lo anterior que en esta seccin discutiremos algunos tipos especficos de
funciones hash.

El mtodo de la divisin

Una funcin hash debe garantizar que el nmero que regresa sea una posicin vlida dentro de
los elementos del arreglo. La manera ms simple de asegurar esto es usando la operacin mdulo.
Supongamos que m es el tamao del arreglo y k es nuestra llave. Entonces la funcin hash h
quedara como sigue:

h(k) = k mod m (1)

Siempre y cuando k sea un nmero. Este mtodo ofrece mejores resultado si k es un nmero
primo. En caso de que k no sea nmero primo, se puede hacer la siguiente modificacin a la
funcin hash:

h(k) = (k mod p) mod m (2)

para algn nmero primo p mayor a m. La razn de esta modificacin responde a ciertas
circunstancias especiales en una tabla hash. Pensemos en un arreglo de 10 elementos y en llaves
que terminan todas en 0, por ejemplo llaves como 10, 20, 30, 40. Todas estas llaves, despus de la
funcin hash (1) regresaran un 0, sin embargo, si multiplicamos k por un nmero primo y
obtenemos el mdulo con el tamao del arreglo, obtendramos resultados diferentes, que es justo
lo que se hace en la funcin hash (2). Un mtodo alternativo para evitar estas colisiones en la
medida de lo posible, es asegurar que el tamao del arreglo sea siempre un nmero impar.

Utilizando valores para k = 10, 20, 30, 40, 50 y m = 10; con la funcin hash (1) obtendramos
siempre cero, sin embargo, para los mismos datos, con la funcin hash (2) y p = 11, tenemos lo
siguiente:

h(10) = (10%11)%10 = 0

h(20) = (20%11)%10 = 9

h(30) = (30%11)%10 = 8

h(40) = (40%11)%10 = 7

h(50) = (50%11)%10 = 6

Un ndice diferente del arreglo para una llave diferente, se aproxima mucho a una funcin hash
perfecta.

El mtodo de la combinacin (folding)

En este mtodo, la llave es dividida en varias partes. Estas partes son combinadas y en ocasiones
transformadas de cierta manera para obtener una direccin dentro del arreglo. Existen dos
mtodos de combinacin, los cuales se mencionan a continuacin:

En ambos mtodos, la llave es dividida en varias partes, y estas partes son procesadas usando
operaciones simples, como la suma, para combinar dichas partes. En el mtodo conocido como
shift folding la llave es dividida desde el final y luego es procesada, por ejemplo, un nmero de
seguridad social 123-45-6789 puede ser dividido en tres partes, 123, 456, 789 y estas partes
pueden ser sumadas. Del nmero resultante, 1368, podemos obtener el mdulo junto con el
tamao del arreglo, 1368%m, o si el tamao del arreglo es 1000, los primero tres dgitos podran
ser usados para la direccin, es decir 136. Otra opcin es dividir el nmero en 5 partes (12, 34, 56,
78 y 9), sumarlos y hacer la operacin mdulo con el tamao del arreglo.

El segundo mtodo es conocido como boundary folding, en este mtodo, una parte de la llave es
puesta en orden inversa y se opera con todas las partes. Consideremos el mismo nmero de
seguridad 123-45-6789, si lo dividimos en tres partes, 123, 456 y 789, e invertimos el segundo
trmino para que quede como 654, hecho esto, la combinacin se hace como en el mtodo
anterior, las partes se suman y al resultado se le aplica la operacin mdulo.
En ambos mtodos, la llave es, generalmente, dividida en un nmero impar de partes iguales ms
algn residuo; como en el segundo ejemplo 12, 34, 56, 78 y 9, cuatro pares de nmero y una cifra;
para despus ser sumadas. Este mtodo es simple y rpido.

En caso de que las llaves fueran cadenas en lugar de nmeros, podramos ocupar la operacin XOR
entre todos los caracteres que forman la cadena y usar el resultado como direccin. Por ejemplo,
para la cadena abcdefgh, h(abcdefgh) = a XOR b XOR c XOR d XOR e XOR f XOR g XOR
h. Sin embargo, este sencillo mtodo slo regresara direcciones entre 0 y 127. Para mejores
resultados, conviene realizar la operacin XOR agrupando los caracteres en conjuntos que sumen
la cantidad de bytes necesarios para representar el tamao de un entero. Esto es, en los
compiladores modernos, un entero ocupa 4 bytes para representarse, entonces la operacin XOR
se hara a conjuntos de 4 caracteres, ya que un carcter slo ocupa un byte: abcd XOR efgh y
el resultado de dicha operacin dividido por el modulo del tamao del arreglo nos dara una
direccin dentro del arreglo.

Cuando la llave no es un nmero entero sino una cadena es necesario ser cuidadosos con la
eleccin de la funcin hash adecuada. Un mtodo simple para el manejo de cadenas, adems del
mencionado anteriormente, es sumar el cdigo ASCII de cada letra en la llave y a ese valor
aplicarle el mdulo con el tamao del arreglo. Si programramos este mtodo, nos daramos
cuenta de que los elementos slo estaran distribuidos al principio y al final del arreglo.

Existen otros tipos diferentes de funciones, los cuales se tornan ms complejos e innecesarios para
las implementaciones de este manual, es por esta razn que slo los nombraremos pero no
otorgaremos una descripcin extra:

- El mtodo de la multiplicacin
- Funcin mid-square (que traducido se acercara a la funcin de la parte intermedia del
cuadrado de la llave)
- Extraccin
- Transformacin de la clave

S-ar putea să vă placă și