Este documento describe estructuras dinámicas de datos como pilas, colas y listas en lenguaje C. Explica las definiciones, creación, declaración, uso y manipulación de pilas, colas y listas a través de funciones, con ejemplos de código C para cada una.
3. PILAS
Definición:
Una pila es una estructura de datos simple. Los datos se van apilando uno tras otro. Podemos
abstraer cada elemento como un nodo que puede apuntar a otro nodo.
Su modo de acceso es LIFO: el último en entrar es el primero en salir. Las operaciones que
tiene son 2: push y pop; la primera le pone un elemento y la segunda quita el último.
Declaración:
El Nodo:
Esto es de lo que estará compuesta nuestra pila, para ello usaremos una lista ligada en donde
un nodo apunta a otro y este otro a otro (opcionalmente), así infinitamente.
Claro que el elemento siguiente de un nodo es opcional; porque el elemento de hasta abajo no
apunta a nadie.
También tiene un nombre, que es el dato en sí que guardaremos en la pila. Si quisiéramos que
nuestra pila fuera de enteros entonces comenzaríamos cambiando el tipo de dato de char[] a
int.
El elemento superior:
En el inicio de todos los tiempos, debe haber un elemento superior aunque no tenga valor y no
apunte a nada. Es decir, aunque la pila esté vacía debemos tener un elemento que será el
inicio de todo:
4. Creación:
Agregar elementos a la pila: push
Veamos la operación push, que agrega un elemento a la pila.
El nodo superior es una variable global, por eso no necesitamos recibirla en la función.
Lo que hacemos es alojar espacio en memoria para almacenar nuestro nuevo elemento usado
memory allocate o malloc, como decidió bautizar esta función el gran Dennis.
A ese nuevo nodo le ponemos el dato que llevará (String), y como es el que estará hasta arriba
entonces hacemos que apunte hacia el que era el superior; y este nuevo ahora tomará su
lugar.
Explicado en otras palabras, este nuevo nodo será el superior y apuntará al que antes era el
superior; se está poniendo encima de él.
La próxima vez que apilemos, se creará otro nuevo y este dejará de ser el superior para pasar a
ser el siguiente del nuevo; y así sucesivamente.
Eliminar el último elemento (operación pop):
Esto es fácil pero interesante a la vez. Como vamos a desapilar el elemento, necesitamos hacer
que el elemento superior ahora sea al que apuntaba; es decir, su siguiente.
Pero eso no es todo, debemos liberar la memoria que se estaba usando y para ello
respaldamos lo que ocupaba el que vamos a eliminar dentro de temporal y llamamos a free.
Esto sólo ocurre en caso de que superior no sea nulo.
5. Uso:
-Apilar, también conocido como push: agregar un elemento
-Desapilar, o la operación pop: quitar el último elemento; es decir, el elemento superior
Manipulación a través de funciones:
-Imprimir: recorrer la pila e imprimir sus valores.
-Tamaño: devolver el tamaño de la pila.
-Leer último: leer el elemento superior de la pila.
-Vacía: indica si la pila está vacía.
Imprimir pila:
Imprimir la pila no es una operación necesaria, pero como este es un ejercicio sí lo haremos.
Únicamente recorremos toda la pila mientras haya un apuntador a siguiente:
Para esto necesitamos un nodo temporal y comenzamos recorriendo desde superior.
Asignamos a temporal lo que esté apuntando a siguiente; si ya no apunta a nada entonces
temporal será nulo y se terminará el ciclo.
Tamaño de pila o stack en C:
Esto es muy parecido al de imprimir; pero en lugar de imprimir vamos aumentando un
contador dentro del ciclo:
Comprobamos si superior es nulo y en caso de que sí ya no hacemos el ciclo, pues la pila está
vacía.
6. Operación top o peek de la pila(último elemento):
Esto es fácil, se lee el último elemento de la pila y en este caso es un String.
Si superior es nulo, se imprime que la pila está vacía.
En caso de que no, entonces devolvemos lo que tenga el elemento superior.
Método para saber si la pila está vacía:
podríamos decir que, si ya tenemos el método tamanio, podríamos regresar un booleano
resultante de la comparación de tamanio == 0 pero eso sería un desperdicio de recursos
Ya que si la pila tiene 0 elementos todo va bien, pero, ¿qué tal si la pila tiene 100000
elementos? tendríamos que recorrer todos dentro de tamanio para determinar al final que la
pila no está vacía.
Es por eso que mejor usamos otra lógica… si el elemento superior no apunta a nada, entonces
no hay más elementos y por lo tanto la pila está vacía.
Ejemplo:
Uniendo todo el código quedaría de esta manera:
#include <stdio.h> // printf
#include <stdlib.h> // malloc y free
#include <stdbool.h> // Booleanos
// Un nodo tiene un dato, el cual es el Nombre. Y otro nodo al que apunta
struct nodo {
char Nombre[45];
struct nodo *siguiente;
};
// Prototipos de funciones
7. void agregar(); // push
void eliminarUltimo(void); // pop
void imprimir(void);// Mostrar
int tamanio(void); // El tamaño de la pila
bool vacia(void); // Indica si la pila está vacía
void ultimo(void); // El último elemento.
// Todo comienza con el nodo superior
struct nodo *superior = NULL;
int main() {
int eleccion;
int Nombre1[45];
while (eleccion != 7) {
printf("nn--BIENVENIDO A LA PILA DEL ALMACEN--n1.- Agregar Objeton2.- Quitar
Objeton3.- Mostrar "
"pilan4.- Mostrar Cantidad de Objetos n5.- Comprobar si esta vacian6.- "
"Mostrar ultimo Objeton-7.- SalirnntElige: ");
scanf("%d", &eleccion);
switch (eleccion) {
case 1:
agregar();
break;
case 2:
eliminarUltimo();
break;
case 3:
imprimir();
break;
case 4:
printf("La pila tiene %d Objetosn", tamanio());
8. break;
case 5:
if (vacia()) {
printf("La pila esta vacian");
} else {
printf("La pila NO esta vacian");
}
break;
case 6:
ultimo();
break;
}
}
}
int tamanio(void) {
int contador = 0;
if (superior == NULL)
return contador;
struct nodo *temporal = superior;
while (temporal != NULL) {
contador++;
temporal = temporal->siguiente;
}
return contador;
}
bool vacia(void) { return superior == NULL; }
9. void ultimo() {
if (superior != NULL){
printf("El Ultimo Objeto es: %sn", superior->Nombre); }
else {
printf("La pila esta Vacia!!");
}
}
// Operación push
void agregar() {
// El que se agregará; reservamos memoria
nodo *nuevoNodo;
nuevoNodo =(struct nodo *) malloc(sizeof(struct nodo));
// Le ponemos el dato
printf("Ingresa el nombre del objeto:n");
scanf("%s",&nuevoNodo->Nombre);
printf("Agregando %s...n", nuevoNodo->Nombre);
// Y ahora el nuevo nodo es el superior, y su siguiente
// es el que antes era superior
nuevoNodo->siguiente = superior;
superior = nuevoNodo;
}
void imprimir(void) {
10. printf("Imprimiendo...n");
struct nodo *temporal = superior;
if(temporal == NULL){
printf("No hay objetos en la pila!!n");
}
while (temporal != NULL) {
printf("%sn", temporal->Nombre);
temporal = temporal->siguiente;
}
}
// Operación pop, eliminar el de hasta arriba
void eliminarUltimo(void) {
printf("Los trabajadores estan Quitando el Ultimo Objeto...n");
if (superior != NULL) {
// Para liberar la memoria más tarde debemos
// tener la referencia al que vamos a eliminar
struct nodo *temporal = superior;
// Ahora superior es a lo que apuntaba su siguiente
superior = superior->siguiente;
// Liberar memoria que estaba ocupando el que eliminamos
free(temporal);
}
}
11. COLAS
Definición:
Una cola es un tipo especial de lista enalazada en la que sólo se pueden insertar nodos en uno
de los extremos de la lista y sólo se pueden eliminar nodos en el otro. Además, como sucede
con las pilas, las escrituras de datos siempre son inserciones de nodos, y las lecturas siempre
eliminan el nodo leído. Este tipo de lista es conocido como lista FIFO (First In First Out), el
primero en entrar es el primero en salir.
Declaración:
El símil cotidiano es una cola para comprar, por ejemplo, las entradas del cine. Los nuevos
compradores sólo pueden colocarse al final de la cola, y sólo el primero de la cola puede
comprar la entrada. El nodo típico para construir pilas es el mismo que vimos en los capítulos
anteriores para la construcción de pilas: Los tipos que definiremos normalmente para manejar
colas serán casi los mismos que para manejar listas y pilas, tan sólo cambiaremos algunos
nombres:
Nodo es el tipo para declarar nodos, evidentemente.
Es evidente, que una cola es una lista abierta. Así que sigue siendo muy importante que
nuestro programa nunca pierda el valor del puntero al primer elemento, igual que pasa con las
listas abiertas. Además, debido al funcionamiento de las colas, también deberemos mantener
un puntero para el último elemento de la cola, que será el punto donde insertemos nuevos
nodos. Teniendo en cuenta que las lecturas y escrituras en una cola se hacen siempre en
extremos distintos, lo más fácil será insertar nodos por el final, a continuación del nodo que
no tiene nodo siguiente, y leerlos desde el principio, hay que recordar que leer un nodo
implica eliminarlo de la cola.
12. Creación:
Agregar Elementos A la Cola(Operación Enqueue):
Se llaman los datos extraidos a la función que creamos para crear la cola:
Lo que hacemos es alojar espacio en memoria para almacenar nuestro nuevo elemento usado
memory allocate o malloc, como decidió bautizar esta función el gran Dennis.
A ese nuevo nodo le ponemos el dato que llevará (el entero), y como es el que estará hasta
arriba entonces hacemos que apunte hacia el que era el superior; y este nuevo ahora tomará
su lugar, (Justo como en la pila).
Se limpia el buffer le almacenan los datos obtenidos en nodoNuevo->nombre y en
nodoNuevo->id para poderlos trabajar
Entonces si la cola está vacía el nodo toma el primer y último lugar si no entonces el ultimo
nodo se agrega el nodoNuevo toma el primer lugar para que después quede final apuntando
siguiente igualando a temporal.
13. Eliminar el Primer elemento (operación Dequeue):
Recordemos que leer un elemento de una cola, implica eliminarlo. Ahora también existen dos
casos, que la cola tenga un solo elemento o que tenga más de uno. Usaremos un puntero a un
nodo auxiliar:
Hacemos que nodo apunte al primer elemento de la cola, es decir a primero. Asignamos a
primero la dirección del segundo nodo de la pila: primero->siguiente. Guardamos el contenido
del nodo para devolverlo como retorno, recuerda que la operación de lectura en colas implica
también borrar. Liberamos la memoria asignada al primer nodo, el que queremos eliminar.
Leer un elemento en una cola con un solo elemento:
También necesitamos un puntero a un nodo auxiliar:
Hacemos que nodo apunte al primer elemento de la pila, es decir a primero. Asignamos NULL
a primero, que es la dirección del segundo nodo teórico de la cola: primero->siguiente.
Guardamos el contenido del nodo para devolverlo como retorno, recuerda que la operación
de lectura en colas implica también borrar. Liberamos la memoria asignada al primer nodo, el
que queremos eliminar. Hacemos que último apunte a NULL, ya que la lectura ha dejado la
cola vacía.
14. Leer un elemento en una cola, Caso general:
Hacemos que nodo apunte al primer elemento de la pila, es decir a primero. Asignamos a
primero la dirección del segundo nodo de la pila: primero->siguiente. Guardamos el contenido
del nodo para devolverlo como retorno, recuerda que la operación de lectura en colas implica
también borrar. Liberamos la memoria asignada al primer nodo, el que queremos eliminar.
Si primero es NULL, hacemos que ultimo también apunte a NULL, ya que la lectura ha dejado la
cola vacía.
Uso:
De nuevo nos encontramos ante una estructura con muy pocas operaciones disponibles. Las
colas sólo permiten añadir y leer elementos:
Añadir: Inserta un elemento al final de la cola. Leer: Lee y elimina un elemento del principio
de la cola.
Manipulación a través de funciones:
Con estas funciones getId() Y getpTrnom() lo que hicimos fue extraer cada dato por separado y
devolverlos para poder trabajarlos en la función enqueue();
Con la función isEmptywrap llamamos un valor que nos genera isEmpty() donde se verifica si
final está totalmente vacía retorna 1 por lo tanto imprimirá “la cola esta vacia!” si no el caso
contrario
15. Función donde se verifica si final esta vacío
Ejemplo:
Uniendo todo el código quedaría de esta manera:
#include <stdio.h>
#include <stdlib.h>
// Un nodo tiene un dato, el cual es el Nombre y el id. Y otro nodo al que apunta
struct persona {
int id;
char *nombre;
struct persona *siguiente;
};
int contador=0;
//Prototipos de funciones
char* getPtrNom();
int getId();
int isEmpty();
int menu();
void clearBuffer();
void dequeue();
void enqueue();
void isEmptyWrap();
16. typedef struct persona Nodo;
Nodo *final;
Nodo *inicio;
main() {
final = inicio = 0;
return menu();
}
int menu() {
char c;
printf("nn--Servitec c.a--n");
printf("nn-Cola para atender a los clientes-n");
do {
printf("n~~ MENU: cola! ~~");
printf("nHaga una seleccion:");
printf("n 1.-Ingresar a la cola (enqueue)");
printf("n 2.-Atender Cliente(dequeue)");
printf("n 3.-Cuantas personas hay en la cola?");
printf("n 4.-Comprobar si esta vacia(isEmpty)");
printf("n q: salir.n");
c = getchar();
switch(c) {
case '1':
enqueue();
17. break;
case '2':
dequeue();
break;
case '3':
printf(" Numero de Clientes en la cola: %d !n",contador);
break;
case '4':
isEmptyWrap();
break;
// default :
// printf(" Seleccione una de las opciones establecidas!");
// break;
}
} while(c != 'q' && c != EOF);
return 1;
}
// metodo wrapper para el menu
void isEmptyWrap() {
clearBuffer();
if (isEmpty()) {
printf("La Cola Esta Vacia!n");
} else {
printf("La Cola NO Esta Vacia!n");
}
}
int getId() {
int num;
18. printf("Ingrese el ID: ");
scanf("%d" , &num);
return num;
}
// retorna un nuevo puntero a un arreglo
char* getPtrNom() {
char d,*newAr;
int i = 0;
newAr = (char*) malloc(sizeof(char)*100);
printf("Ingrese el nombre: ");
while((d = getchar()) != EOF && d != 'n') {
newAr[i++] = d;
}
return newAr;
}
// imprime el primer valor ingresado
// el penultimo valor es el nuevo inicio
// el primer valor es borrado de la lista
void dequeue() {
Nodo *actual,
*temporal;
contador--;
printf("nEL siguiente cliente a atender es:...n");
clearBuffer();
19. if (isEmpty()) {
printf("nNO hay clientes Ingrese uno a la cola!n");
} else {
if (final == inicio) { // si solo hay 1 nodo
printf("%d | %sn", final->id, final->nombre);
final = inicio = 0; // dejamos los punteros en null
} else { // si hay mas de 1 nodo
actual = final;
while (actual != inicio) { // recorra la cola y quede en el penultimo
temporal = actual; // temporal guarda la direccion del penultimo
actual = temporal->siguiente;
}
printf("%d | %sn", inicio->id, inicio->nombre); // imprimimos el ultimo nodo
inicio = temporal; // el penultimo es ahora el ultimo
}
}
}
// agrega un nodo nuevo al final de la cola
void enqueue() {
Nodo *nodoNuevo,
*temporal;
nodoNuevo = (Nodo*) malloc(sizeof(Nodo));
clearBuffer();
20. nodoNuevo->nombre = getPtrNom();
nodoNuevo->id = getId();
contador++;
if (isEmpty()) { // si la cola esta vacia
final = nodoNuevo; // el nodo toma el primer
inicio = nodoNuevo; // y el ultimo lugar
} else { // si hay al menos 1 nodo
temporal = final; // almacenamos el ultimo nodo agregado
final = nodoNuevo; // el nodoNuevo toma el primer lugar
final->siguiente = temporal; // apuntando al nodo que estaba en primer lugar
}
clearBuffer();
}
int isEmpty() {
if (!final) {
return 1;
} else {
return 0;
}
}
// cuando se manejan menus que tienen breaklines, se ocupa limpiar el buffer
// antes de empezar a leer caracteres
void clearBuffer() {
while(getchar() != 'n')
;
}
21. LISTAS
Definición:
Una lista se comporta como una pila si las inserciones y extracciones las hacemos por un
mismo lado de la lista. También se las llama listas LIFO (Last In First Out - último en entrar
primero en salir)
Importante: Una pila al ser una lista puede almacenar en el campo de información cualquier
tipo de valor (int, char, float, vector de caracteres, un registro, etc.)
Para estudiar el mecanismo de utilización de una pila supondremos que en el campo de
información almacena un entero (para una fácil interpretación y codificación)
Inicialmente la PILA está vacía y decimos que el puntero raiz apunta a NULL (Si apunta a NULL
decimos que no tiene una dirección de memoria, en realidad este valor NULL es el valor cero):
Declaración:
Se declara la estructura tal cual como lo hemos hecho en capítulos anteriores apuntando nodo
a enlace
Y su respectiva declaración de funciones en este caso solo tenemos mostrar_lista e insertar.
Creación:
Insertar :
22. En esta forma los nodos se organizan de modo que cada uno apunta al siguiente, y el último no
apunta a nada, es decir, el puntero del nodo siguiente vale NULL:
En las listas simples enlazadas existe un nodo especial: el primero. Normalmente diremos que
nuestra lista es un puntero a ese primer nodo y llamaremos a ese nodo cabeza de la lista. Eso
es porque mediante ese único puntero podemos acceder a toda la lista.
Es muy importante que nuestro programa nunca pierda el valor del puntero al primer
elemento, ya que, si no existe ninguna copia de ese valor, y se pierde, será imposible acceder
al nodo y no podremos utilizar la información almacenada ni liberar el espacio de memoria que
ocupa.
Cuando el puntero que usamos para acceder a la lista vale NULL, diremos que la lista está
vacía:
Uso:
Las operaciones sobre listas ordenadas más comunes son:
Alistar datos sin usar arrays
– Insertar: añade un elemento (nodo) a la lista.
– Eliminar: suprime el nodo que contiene un elemento especificado de la lista.
Manipulación a través de funciones:
– ImprimeLista: imprime todos los elementos de la lista.
–ListaVacía: operación booleana que indica si la lista está vacía
Imprimir Lista:
Para imprimir es un proceso muy sencillo solo se coloca un while y que mientas ptr sea
diferente de vacío vaya imprimiendo el dato.
El resto… es solo adorno.
23. Lista vacía:
Muy sencillo para saber si tu lista esta vacia se puede agregar un contador en la función de
insertar() igualado a 0 y que cuando se seleccione la opción para verificar y está vacía se
coloca un condicional If y si el contador es igual a 0 por la tanto la lista está vacía del resto No.
Ejemplo:
Uniendo todo el código quedaría de esta manera:
#include <stdio.h>
#include <stdlib.h>
#include<conio.h>
typedef struct nodo
{
int dato;
struct nodo *enlace;
} LISTA;
void mostrar_lista(LISTA *ptr);
void insertar(LISTA **ptr, int elemento);
int contador=0;
main(){
LISTA *n1 = NULL;
int elemento;
int opcion=0;
printf("Crea tu lista de Numeros Pares:");
do{
printf("nn 1.-Ingresar elemento en la lista.");
printf("nn 2.-Mostrar elementos de la lista.");
printf("nn 0.-Salir del programa.");
24. printf("nn Opcion: ");
scanf(" %d",&opcion);
switch(opcion) {
case 1:
system("cls");
printf("nIntroduzca elemento: ");
scanf("%d", &elemento);
if(elemento%2==0){
insertar(&n1, elemento);
contador++;
}
break;
case 2:
printf("nLa Lista De Numeros Pares es:n ");
mostrar_lista(n1);
break;
}
} while(opcion!=0);