Documentos de Académico
Documentos de Profesional
Documentos de Cultura
- Pilas
- Colas
- Listas
Veremos a qué nos referimos con "tipos abstractos de datos" y cómo definir sus
estructuras en forma lógica.
Un adelanto:
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.
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:
- ¿Cuáles son las operaciones lógicas que puedo hacer sobre esa estructura?
Para implementar un TAD, debemos realizar las siguientes acciones:
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.
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
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.
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:
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
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.
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.
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.
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
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
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.
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.
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).
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"
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".