Está en la página 1de 49

Tipos abstractos de datos:

- Pilas
- Colas
- Listas

Implementación a través del uso de memoria dinámica


En este documento comenzaremos a tratar las estructuras de datos que no están
incorporadas a los lenguajes de programación comúnmente utilizados.

Este tipo de estructuras deben crearse e implementarse por el desarrollador, en cada


algoritmo, antes de que puedan usarse.

Veremos a qué nos referimos con "tipos abstractos de datos" y cómo definir sus
estructuras en forma lógica.

Un adelanto:

Un tipo de datos abstracto como una estructura de datos más un


conjunto de operaciones básicas que pueden operar sobre estas
estructuras.
Algunas definiciones y conceptos:
En muchas ocasiones desarrollamos algoritmos que procesan información del mismo tipo, para lo cual
representamos en memoria mediante vectores y/ archivos. La característica principal de estas estructuras
de datos consiste en que todos los datos son del mismo tipo y representan a una misma entidad
(estudiantes, artículos, materias, etc.)

Se trabajó mucho con el concepto de funciones y se desarrollaron especialmente para cada tipo de
implementación. Entre otras podemos destacar la función traer mediante la cual obtenemos el siguiente
elemento (en orden secuencial) a ser tratado; la función inicializar que consiste en "limpiar" la variable (por
ejemplo vector); la función ordenar; la función buscar (tanto para vectores cuanto para archivos) y así
podemos mencionar otras funciones más.
En síntesis:
Tipo de dato. agrupación de elementos con las mismas características: datos de estudiantes; artículos, etc.
Abstracción. consiste en manejar un problema, fenómeno, objeto, tema o idea como un concepto general, sin
considerar la gran cantidad de detalles que estos puedan tener: inicializa vector, ordenar, buscar, etc.

Un tipo abstracto de datos es un conjunto de elementos del mismo tipo cuya creación y
conocimiento de sus características sólo puede realizarse mediante un conjunto de
operaciones.
Se pueden pensar diversos ejemplos de tipos abstractos de datos, en lo cotidiano utilizamos dispositivos mediante
elementos externos sin preguntarnos cómo lo hacen: navegar en el TV a través de los botones del control remoto; manejar
un automóvil a través de los pedales y el volante; etc.

En algoritmos sumamos, restamos, multiplicamos y dividimos números enteros sin conocer qué pasa con cada una de
estas operaciones. Incluso podríamos definir operaciones de suma, resta, multiplicación y división entre números
binarios y luego utilizar las mismas sin siquiera pensar cómo fueron resueltas.

Entonces podemos definir un Tipo Abstracto de Datos (TAD) como un conjunto de datos y
un conjunto de operaciones sobre los datos.
Entonces, definiremos un TAD como:
1) Una estructura de datos.
2) Un conjunto de operaciones o herramientas que operan de acuerdo un comportamiento
determinado sobre la estructura de datos.

Esto se logra a través del concepto de encapsulamiento: propiedad de las estructuras


de datos de permitir el acceso a su estado únicamente a través de su interfaz o de
relaciones preestablecidas con otras estructuras de datos.
Denominamos interfaz a la parte de un algoritmo que permite la circulación correcta y sencilla de datos
entre varias aplicaciones y entre el propio algoritmo y el usuario.

El encapsulamiento implica ocultar la información para tener libertad de elección o cambio de


implementación. De esta manera el programador sólo puede acceder a los datos de la estructura por medio
de un conjunto de operaciones.
Es importante entender que debemos separar las funciones que utilizan una estructura de datos, de las
funciones que los implementan.
De esta forma podemos modificar el nivel de implementación de dichas estructuras, sin tener que hacer una
modificación sustancial en el resto del programa.

Este encapsulamiento de las estructuras de datos hace a nuestros


algoritmos mucho más fácilmente modificables.
En los diversos temas desarrollados hasta el momento se trabajó intensamente con estos conceptos.

Recordemos solamente el desarrollo de un algoritmos de corte de control que procese datos de un


vector o un archivo. Pensamos que se trata del mismo algoritmo independientemente del origen de los datos.
Se resuelve la implementación a través de diversas interfaces: iniciar, traer, guardar, finalizar, que
en un caso opera sobre datos en un vector y en el otro caso opera sobre datos de un archivo.
Pensemos entonces en qué se diferencian los tipos de datos (entero, flotante, cadena de caracteres) de los tipos de
datos abstractos (vectores, archivos, otros….)
Cuando definimos un tipo de dato, estamos haciendo referencia al conjunto de valores al que puede pertenecer
una variable o una constante y a las operaciones que podemos hacer con los mismos. Así, con el tipo de datos
"cadena de caracteres", podremos concatenar, buscar subcadena, comparar y otro conjunto de
funciones inherentes a las cadenas de caracteres.
Pensamos nuevamente en el ejemplo de los Así, queda encapsulada la
números enteros y definamos una función fSuma implementación de las funciones que
(no usamos el símbolo +), cuya implementación
operan con este tipo de números.
sume 2 parámetros que llegan y devuelve el
Encapsulamiento de
resultado. Datos
Entonces, resolvemos algoritmos que deban suma
dos números enteros (en base 10) mediante la Números enteros

función fSuma. Ahora, si debemos resolver un •SUMA


•RESTA
algoritmo (similar al anterior) que sume números •MULTIPLICACION
•DIVISION
binarios (en base 2), será suficiente modificar la
implementación de la función fSuma de forma tal
que sume números binarios. Abstracción de Datos
Lo mismo para la resta, multiplicación y división.
Cuando definimos un tipo de dato, estamos referencia al conjunto de valores al que puede pertenecer una
variable o una constante y a las operación haciendoes que podemos hacer con los mismos.
Podemos pensar que definir un tipo de datos implica determinar qué tipo de valores pueden almacenar y qué tipo
de operaciones se pueden realizar.
Estructura de datos = estructura de almacenamiento +
operaciones

Las estructuras de datos son las que conocemos: variables simples, variables compuestas (registros), vectores ,
matrices y aquellas estructura que iremos viendo. En particular nos detendremos en la llamada "memoria
dinámica".
Cuando hablamos de TAD, estamos haciendo lo mismo, solamente que este tipo de datos no forma parte del lenguaje de programación, es
decir no está implementado como lo está el tipo int.

Un TAD lo utilizaremos para representar parte de la realidad que no podemos representar con los tipos de datos que
nos provee el lenguaje.
Podríamos pensar entonces que definir un TAD implica,
(1) definirlo
(2) implementarlo en un lenguaje de programación
(3) usarlo en el desarrollo de aplicaciones (algoritmos)
Para definir un TAD, debemos partir de las siguientes preguntas:

- ¿En qué se puede utilizar?

- ¿Cuáles son las operaciones lógicas que puedo hacer sobre esa estructura?
Para implementar un TAD, debemos realizar las siguientes acciones:

- Elegir la estructura de datos (conocida por el lenguaje) más ajustada a la


necesidad

- Definir las operaciones lógicas (primitivas) e implementarlas en el lenguaje de


programación
Finalmente se puede utilizar este TAD en las aplicaciones que lo requieran.
Un TAD representa un concepto que define un "COMPORTAMIENTO" sobre
los datos. Entonces se debe seleccionar una estructura de datos, definir
las acciones y desarrollar estas.
Abordemos cuáles son los Tipos Abstracto de Datos más conocidos.

Antes de detallarlos recordemos que podemos trabajar con variables simples,


vectores y archivos. Pero sobre todo debemos tener presente que hasta el
momento pensamos las estructuras y resolvemos los algoritmos asociados a
estas.

También debemos comprender que el único comportamiento de las estructuras


repetitivas que nos proveen los lenguajes y con las cuales trabajamos son los
recorridos secuenciales y los accesos directos.

Así pudimos recorrer vectores, matrices y archivos. Y hasta podríamos pensar


que lo podríamos hacer con nuevos tipos de datos, por ejemplo memoria
dinámica que abordaremos en las próximas filminas.

Pero antes sería interesante pensar en otros tipos de comportamiento, y


hasta animarse a pensar en el orden de la información.
Pensemos algunos tipos de comportamientos:
En documentos anteriores nos planteamos a modo de ejemplo la inscripción a las
carreras en una facultad, diciendo que las mismas podrían ser en formularios
impresos y que estos estaban "apilados" sobre una mesa. Esta es una forma
bastante clásica de tener la información donde cada aspirante que se inscribe
"tira" la hoja sobre la PILA. Pasa lo mismo con los platos en la alacena de cada
una de nuestras casas, están guardados en una PILA.

He ahí un comportamiento. Se trata de pensar que la información se debe


procesar en el sentido inverso a cómo llego. Se lo denomina comportamiento
Last Input, First Output (LIFO por sus siglas en inglés)

Muchos años atrás en la televisión argentina había un programa semana cómico


(La tuerca) y en este había un sketch con un conjunto de jubilados, uno de los
cuales quería plantar un arbolito en la puesta de su casa y hacía el trámite en la
municipalidad y nunca lo lograba. Pasaba que su carpeta estaba en una pila
debajo de todo.
Así como describimos el comportamiento de una PILA, podemos pensar otros problemas que
nuevos tipos e comportamiento.

Por ejemplo cuando llegamos al banco, fábrica de pastas, a esperar el colectivo o a la farmacia,
debemos aguardar nuestro turno. Esto quiere decir que atenderán antes a quienes llegaron antes
que nosotros y, una vez nos atiendan, seguirán con quienes llegaron después que nosotros. Este
comportamiento se lo conoce como COLA.

Se trata de procesar la información en el mismo orden en que llegó, se lo denomina comportamiento


First Input, First Output (FIFO por sus siglas en inglés).
Los tipos abstractos de datos mencionados hasta el momento se definen por su comportamiento (FIFO y
LIFO), pero sobre todo se caracterizan por la temporalidad de la información.

La información entra y sale del TAD, no permanece en el mismo . Para ser más claros y precisos, en las
aplicaciones que requieren este tipo de comportamiento, los datos ingresan "en espera" para luego ser
atendidos e irse.
Existen otros TAD de mucha utilidad, algunos de los cuales abordaremos más adelante: LISTAS, ÁRBOLES y
GRAFOS. En cada caso podremos pensar fu funcionalidad y aplicaciones que requieran este tipo de
comportamiento.
Memoria Dinámica
En todos los algoritmos desarrollados hasta el momento utilizamos variables estáticas,
cuyo tamaño es constante durante toda la ejecución del programa y se determina en
tiempo de compilación. El ejemplo típico son los vectores y matrices, que hay que
dimensionarlos de antemano, lo que puede conlleva a un desperdicio o falta de
memoria.
En esta estructura las posiciones de memoria son contiguas y se las puede ir
recorriendo con solo incrementar el índice, el primer elemento lógico coincide con el
primero físico, es decir se comienza desde la primera posición, y el siguiente lógico
coincide con el siguiente físico, es decir al próximo elemento se accede incrementando
en uno el índice.
Esta forma de pensar el orden de la información tienen ventajas y desventajas. Respecto
a la primera vimos que es altamente eficiente a la hora de realizar un acceso directo,
realizar una búsqueda (algoritmo de búsqueda binaria).
Pero son muy ineficientes a la hora de mantener el orden. Pensemos que mantener una
estructura estática en un vector ordenada conlleva a mover todos los elementos
mayores al insertado. Otra alternativa es insertar todos y luego ordenar.. Ya vimos que
esto último es muy costoso en tiempo y espacio.
Los lenguajes de programación (Pascal, C++ y otros) proveen la posibilidad de trabajar con memoria dinámica,
esto es poder obtener espacio de almacenamiento en tiempo de ejecución, es decir poder crear variables
mientras el programa se está ejecutando.
Pero entonces, ¿cómo organizar una secuencia de datos del mismo tipo (equivalente a un vector con este tipo de
datos?

Pensar esto es algo complejo. Deberíamos suponer que cada dato (estructura) debe contener, además del propio
dato, alguna referencia al siguiente.

Pensemos en el siguiente ejemplo: estamos todos sentados en un aula de la facultad. Cada cual se sentó donde
quiso. Llega el bedel, pregunta en qué orden llegaron. Obviamente nadie lo recuerda. MAL. El bedel se va sin la
respuesta que vino a pedir.
Supongamos ahora que en la clase hay estudiantes que ya leyeron estas filminas, por lo tanto proponen pensar
como una sucesión de "variables" dinámicas (por esto que fueron llegando de a une (sin saber cuántos serían).
Proponen que el primero que llega escriba en el pizarrón su nombre y apellido y el banco en el que se sentó
(están numerados). Y le propone a cada estudiante que llega que se siente donde quiera y que cuando llegue el
próximo escriba en su cuaderno el nombre del o la estudiante y el asiento en donde se sentó.
Cuando llega el bedel, le proponen que lea en el pizarrón el nombre del primer estudiante que llegó y a partir de
ahí le vaya preguntando a cada uno (en ese orden) quién llegó después.
ASÍ PODRÁ CONOCER EL ORDEN DE LLEGADA DE LES ESTUDIANTES, SIN QUE ESTOS
ESTÉN ORDENADOS FISICAMENTE.
Entonces, como vimos en el ejemplo de la filmina anterior, el primer elemento lógico puede no coincidir con el primero
físico, en ese caso, para conocer dónde comienza lógicamente se debe saber la posición de ese primero.
El siguiente elemento lógico no es contiguo al primero, por lo tanto para saber dónde está es necesario tener algo que
referencie la posición del elemento. Y así sucesivamente

Una estructura con esta organización es una estructura enlazada.


Esta organización también tiene ventajas y desventajas. La desventaja más importante es no permitir accesos directos (al
menos tan fácilmente con los vectores), sino a través de crear estructuras complejas para el tratamiento de los datos (los
llamados Tipos Abstractos de Datos: listas, listas combinadas, árboles y grafos)

Entre las ventajas vimos que podemos mantener un orden lógico (no físico) sin necesidad de estar ordenando
permanentemente.

Otra ventaja importante es poder mantener más de un orden lógico. En el ejemplo discutimos acerca de la información
que posee cada estudiante (además de sus propios datos personales). Evaluamos que debe conocer quién llegó después.
Y que en el pizarrón debe estar escrito quién es el o la primer estudiante que llegó.

¿Qué pasa si además de conocer quién llegó después, sabe quién es el o la estudiante que lo sucede en orden
alfabético?. Por supuesto en el pizarrón también está escrito el nombre y apellido del o la estudiante con menor nombre
y apellido.
Así podremos "recorrer" les estudiante por dos criterios diferentes: orden de llegada;
nombre y apellido
Las estructuras enlazadas tienen la particularidad que se necesita conocer la posición del primer elemento
y en cada posición se tiene un registro, llamado nodo, con al menos dos campos, uno que contenga la
información y otro que indique o referencie dónde se encuentra el siguiente que no necesariamente debe
ser el contiguo.

Un nodo es una estructrura (registro) que contiene datos y punteros. Un puntero es una
referencia al siguiente nodo. De esta forma se puede ir enlazando la información.

Si, como en el ejemplo anterior queremos más de un criterio para el recorrido,


deberemos tener más de un puntero.
Este concepto amplía el abanico de problemas a resolver. Claramente lo podemos pensar como un tipo abstracto de
datos donde cada elemento tiene sus propios datos, más información relevante para la navegación. Así podemos
pensar cuáles son las funciones básicas que permiten operar con estos.

Algunas ya están implícitas en los ejemplos: agregar un nuevo elemento; sacar un elemento.

Debemos tener en cuenta que agregar un elemento implica –además de registrar los datos- indicar a quién o quiénes
corresponde información que ayude al recorrido.. He ahí una tercer función: recorrer. Y hay más, por ejemplo
buscar.
Existen diversos tipos de punteros. Aquellos que apuntan a Los punteros son tipos de datos, en el caso de C/C++ se
un tipo de datos simples y otros que apuntan a estructuras o disponen de dos operadores:
registros.
- el operador de dirección (*) y
Presentamos a continuación un ejemplo de puntero a una - el operador de indirección o de desreferenciación (&)
variable simple y los operadores mediante los cuales se
declara y accede a los datos. Por ejemplo:

Recordemos que ya utilizamos algunos de estos conceptos. int a = 10;


Por ejemplo para pasar el nombre de un archivo a una int *p = &a;
función abrirArchivo, trabajamos con el parámetro char * Nombre Tipo Valor
nbe
a entero 10
Para definir una variable de tipo archivo usamos la sintaxis p punter dirección de a
FILE * f; o
Para leer un registro de un archivo usamos el operador de *p entero 10 (es el valor del
desreferenciación (&) para indicar el registro de dónde o hacía
dónde transferir contenido de la
dirección a dónde
fread(&registro, sizeof(registro), 1, f) ;
apunta a)
Como se mencionó anteriormente, el uso de memoria dinámica es de gran utilidad para definir estructuras repetitivas
(NO SE TRATA DE VECTORES).
En este caso los algoritmos deberán pedir memoria para cada dato (recordar que es de tipo registro). Es importante que
cuando se deja de usar la memoria se debe devolver al sistema.
Para trabajar con memoria dinámica vamos a necesitar las operaciones “asignar” y “liberar” memoria, que son cumplidas por
las funciones new y delete de C++ y además algún tipo de dato que nos permita acceder a estas variables dinámicas.
Este tipo de dato es el que llamaremos “tipo puntero”.

En un algoritmo, podremos crear variables dinámicas de cualquier tipo mediante la instrucción new.

Cada variable creada dinámicamente va a ocupar un lugar en la memoria y como no tienen un identificador asignado
previamente en el algoritmo, la instrucción new devuelve cuál es la posición de memoria en que fue creada para poder
almacenar esta dirección en la variable de tipo puntero y luego poder acceder a la variable dinámica por medio de este
puntero.
Veamos un ejemplo con una variable dinámica de tipo registro:
Int main(){
struct tRPer{ La instrucción new pide memoria al sistema u
int dni; tRPer * rPer;
devuelve la dirección de la memoria obtenida.
str60 nombre; pPer = NULL;
}; rPer = new tPRer ; Mediante el operador -> se accede a la
rPer->dni = 50987654; información en la memoria de cada campo de la
strcpy(rPer->nombre, "Juan Perez") ; estructura
printf("Dni %d Nombre %s\n", rPer->dni, rPer- Mediante la constante NULL indicamos que la
>nmbre) ;
variable puntero está "vacía".
};
TAD Pilas
Recordemos que la característica principal de un Tipo Abstracto de Datos es definir un comportamiento,
seleccionar un tipo de datos (repetitivo) y desarrollar las funciones "primitivas" llamadas comúnmente
herramientas públicas.

Esto quiere decir que las aplicaciones (algoritmos) podrán definir una variable de este nuevo tipo y utilizar
las funciones sin conocer el tipo de datos dónde se alojan los valores ni tampoco la forma en que están
desarrolladas las herramientas.

Una pila se comporta de acuerdo a cómo llegan y cómo salen los datos, esto lo Last Input, First Output
(LIFO por sus siglas en inglés). Por lo tanto podríamos pensar que a una "variable" de tipo PILA se
ingresa y se saca la información por el mismo lugar (llamado cabeza).

Entonces, se puede decir que una pila es un conjunto de elementos homogéneos (todos
del mismo tipo) que solamente puede crecer o decrecer por la parte superior (llamada
cabeza) de la pila, o sea, sólo por uno de sus extremos.

Esto quiere decir que el orden de los elementos en la PILA está relacionados con el
orden de llegada.
Herramientas necesarias para la definición del TAD Pilas
Hasta el momento se mencionaron dos
Esquema del comportamiento de
herramientas:
un Tipo Abstracto de Datos Pila (1)
- Poner en la pila: agrega un elemento a la Pila
- Sacar de la pila: saca un elemento de la pila

Debemos recordar que se trata de un tipo


abstracto de datos y que este se define por su
comportamiento, por lo cual cada vez que una
aplicación utilice la función sacar, sacará el
último elemento ingresado a la pila.
Por el momento no importa cómo se implementan estas funciones, por lo tanto
tampoco debemos intentar pensar cómo están organizados los datos en la
memoria.
(1) Fuente: material de consulta y animaciones Instituto de Tecnología ORT , del cual fui coautor (http://campus.ort.edu.ar/descargar/articulos/172851/Colas)
En la animación se puede observar el
comportamiento de una pila.
Al apilar C, éste
Aparecen las funciones mencionadas tapa a B. En tope
Volvemos
ahora a
(poner, sacar) y funciones aún no En tope, a
encontrar
encontramo
definidas: A
B
C En tope,
ahora,
s aB C.
en Tope.
Tanto
Al desapilar entonces,
vemos a B,
- ver si está vacía, C B como A
C, el tope de yse ve A.
A queda
- inicializar quedan
la pila inaccesible.
inaccesibles.
“desciende”. Tope
Pensemos: (la pila vista desde arriba)
B
¿hasta cuándo podemos sacar? La pila
¿podemos poner en una pila si no Al apilar el está
Podemos vacía.
seguir
elemento B,así hasta que
sabemos qué tiene (la primera vez)?
la apilar
Al pilaéste
quede completamente
elqueda
vacía,
elemento AúnoAhasta
“sobre” seobtener
no el ha el
Entonces, antes de comenzar a poner A
elemento
la pila deja que
apilado
elemento deseamos.
ningún
datos en una pila se debe inicializar la
elemento.
anterior.
de estar
misma.
Antes de sacar de una pila se debe vacía.
detectar si quedan elementos.
(hacer click para continuar con la animación)
Aplicaciones de las Pilas.
Las pilas tienen muchas aplicaciones en informática.
Por ejemplo se usan para: Evaluar expresiones aritméticas; analizar sintaxis; Cualquier otro problema que
trate la información en sentido inverso a como llega la misma.

Conociendo el comportamiento de una Pila podemos aplicar este recurso en diversas oportunidades, solo
hace falta pensar el problema y la estrategia de resolución.

Una solución posible es utilizar un TAD Pila, analizar la expresión


Veamos el siguiente ejemplo: carácter a carácter y cada vez que nos encontramos con un símbolo
Disponemos de una expresión algebraica que contiene que abre, lo apilamos (poner pila) y cada vez que encontramos un
símbolos llaves "{}", corchetes "[]" y paréntesis "()". símbolo que cierra, desapilamos (sacar pila). En este caso se verifica
si el símbolo que se está analizando (cierra) se corresponde con el
desapilado (abre).
Debemos desarrollar un algoritmo que controle el
balanceo de los símbolos:
El algoritmo finaliza cuando se detecta un símbolo que no
1. debe haber tantos símbolos de cada tipo que corresponde, o cuando se acaba la frase a analizar. En este último
abran, como que cierren caso, si quedan símbolos en la pila, la expresión es incorrecta.
2. Los símbolos deben estar equilibrados, esto es
cada vez que llega un símbolo que cierra, debe ser Solamente es correcta cuando se termina de analizar la frase y la
el correspondiente al último que se abrió. pila queda vacía.
El algoritmo es el siguiente:
Si la frase es:
1. Crear una pila.
{2 + (3-5) * 4 + 4 * [20 – 2 * (1+3)]}
2. Leer el primer carácter.
3. Si el carácter es de apertura entonces Estado de la pila analizar el último
apilarlo. paréntesis {[(
4. Si es de cierre y la pila está vacía se
informa un error.
Descripción de los estados de la pila:
5. Si es de cierre y la pila no está vacía, Entra {; entra (; sale ( (cuando analiza el primer paréntesis que
sacar un elemento de la pila. Si este cierra); entra [; entra (; sale ( (cuando analiza el segundo
elemento no es el correspondiente paréntesis que cierra; sale [ (cuando analiza el corchete que
símbolo de apertura se informa un cierra: sale { (cuando analiza la lleve que cierra
error, sino desapilar.
6. Leer el próximo carácter y repetir los
pasos 4, 5 y 6 hasta el fin de los
Intenten describir otras expesiones y
datos. verifiquen (mediante la pila) si están
balanceadas.
Para concluir
En una pila, el último elemento agregado es el que está en la parte superior, en el tope, y el primero es el que
está en el fondo.
El único elemento visible, y por consiguiente al único que se puede acceder, es el que está en el tope.
Como ocurre con todo tipo abstracto de datos es importante recordar que la implementación de la pila
dependerá del lenguaje que se utilice, de las herramientas que este posea y de la forma en que se decida
implementar.

Esto quiere decir que cuando resolvemos un problema utilizando un TAD, debemos hacerlo
independientemente de cómo vaya a ser su implementación, por lo tanto el acceso a los datos de la
estructura va a estar permitido sólo a las operaciones básicas, quedando restringido para los programas.

De esta manera se encapsula la implementación y cualquier modificación en la misma, no


implica modificación de los programas que la utilizan.

Más adelante veremos cómo implementar las herramientas y la estructura de datos para
el Tipo Abstracto de Datos PILA
Implementación del TAD Pilas
mediante el uso de memoria dinámica
y
templates
Algoritmos de poner pila (push) y sacar pila (pop)

Una pila es un tipo abstracto de datos (TDA) donde se "guarda" temporalmente información.

La característica de este TDA es que el último elemento en ingresar es el primer elemento que debe salir. Por lo tanto
podemos pensar que hay una única "puerta" de entrada/salida.

En la figura se observa una pila que está "apuntada"


por la variable de tipo TDAPila cuyo identificador es
colores. colores rojo verde azul

Esta variable "apunta" a un nodo cuyo contenido es el


último ingresado y será el primero en salir

El algoritmo poner en la pila (push) consiste en las siguientes El algoritmo sacar de la pila (pop) consiste en las siguientes
acciones: acciones:
1. obtener memoria para el nuevo nodo (en donde estará el nuevo dato) 1. apuntar al nodo a liberar y copiar el dato en la variable (parámetro) de
2. enganchar el nuevo nodo al que hasta ese momento era el primero (en salida
la imagen si queremos agregar negro, debemos enganchar el nodo que 2. enganchar la variable de tipo TDAPila colores al siguiente nodo del que
contiene negro con el nodo que contiene rojo) sale (en la figura verde)
3. enganchar la variable de tipo TDAPila (en el ejemplo colores) al nuevo 3. liberar memoria del nodo cuyo valor salió (en el ejemplo rojo), el cual
nodo está apuntado en el punto 1
Gráfico con acciones sobre una pila
Acción Operación Gráficamente

Definición de una TDAPila <string> colores; basusa El contenido de colores es


variable de tipo TDAPila string color; colores "basura"

Inicializar Pila colores.inicializar(); NULL El contenido de colores es NULL


colores
pAux es una variable de tipo TDAPila,
Agregar color azul colores.push("azul"); colores se usa para obtener nodo y cuando
este se "engancha" en la pila, pAux no
pAux azul se usa más

azul Se crea un nuevo nodo, se engancha


Agregar color verde colores.push("verde");
colores este al verde (apuntado por "colores")
y luego se engancha "colores" al
pAux verde nuevo nodo.

Se crea un nuevo nodo, se engancha


Agregar color rojo colores.push("rojo"); colores verde azul este al verde (apuntado por "colores")
y luego se engancha "colores" al
pAux rojo nuevo nodo.

colores rojo verde azul Se saca un elemento de la pila, sale el


Sacar un elemento colores.pop(color); elemento "rojo". Se apunta con la
---> Sale el "rojo" pAux
variable colores al elemento siguiente
dell "rojo", es el elemento "verde".
Para poder implementar una pila vamos a necesitar un conjunto de nodos. Recordar que un nodo es una estructura que contiene un
dato (puede ser de tipo estructura) y una referencia al siguiente nodo de la pila. Esta referencia debe contener una dirección en
memoria, por lo cual se trata de una variable de tipo puntero a la estructura. Esto se llama una definición recursiva.
Definición del nodo de una El C++ permite definir una estructura que contenga datos y funciones (es parecido a la programación
pila, está compuesto por un orientada a objetos, pero no lo es). Mediante esta característica podremos "agrupar" las funciones
campo info del tipo TS (es el con los datos, permitiendo así darle un comportamiento al dato.
definido por el template) y un
campo puntero al siguiente Suena raro, pero no lo es. Recordemos que para que sea un tipo abstracto de datos debe tener una
nodo de la pila estructura de datos que contenga a los datos, más un conjunto de funciones que operen sobre los
template <typename TS> datos de acuerdo a un comportamiento determinado.
struct pNodo {
TS info ; Las funciones que hemos definido son: poner (push), sacar (pop), verVacia. Podemos agregar una
pNodo<TS> * pSig ; nueva que la denominaremos verProximo que nos permite conocer cuál es el próximo dato a salir, sin
}; tener necesidad de sacarlo.
bool push(TS d) { bool pop(TS & d) { bool verProximo(TS & d) {
pNodo <TS> * p = new pNodo<TS>(); pNodo <TS> * p = pila ; pNodo <TS> * p = pila ;
if (p!=NULL) { if (pila!=NULL) { if (pila!=NULL) {
p->info = d ; d = p->info ; d = p->info ;
pila = p->pSig ; return true ;
p->pSig = pila;
} else return false ;
pila = p ; delete (p) ;
};
return true ; return true ;
} else return false ; } else return false ; void inicializar() { pila = NULL; }
}; }; bool vacia(){return pila == NULL ;}
Veamos el código unido en una "súper estructura" llamada TDAPila, la cual permite encapsular totalmente
las operaciones.
template <typename TS>
struct TDAPila {

// es global a la estructura, entonces se utiliza en bool verProximo(TS & d) {


los métodos asociados al TDA Pilas pNodo <TS> * p = pila ;
pNodo<TS> * pila ; // es la llamada "cabeza" if (pila!=NULL) {
d = p->info ;
bool push(TS d) { return true ;
pNodo <TS> * p = new pNodo<TS>(); } else return false ;
if (p!=NULL) { };
p->info = d ;
p->pSig = pila;
pila = p ; void inicializar() {
return true ; pila = NULL;
} else return false ; }
};
bool vacia(){return pila == NULL ;} ;
bool pop(TS & d) {
pNodo <TS> * p = pila ;
/* constructor, se ejecuta cada vez que se crea una variable .
if (pila!=NULL) {
d = p->info ; Este proceso se ejecuta "automáticamente" al definir una variable de
pila = p->pSig ; tipo TDAPila. No se indica que devuelva nada, ya que tiene una
delete (p) ; denominación especifica "constructor" */
return true ; TDAPila() { inicializar();};
} else return false ; };
};
En la filmina anterior se definió una estructura que contiene una variable global a la estructura (todos los
procesos descriptos en la estructura la pueden manipular) y las funciones necesarias para trabajar con una
pila.
Cada vez que definimos una "variable" de este tipo, se reserva memoria para el puntero a la "cabeza" de la
pila y se definen las funciones asociadas al comportamiento de pila.
Se ejecuta "automáticamente" un proceso de inicialización de la pila.
En el siguiente ejemplo vemos TDAPila <int> pila; Se define una variable de
cómo trabajar con pilas. En int x; tipo TDAPila, donde cada
nodo contendrá un
primer lugar definimos una pila cout << "Pilas de enteros" <<endl; número entero.
de números enteros y asignamos // asignamos valores a la pila
valores a la pila (1, 2, 3 y 4, en pila.push(1); Se "apilan" los valores 1, 2,
pila.push(2); 3 y 4 en ese orden
ese orden). Luego recorremos la
pila sacando y mostrando esos pila.push(3); Se recorre la pila (se sacan)
pila.push(4); y se muestran los valores
valores.
// mostramos la pila
while (!pila.vacia()) {
Prestar especial atención a la pila.pop(x) ;
forma de trabajar con las cout << x << endl;
funciones. };
Si prestamos atención a este nuevo ejemplo, se define una "variable" cuyo identificador es "pila2" y al ser
un tipo con template, se indica que los datos de esta pila son de tipo string.
Para agregar elementos a la pila utilizamos la función "push", y por estar definida dentro de la estructura
TDAPilas, se la invoca mediante el nombre de la variable definida, seguida del opeador "." (punto). El
parámetro que pasamos es el valor a apilar.
Para recorrer la pila, extraemos mediante la función "pop" hasta que la pila esté vacía.

En el siguiente ejemplo vemos TDAPila <string> pila2; Se define una variable de


string z; tipo TDAPila, donde cada
cómo trabajar con pilas. En nodo contendrá una
primer lugar definimos una pila cout << "Pilas de string" <<endl; cadena de caracteres.
de string y asignamos valores a // armamos una pila de string
la pila (Julieta, Martina, Facundo, pila2.push("Julieta"); Se "apilan" los valores
Julieta, Martina, Facundo,
pila2.push("Martina");
Ignacio, en ese orden). Luego Ignacio en ese orden
pila2.push("Facundo");
recorremos la pila sacando y pila2.push("Ignacio"); Se recorre la pila (se sacan)
mostrando esos valores. // mostramos la pila 2 y se muestran los valores
while (!pila2.vacia()) {
Prestar especial atención a la pila2.pop(z) ;
forma de trabajar con las cout << z << endl;
funciones. };
Si prestamos atención a este nuevo ejemplo, se define una "variable" cuyo identificador es "pila2" y al ser
un tipo con template, se indica que los datos de esta pila son de tipo registro.
Para agregar elementos a la pila utilizamos la función "push", y por estar definida dentro de la estructura
TDAPilas, se la invoca mediante el nombre de la variable definida, seguida del opeador "." (punto). El
parámetro que pasamos es el valor a apilar.
Para recorrer la pila, extraemos mediante la función "pop" hasta que la pila esté vacía.
Se define una variable de
En el siguiente ejemplo vemos cómo struct tReg { tipo TDAPila, donde cada
trabajar con pilas. int orden ;
nodo contendrá un
string nbe ;
string mail; registro.
En primer lugar definimos una pila de };
estructuras TDAPila <tReg> pila3; Se "apilan" los valores de:
y asignamos valores a la pila (datos de tReg r; Julieta, Martina, Facundo,
cout<<"Pilas con registros" <<endl; Ignacio en ese orden
Julieta, Martina, Facundo, Ignacio, en
r = {1,"Julieta","m@m1" }; pila3.push(r);
ese orden). r = {2,"Martina","m@m2" }; pila3.push(r); Se recorre la pila (se sacan)
r = {3,"Facundo","m@m3" }; pila3.push(r);
y se muestran los valores
Luego recorremos la pila sacando y r = {4,"Ignacio","m@m3" }; pila3.push(r);
// mostramos la pila 2
mostrando esos valores.
while (!pila3.vacia()) {
pila3.pop(r) ;
Prestar especial atención a la forma de cout << r.orden << " " << r.nbe << " " << r.mail << endl;
trabajar con las funciones. };
Un ejercicio usando el Tipo Abstracto de Datos Pila (TDAPila)

Veamos ahora cómo implementar el El algoritmo es el siguiente:


problema de analizar una expresión 1. Crear una pila.
algebraica. 2. Leer el primer carácter.

Recordemos que disponemos de una expresión algebraica 3. Si el carácter es de apertura entonces


que contiene símbolos llaves "{}", corchetes "[]" y apilarlo.
paréntesis "()". 4. Si es de cierre y la pila está vacía se
informa un error.
Debemos desarrollar un algoritmo que controle el balanceo 5. Si es de cierre y la pila no está vacía,
de los símbolos: sacar un elemento de la pila. Si este
1. debe haber tantos símbolos de cada tipo que abran, elemento no es el correspondiente
como que cierren símbolo de apertura se informa un error,
sino desapilar.
2. Los símbolos deben estar equilibrados, esto es cada vez
que llega un símbolo que cierra, debe ser el 6. Leer el próximo carácter y repetir los
correspondiente al último que se abrió. pasos 4, 5 y 6 hasta el fin de los datos.

Ahora vamos a ver cómo resolver este problema mediante el TDAPila


void analizarExpresiones(){ bool expresionAritmetica(string expresion) {
string expresion ; bool ok = true ;
expresion ="{5+4*[6-3*(2+1) - (6*3)]+4*(2-2)}"; char carEntra = char(62);
if (expresionAritmetica(expresion)) char carSale = char(60);
cout<<"la expresion " << expresion << " es correcta"<<endl; int i=0;
else
TDAPila <char> pilaSimbolos;
cout<<"la expresion " << expresion << " es INcorrecta"<<endl; char simbolo;

expresion ="{5+4*[6-3*(2+1) - (6*3)+4*(2-2)}"; while ((i<expresion.length())&&ok){


if (expresionAritmetica(expresion)) if (esSimboloAbre(expresion[i])){
cout<<"la expresion " << expresion << " es correcta"<<endl; pilaSimbolos.push(expresion[i]);
else cout<<carEntra<<expresion[i]<<" ";
cout<<"la expresion " << expresion << " es INcorrecta"<<endl; } else
if (esSimboloCierra(expresion[i])){
expresion ="{5+4*[6-3*(2+1) - (6*3)}+4*(2-2)]"; pilaSimbolos.pop(simbolo);
if (expresionAritmetica(expresion)) cout<<carSale<<simbolo<<" "; //<<expresion[i]<<" ";
cout<<"la expresion " << expresion << " es correcta"<<endl; ok = ((expresion[i]=='}' && simbolo =='{') ||
(expresion[i]==']' && simbolo =='[') ||
else (expresion[i]==')' && simbolo =='('));
cout<<"la expresion " << expresion << " es INcorrecta"<<endl; }
bool esSimboloAbre(char c){ i++;
}
return (c=='{' || c=='[' || c=='('); cout<<endl;
} ok = ok && pilaSimbolos.vacia();
bool esSimboloCierra(char c){ return ok;
return (c=='}' || c==']' || c==')'); }
}
TAD Colas
Recordemos nuevamente que la característica principal de un Tipo Abstracto de Datos es definir un
comportamiento, seleccionar un tipo de datos (repetitivo) y desarrollar las funciones "primitivas" llamadas
comúnmente herramientas públicas.

Esto quiere decir que las aplicaciones (algoritmos) podrán definir una variable de este nuevo tipo y utilizar las
funciones sin conocer el tipo de datos dónde se alojan los valores ni tampoco la forma en que están
desarrolladas las herramientas.

Una cola se comporta de acuerdo a cómo llegan y cómo salen los datos, esto lo First Input, First Output
(FIFO por sus siglas en inglés). Por lo tanto podríamos pensar que a una "variable" de tipo COLA se ingresa
y se saca la información por el diferentes lugar (llamados frente y fondo para determinar por donde
salen y por donde ingresan respectivamente).

Entonces, se puede decir que una cola es un conjunto de elementos homogéneos (todos
del mismo tipo) en donde los nuevos elementos se agregan por un extremo (el final) y se
sacan por otro extremo (el frente). Esta estructura también se la conoce como estructura
FIFO (First In, First Out), lo que significa “Primero en llegar, Primero en salir”.

Esto quiere decir que el orden de los elementos en la COLA está relacionados con el orden
de llegada.
Herramientas necesarias para la definición del TAD Pilas
Hasta el momento se mencionaron dos
Esquema del comportamiento de
herramientas:
un Tipo Abstracto de Datos Colas
(1)
- Poner en la cola: agrega un elemento a la Cola
- Sacar de la cola: saca un elemento de la Cola
Por aquí entran Por aquí
Debemos recordar que se trata de un tipo los elementos salen

abstracto de datos y que este se define por su


comportamiento, por lo cual cada vez que una
Fondo Frente
aplicación utilice la función sacar, sacará el
primer elemento ingresado a la cola.

Por el momento no importa cómo se implementan estas funciones, por lo tanto tampoco debemos
intentar pensar cómo están organizados los datos en la memoria.
(1) Fuente: material de consulta y animaciones Instituto de Tecnología ORT , del cual fui coautor (http://campus.ort.edu.ar/descargar/articulos/172851/Colas)
En la animación se puede observar el
comportamiento de una cola.

Aparecen las funciones mencionadas


(poner, sacar) y funciones aún no
A
C
B
definidas:
A
- ver si está vacía,
- inicializar

Pensemos: Lo mismoArrancamos
pasa con C, con
Ya tenemos
Al extraer
que unel encolado
Alqueda
agregar B, vacía, sin
la colaQuedaría al
¿hasta cuándo podemos sacar? primer
primer éste
elementoqueda
detrás de B.
elementos.
¿podemos poner en una cola si no elemento
insertado,detrás
C deberáB pasa
esperar A. afinal,
dePodemos queseguir así hasta
sabemos qué tiene (la primera vez)? agregado a la encolado
alse
frente.
desencolen En Alaycabecera
B
vaciar la cola.
cola. detrás de C.
C
A
B paraB,llegar
ahora,A, alqueda
frente.
Pero, sivemos
elPodemos
no
primerviniese
en lavernuevamente
el
Entonces, antes de comenzar a poner A y mientras
loelemento
agregásemos
cabecera,
elemento elemento CA en a la cola,
agregado,
datos en una cola se debe inicializar la ¿en laqué posición
misma. sigue
sigue oculto
al cabecera
alguno.
frente;
tras B (alquedaría?
éste,queda frente).
e inaccesible.
oculto e
Antes de sacar de una cola se debe
detectar si quedan elementos. inaccesible.
(hacer click para continuar con la animación)
Para concluir
En unacola, el último elemento agregado es el que está en el fondo, y el primero es el que está en el
frente.
El único elemento visible, y por consiguiente al único que se puede acceder, es el que está en el frente.
Como ocurre con todo tipo abstracto de datos es importante recordar que la implementación de la cola
dependerá del lenguaje que se utilice, de las herramientas que este posea y de la forma en que se decida
implementar.

Esto quiere decir que cuando resolvemos un problema utilizando un TAD, debemos hacerlo
independientemente de cómo vaya a ser su implementación, por lo tanto el acceso a los datos de la
estructura va a estar permitido sólo a las operaciones básicas, quedando restringido para los programas.

De esta manera se encapsula la implementación y cualquier modificación en la misma, no


implica modificación de los programas que la utilizan.

Más adelante veremos cómo implementar las herramientas y la estructura de datos para
el Tipo Abstracto de Datos COLA
Implementación del TAD Colas
mediante el uso de memoria dinámica
y
templates
Algoritmos de poner cola (encolar) y sacar cola (desencolar)

Una cola es un tipo abstracto de datos (TDA) donde se "guarda" temporalmente información.

La característica de este TDA es que el primer elemento en ingresar es el primer elemento que debe salir. Por lo tanto
podemos pensar que hay dos "puertas", una de entrada (fondo) y otra de salida (frente).

En la figura se observa una cola que está "apuntada"


por las variables de tipo TDACola cuyo identificador colores.fte
es colores, y tiene dos "campos" fte y fdo. colores.fdo rojo verde azul
La variable fte apunta al nodo que debe salir. La
variable fdo apunta al último nodo ingresado.

El algoritmo sacar de la cola (desencolar) consiste en las


El algoritmo poner en la cola (encolar) consiste en las
siguientes acciones:
siguientes acciones: 1. apuntar al nodo a liberar (está apuntado por la variable fte) y copiar el
dato en la variable (parámetro) de salida
1. obtener memoria para el nuevo nodo (en donde estará el nuevo dato) 2. enganchar la variable fte al siguiente nodo del que sale (en la figura
2. enganchar el nuevo nodo al nodo que está apuntado la variable fdo. verde). Tener en cuenta que si es el último, se debe poner en NULL la
3. enganchar la variable fdo al nuevo nodo, teniendo en cuenta que si es variable fdo.
el primero también se debe enganchar a la variable fte. 3. liberar memoria del nodo cuyo valor salió (en el ejemplo azul), el cual
está apuntado en el punto 1
Gráfico con acciones sobre una cola

Acción Operación Gráficamente

colores.fte basusa
Definición de una variable TDACola <string> colores; El contenido de colores.fte y
de tipo TDACola string color; colores.fdo basusa colores.fdo es "basura"

Inicializar Cola colores.inicializar(); colores.fte . NULL El contenido de colores.fte y


colores.fdo NULL colorres.fdo es NULL
pAux es una variable de tipo TDACola, se
colores.fte
Agregar color azul colores.encolar("azul"); usa para obtener nodo y cuando este se
colores.fdo "engancha" en la cola, pAux no se usa
más. En este caso se enganchar frente y
pAux azul fondo

colores.fte azul Se crea un nuevo nodo, se engancha el


Agregar color verde colores.encolar("verde"); colores.fdo fondo y el apuntado por frente se
engancha al nuevo.
pAux verde

colores.fte azul Se crea un nuevo nodo, se engancha al


Agregar color rojo colores.encolar("rojo"); verde fondo, y el que antes era fondo debe
colores.fdo apuntar al nuevo nodo.
pAux rojo
colores.fte Se saca un elemento de la cola, sale el
Sacar un elemento colores.desencolar(color);
colores.fdo rojo verde azul elemento apuntado por frente. Luego
---> Sale el "azul" frente apunta al siguiente del que salió.
pAux En caso de quedar vacía se debe indicar
con NULL en fondo..
Para poder implementar una cola vamos a necesitar un conjunto de nodos. Recordar que un nodo es una estructura que contiene un
dato (puede ser de tipo estructura) y una referencia al siguiente nodo de la ola. Esta referencia debe contener una dirección en
memoria, por lo cual se trata de una variable de tipo puntero a la estructura. Esto se llama una definición recursiva.
Definición del nodo de una El C++ permite definir una estructura que contenga datos y funciones (es parecido a la programación
pila, está compuesto por un orientada a objetos, pero no lo es). Mediante esta característica podremos "agrupar" las funciones con
campo info del tipo TS (es el los datos, permitiendo así darle un comportamiento al dato.
definido por el template) y un
campo puntero al siguiente Suena raro, pero no lo es. Recordemos que para que sea un tipo abstracto de datos debe tener una
nodo de la pila estructura de datos que contenga a los datos, más un conjunto de funciones que operen sobre los
template <typename TS> datos de acuerdo a un comportamiento determinado.
struct pNodo {
TS info ; Las funciones que hemos definido son: encolar, desencolar, vacía. Podemos agregar una nueva que la
pNodo<TS> * pSig ; denominaremos verProximo que nos permite conocer cuál es el próximo dato a salir, sin tener
}; necesidad de sacarlo.
bool encolar(TS d) { bool desencolar(TS & d) { bool verProximo(TS & d) {
pNodo <TS> * p = new pNodo<TS>(); pNodo <TS> * p = fteCola ; // apunta pNodo <TS> * p = fteCola ; // apunta al que va a
if (p!=NULL) { al que va a salir salir
p->info = d ;
p->pSig = NULL;
if (p!=NULL) { if (p!=NULL) {
if (fdoCola != NULL) d = p->info ; d = p->info ;
fdoCola->pSig = p; // debido a que la cola no está fteCola = p->pSig ; // esto se hace return true ;
vacía, el anterior ingresado se apunta al nuevo siempre } else return false ;
else delete (p) ; // libera memoria };
fteCola = p ; // la cola estaba vacía , el fte se apunta if (fteCola == NULL) fdoCola = NULL ; void inicializar() {
al nuevo
fdoCola = p ; //siempre se modifica en encolar
// si se vació, entonces el fondo es NULL fteCola = NULL; fdoCola = NULL;
return true ; }
return true ;
} else return false ; } else return false ;
bool vacia(){return fteCola == NULL ;}
}; };
Veamos el código unido en una "súper estructura" llamada TDACola, la cual permite encapsular
totalmente las operaciones.
template <typename TS>
struct TDACola {

pNodo<TS> * fteCola ; // es global a la estructura, entonces se


utiliza en los métodos asociados al TDA Colas
pNodo<TS> * fdoCola ; // es global a la estructura, entonces se utiliza en los bool verProximo(TS & d) {
métodos asociados al TDA Colas
pNodo <TS> * p = fteCola ; // apunta al elemento que va a salir
bool encolar(TS d) { if (p!=NULL) {
pNodo <TS> * p = new pNodo<TS>(); d = p->info ;
if (p!=NULL) {
p->info = d ; return true ;
p->pSig = NULL; } else return false ;
if (fdoCola != NULL)
fdoCola->pSig = p; // debido a que la cola no está vacía, el anterior };
ingresado se apunta al nuevo void inicializar() {
else
fteCola = p ; // la cola estaba vacía , el fte se apunta al nuevo fteCola = NULL; fdoCola = NULL;
fdoCola = p ; //siempre se modifica en encolar }
return true ;
} else return false ; /* constructor, se ejecuta cada vez que se crea una variable
}; este proceso se ejecuta "automáticamente" al definir una variable
de tipo TDACOla.
bool desencolar(TS & d) { No se indica que devuelva nada, ya que tiene una
pNodo <TS> * p = fteCola ; // apunta al que va a salir
if (p!=NULL) { denominación especifica "constructor" */
d = p->info ; TDACola() { inicializar(); };
fteCola = p->pSig ; // esto se hace siempre
delete (p) ; // libera memoria
if (fteCola == NULL) fdoCola = NULL ; // si se vació, entonces el fondo es NULL bool vacia(){return fteCola == NULL ;} ;
return true ;
} else return false ;
};
};
En la filmina anterior se definió una estructura que contiene una variable global a la estructura (todos los
procesos descriptos en la estructura la pueden manipular) y las funciones necesarias para trabajar con una
cola.
Cada vez que definimos una "variable" de este tipo, se reserva memoria para el puntero "frente" y el
"fondo" de la cola y se definen las funciones asociadas al comportamiento de cola.
Se ejecuta "automáticamente" un proceso de inicialización de la cola.
En el siguiente ejemplo vemos Se define una variable de
TDACola <int> cola;
tipo TDACola, donde cada
cómo trabajar con colas. En int x1; nodo contendrá un
primer lugar definimos una cola cola.encolar(1); número entero.
de números enteros y asignamos cola.encolar(2);
Se "encolan" los valores 1,
valores a la pila (1, 2, 3 y 4, en cola.encolar(3);
2, 3 y 4 en ese orden
ese orden). Luego recorremos la cola.encolar(4);
pila sacando y mostrando esos Se recorre la cola (se
valores. // mostramos la cola 1 sacan) y se muestran los
while (!cola.vacia()) { valores
cola.desencolar(x1) ;
Prestar especial atención a la cout << x1 << endl;
forma de trabajar con las };
funciones.
Si prestamos atención a este nuevo ejemplo, se define una "variable" cuyo identificador es "cola2" y al ser
un tipo con template, se indica que los datos de esta cola son de tipo string.
Para agregar elementos a la cola utilizamos la función "encolar", y por estar definida dentro de la
estructura TDAolas, se la invoca mediante el nombre de la variable definida, seguida del opeador "."
(punto). El parámetro que pasamos es el valor a encolar.
Para recorrer la cola, extraemos mediante la función "desencolar" hasta que la cola esté vacía.

En el siguiente ejemplo vemos TDACola <string> cola2; Se define una variable de


string z1; tipo TDACola, donde cada
cómo trabajar con colas. En nodo contendrá una
primer lugar definimos una cola cola2.encolar("Julieta"); cadena de caracteres.
de string y asignamos valores a cola2.encolar("Martina");
cola2.encolar("Facundo"); Se "encolan" los valores
la pila (Julieta, Martina, Facundo, de: Julieta, Martina,
cola2.encolar("Ignacio");
Ignacio, en ese orden). Luego Facundo, Ignacio en ese
recorremos la cola sacando y // se muestran los valores de la
orden
mostrando esos valores. cola 2 Se recorre la cola (se
while (!cola2.vacia()) { sacan) y se muestran los
Prestar especial atención a la cola2.desencolar(z1) ; valores
forma de trabajar con las cout << z1 << endl;
funciones. };
Si prestamos atención a este nuevo ejemplo, se define una "variable" cuyo identificador es "cola3" y al ser
un tipo con template, se indica que los datos de esta cola son de tipo registro.
Para agregar elementos a la ola utilizamos la función "encolar", y por estar definida dentro de la estructura
TDAColas, se la invoca mediante el nombre de la variable definida, seguida del opeador "." (punto). El
parámetro que pasamos es el valor a encolar.
Par recorrer la cola, extraemos mediante la función "desencolar" hasta que la cola esté vacía.
Se define una variable de
En el siguiente ejemplo vemos cómo struct tReg { tipo TDACola, donde cada
trabajar con colas. int orden ; nodo contendrá un
string nbe ;
registro.
string mail;
En primer lugar definimos una cola de };
estructuras TDACola <tReg> cola3; Se "encolan" los valores
y asignamos valores a la cola (datos de tReg r1; deJulieta, Martina,
cout<<"Colas con registros" <<endl; Facundo, Ignacio en ese
Julieta, Martina, Facundo, Ignacio, en
r1 = {1,"Julieta","m@m1" }; cola3.encolar(r1); orden
ese orden). r1 = {2,"Martina","m@m2" }; cola3.encolar(r1);
r1 = {3,"Facundo","m@m3" }; cola3.encolar(r1); Se recorre la cola (se sacan)
Luego recorremos la cola sacando y r1 = {4,"Ignacio","m@m3" }; cola3.encolar(r1);
y se muestran los valores
// mostramos los valores de la cola 2
mostrando esos valores.
while (!cola3.vacia()) {
cola3.desencolar(r1) ;
Prestar especial atención a la forma de cout << r1.orden << " " << r1.nbe << " " << r1.mail
trabajar con las funciones. <<endl;
};
Para ir cerrando este documento. Presentamos una nueva forma de "administrar" la información, en
donde definimos un orden que no es físico.

Un tipo abstracto de datos es un conjunto de datos y funciones que aplican sobre los datos. Y sobre
todo, definir un comportamiento de los datos. Por ejemplo establecer un orden de acuerdo a cómo
llegan. Así surgen los Tipos Abstractos de Datos Pila (LIFO) y Colas (FIFO).

Para implementar estos TDA se utilizó memoria dinámica, de forma tal de ir pidiendo memoria a medida
que se agregan los datos y se devuelve la memoria cuando se sacan los datos.

Trabajamos con templates e incorporamos encapsulamiento, de forma tal de "asociar" las funciones con
los datos.

Para concluir con este documento debemos mencionar que, al usar un TDA de Pila o Cola, se desconoce
su implementación y contenido Esto quiere decir "para conocer el contenido se debe sacar la
información del TDA". Entonces, para contar la cantidad de elementos de una pila o de una cola se la
debe "vaciar". Tener cuidado, si para contar solo vaciamos, entonces perdemos los datos. Entonces,
para contar se deber sacar y guardar temporalmente en otra estructura (pila o cola) y luego reponer
todo.

Como diría mi hijo "NO SE VALE" pasar la pila o la cola por valor y así poder vaciar "sin vaciar".

También podría gustarte