Story Transcript
Algoritmos y Programación II. Estructuras de Datos. Parte II. PILAS y COLAS. Pilas: Una pila es una estructura de datos a la cual se puede acceder solo por un extremo de la misma. Las operaciones de inserción y extracción se realizan a través del tope, por lo cual no se puede acceder a cualquier elemento de la pila. Se la suele llamar estructura L.I.F.O. como acrónimo de las palabras inglesas "last in, first out" (último en entrar, primero en salir). La pila se considera un grupo ordenado de elementos, teniendo en cuenta que el orden de los mismos depende del tiempo que lleven "dentro" de la estructura. Las pilas son frecuentemente utilizadas en el desarrollo de sistemas informáticos y software en general. Por ejemplo, el sistema de soporte en tiempo de compilación y ejecución del Pascal utiliza una pila para llevar la cuenta de los parámetros de procedimientos y funciones, variables locales, globales y dinámicas. Este tipo de estructuras también son utilizadas para traducir expresiones aritméticas o cuando se quiere recordar una secuencia de acciones u objetos en el orden inverso del ocurrido. Dada la definición teórica de una pila, podremos representar la misma en forma gráfica como se ve en la figura: La pila recién creada se encuentra 1 TOPE vacía, el tope apunta a nil. Se desean ingresar los elementos 5 1, 5 y 7 en ese orden. 7 nil TOPE El elemento '1' se ubica en (Se ve claramente como los elemento el "fondo" de la pila, mien− 5 se ingresan por "arriba" y se van tras que el siguiente, el '5', "apilando".) se ubica "sobre" él. 1 nil 7 TOPE Luego de dicha operación El tope apunta al elemento de la pila puede representarse 5 "arriba", y el enlace se realiza 1
como en la figura. Así de− desde él hacia el "fondo". cimos que se encuentra 1 "llena" nil Podemos de esta manera observar claramente que para obtener el elemento '5' que ingresamos anteriormente, al poder acceder únicamente a la pila por el tope (la parte "superior"), debemos retirar antes los elementos que se encuentren antes o "sobre" él. De ésta manera la acción de retirar elementos de la estructura podemos graficarla de la siguiente forma: TOPE Así, entonces, queda la elemento pila al retirar los elementos 5 retirado en forma secuencial. 7 1 nil El tope apunta al siguiente del elemento retirado. Si se quiere continuar retirando los elementos que se ingresaron con anterioridad, la pila quedará completamente vacía, y se ve claramente como los elementos se van repitiendo en el orden inverso en que se habían ingresado. Finalmente, cuando la pila se encuentre vacía, el tope apuntará a nil. 5 TOPE se retiran los elementos 1 en forma secuencial. nil 7 elemento retirado Ahora bien, una vez conocido el comportamiento de las pilas veremos como se definen las mismas y su forma de manejo, o "comportamiento" de la pila. Definición dinámica de una pila: Las operaciones que definen el comportamiento de una pila o primitivas son las siguientes: • Crear pila. • Insertar elemento. • Retirar elemento. • Pila vacía. • Vaciar pila. 2
La definición junto con la implementación de éste tipo de estructura, es conveniente realizarlas en una unidad de biblioteca, de este modo se mantiene el nivel de abstracción de la estructura. Unit Pilas; Interface type tipo_dato = ; tptr_nodo_pila = ^tnodo_pila tnodo_pila = record dato : tipo_dato; enlace : tptr_nodo_pila; end; {encabezamiento de las primitivas que utilizaremos en el tipo de dato PILA} Procedure Pila_crear (var pila : tptr_nodo_pila); {Pre: La pila nunca fue creada. Post: Se creo la pila y se encuentra vacía} Procedure Pila_insertar (var pila : tptr_nodo_pila; elem : tipo_dato; var error : boolean); {Pre: La pila existe. Post: Si error = false el elemento fue ingresado en el tope de la pila, si error = true no se ha ingresado el elemento, la pila se encuentra llena} Procedure Pila_retirar (var pila : tptr_nodo_pila; var elem : tipo_dato); {Pre:La pila fue creada y no está vacia. Post: El elemento del tope de la pila fue retirado y se encuentra en elem, la pila queda igual, sin el elemento que se encontraba en el tope} Procedure Pila_vaciar (var pila : tptr_nodo_pila); {Pre: La pila fue creada. Post: La pila se encuentra vacía} Function Pila_vacia (pila : tptr_nodo_pila); {Pre: La pila fue creada. 3
Post: Devuelve true si la pila se encuentra vacía, y sino false} Implementation Procedure Pila_crear (var pila : tptr_nodo_pila); begin pila := nil; end; Procedure Pila_insertar (var pila : tptr_nodo_pila; elem : tipo_dato; var error : boolean); var ptr_aux : tptr_nodo_pila; {se utiliza para insertar el nuevo elemento} begin error:= Maxavail < sizeof(tnodo_pila); if ( not error) then begin new (ptr_aux); ptr_aux^.dato := elem; ptr_aux^.enlace := pila; pila := ptr_aux; end end; Procedure Pila_retirar (var pila : tptr_nodo_pila; var elem : tipo_dato); var ptr_aux : tptr_nodo_pila; {se utiliza para eliminar el elemento que se encuentra en} {el tope} begin ptr_aux := pila; elem := pila^.dato; pila := pila^.enlace; dispose (ptr_aux);
4
end; Procedure Pila_vaciar (var pila : tptr_nodo_pila); var e : tipo_dato; begin while not(Pila_vacía(pila)) do Pila_retirar (pila,e) end; Function Pila_vacia (pila : tptr_nodo_pila); begin Pila_vacía := (pila = nil); end; end. De esta manera la pila, queda definida y lista para su utilización. Colas: Una cola es una colección de elementos homogéneos (almacenados en dicha estructura), en la misma se pueden insertar elementos por uno de los extremos, llamado frente, y retirar los mismos por el otro extremo, denominado final. Es importante aclarar que, tanto el frente como el final de la cola, son los únicos indicados para retirar e insertar elementos, respectivamente. Esto nos indica que no podemos acceder directamente a cualquier elemento de la cola, sino solo al primero, o sea el que está o se encuentra en el frente, y no se pueden insertar elementos en cualquier posición sino solo por el final, así el elemento insertado queda como último. Por esta razón la cola es denominada una estructura F.I.F.O., o simplemente una lista F.I.F.O., esto representa el acrónimo de las palabras inglesas first in, first out (primero en entrar, primero en salir). Gráficamente podemos representarla como: La cola fue recién creada y esta vacía. (frente y final apuntan FINAL FRENTE a nil). Si ahora le ingresamos el elemento A, la misma quedará se la siguiente manera: Como A es el único A elemento, frente y final apuntan a él. FINAL nil FRENTE Si a continuación se ingresa el elemento B, el frente de la cola continuará apuntando a A, pero ahora el final apuntará al elemento recién ingresado.
5
B A El enlace se realiza desde el frente hacia el final. FINAL nil FRENTE Al retirar un elemento, el frente apuntará al siguiente del elemento retirado y en el caso que la cola quedara vacía, frente y final apuntarán a nil. B A elemento retirado. FINAL nil FRENTE FINAL FRENTE nil nil Ahora bien, una vez conocido el comportamiento de las colas veremos como se definen las mismas y su forma de manejo, o "comportamiento" de la cola. Para trabajar con una cola, así como para cualquier tipo de estructura abstracta, tendremos que definir las operaciones que representen el comportamiento de la misma, para de esta manera poder utilizarlas. Dichas operaciones son: • Crear cola. • Insertar elemento. • Retirar elemento. • Cola vacía. • Vaciar cola. Podemos definir una cola en forma dinámica implementándola como una lista simple y respetando las restricciones de inserción (sólo se puede realizar a través del final) y extracción (sólo se puede realizar por el frente). A partir de la definición dada, podremos implementar una estructura de tipo cola en una unidad de biblioteca de la siguiente manera. Unit Colas; interface type tipo_dato = ; ptr_nodo_cola = ^tnodo_cola; tnodo_cola = record dato : tipo_dato; enlace : ptr_nodo_cola; end; tipo_cola = record frente, 6
final : ptr_nodo_cola; end; {encabezamiento de las primitivas que utilizaremos en el tipo de dato COLA} Procedure Cola_crear (var Cola : tipo_Cola); {Pre: La cola nunca fue creada. Post: Se creo la cola y se encuentra vacía} Procedure Cola_insertar (var Cola : tipo_cola; elem : tipo_dato; var error : boolean); {Pre: La Cola existe. Post: Si error = false el elemento fue ingresado en el final de la Cola, si error = true no se ha ingresado el elemento, la Cola se encuentra llena} Procedure Cola_retirar (var Cola : tipo_cola; var elem : tipo_dato); {Pre:La Cola fue creada y no está vacia. Post: El elemento del frente de la Cola fue retirado y se encuentra en elem, la Cola queda igual, sin el elemento que se encontraba en el frente} Function Cola_vacia (Cola : tipo_cola); {Pre: La Cola fue creada. Post: Devuelve true si la Cola se encuentra vacía, y sino false} Procedure Cola_vaciar (var Cola : tipo_cola); {Pre: La Cola fue creada. Post: La Cola se encuentra vacía} Implementation Procedure Cola_crear (var Cola : tipo_Cola); begin cola.frente := nil; cola.final := nil; end; Procedure Cola_insertar (var Cola : tipo_cola; elem : tipo_dato; var error : boolean);
7
var ptr_aux : ptr_nodo_cola; begin error:= Maxavail < sizeof(tnodo_cola); if (not error) then begin new (ptr_aux); ptr_aux^.dato := elem; ptr_aux^.enlace := nil; if (Cola_vacía (cola)) then cola.frente := ptr_aux else cola.final^.enlace := ptr_aux; cola.final := ptr_aux; end end; Procedure Cola_retirar (var Cola : tipo_cola; var elem : tipo_dato); var ptr_aux : ptr_nodo_cola; begin ptr_aux := cola.frente; cola.frente := cola.frente^.enlace; if (cola.frente = nil) then cola.final := nil; elem := ptr_aux^.dato; dispose (ptr_aux); end; Function Cola_vacia (Cola : tipo_cola); begin Cola_vacía := (cola.frente=nil) and (cola.final=nil); end;
8
Procedure Cola_vaciar (var Cola : tipo_cola); var e: tipo_dato; begin while not(Cola_vacía(cola)) do Cola_retirar(cola,e); end; end. Existen además del tipo de cola arriba definido, otros que tienen distintas utilidades según el requerimiento del programador, por ejemplo, la cola de prioridad. Cola de prioridad: Una cola de prioridad es una estructura característica, donde se pude retirar e insertar un ítem teniendo en cuenta la clave más grande o más chica (según la implementación) definida por el programador. Si los ítems tienen claves iguales, entonces la regla usual utilizada es que el primer ítem insertado es el que se retirará primero. Algunas aplicaciones de éste tipo de estructura son: la representación simulada de eventos dependientes del tiempo, como por ejemplo el funcionamiento de un aeropuerto, controlando partidas y aterrizajes de aviones. Otro ejemplo puede verse en los sistemas informáticos, el CPU asigna prioridades a las distintas tareas que debe ejecutar y las inserta en su cola, para de esta manera realizarlas en el orden correcto (multitareas). Podemos representar una cola de prioridad como una lista contigua ordenada, en la cual retirar un ítem es una operación inmediata, pero la inserción tomaría un tiempo proporcional al número de elementos que se encuentren en la cola, hay que tener en cuenta que dicha operación se debe realizar en forma tal que la cola quede ordenada. Otra forma de representación es a través de una lista desordenada, en la cual el proceso de inserción es inmediato, pero el de extracción es muy lento, ya que debo recorrer toda la cola para encontrar el ítem que debo retirar.
9