ELO320 Estructuras de Datos y Algoritmos Stacks y Colas Tomás Arredondo Vidal Este material está basado en: Robert Sedgewick, "Algorithms in C", (third edition), Addison-Wesley, ISBN 0-201-31663-3. 2001 material del curso ELO320 del Prof. Leopoldo Silva material en el sitio http://es.wikipedia.org 6: Stacks 1 6-Stacks y Colas 6.1 Stacks: definiciones y operaciones 6.2 Stacks: implementación 6.3 Colas: definiciones y operaciones 6.4 Colas: implementación 6: Stacks 2 Definiciones y operaciones Un stack (o pila) es una lista restringida, en cuanto a operaciones, ya que sólo permite inserciones y descartes en un extremo (el tope del stack). Tiene gran utilidad al ser usado para implementar variables automáticas, implementar funciones recursivas, para evaluar balance de paréntesis entre otras. Operaciones posibles sobre stacks incluyen: Empujar en el stack o Push Sacar del stack o Pop Leer el primer elemento del stack o Read 6: Stacks 3 6-Stacks y Colas 6.1 Stacks: definiciones y operaciones 6.2 Stacks: implementación 6.3 Colas: definiciones y operaciones 6.4 Colas: implementación 6: Stacks 4 Stacks: Implementación En general la implementación de las operaciones de inserción y descarte usando arreglos son costosas, en comparación con nodos enlazados vía punteros, porque es necesario desplazar el resto de las componentes después de una inserción o descarte Otro beneficio de usar punteros es que el stack puede crecer dinámicamente 6: Stacks 5 Stacks: Implementación usando arreglos #ifndef __STACK_H__ #define __STACK_H__ typedef int Item; // Item almacenado es un int void StackInit(int); int StackEmpty(void); int StackFull(void); void StackPush(Item); Item StackPop(void); void StackDestroy(void); #endif /* __STACK_H__ */ 6: Stacks 6 Stacks: Implementación usando arreglos (cont) #include “stack.h” static Item * stack; // puntero al inicio de la zona del stack static int NumItems; // numero items en el stack static int MAXN; // Máxima capacidad del stack void StackInit(int max) { stack = malloc(max*sizeof(Item) ); //se solicita arreglo. if (stack == NULL) exit(1); NumItems = 0; MAXN=max; } 6: Stacks 7 Stacks: Implementación usando arreglos (cont) int StackEmpty(void) { return(NumItems == 0) ; //Retorna True o 1 si stack vacío } int StackFull(void) { return(NumItems == MAXN) ; //Ret. True si stack lleno } //se puede empujar algo al stack si no está lleno. void StackPush(Item item) { if (!StackFull() ) stack[NumItems ++] = item; } 6: Stacks 8 Stacks: Implementación usando arreglos (cont) Item StackPop(void) { if( StackEmpty() ) { printf("error. Extracción de stack vacio\n"); exit(1); } else return ( stack[--NumItems] ) ; } void StackDestroy(void) { free(stack); } 6: Stacks 9 Ejemplo: Balance de Parentesis Es útil poder detectar si es que los paréntesis en un archivo fuente están o no correctamente balanceados Ejemplo: a + (b + c) * [(d + e])/f Seudo-código usando un stack: Crear el stack. Mientras no se ha llegado al final del archivo de entrada: Descartar símbolos que no necesiten ser balanceados. Si es un paréntesis de apertura: empujar al stack. Si es un paréntesis de cierre, efectuar un pop y comparar. Si son de igual tipo continuar Si son de diferente tipo: avisar el error. Si se llega al fin de archivo, y el stack no esta vacío: avisar error. Destruir el stack. 6: Stacks 10 Ejemplo: Expresiones en notacion Polaca Inversa Las expresiones aritméticas que generalmente escribimos están en notación “in situ” o fija. En esta notación los operadores se presentan entre dos operandos; por ejemplo: 2 + 3 * 4. Esta notación no explica el orden de precedencia, esto puede resolver con reglas y con paréntesis: (2+3)*4 La Reverse Polish Notation inventada por Jan Lukasiewicz se usa en calculadoras HP resuelve esto . En notación RPN el operador sigue a los operandos: 423+* que en “in situ” corresponde a: ( 2 + 3 ) * 4 6: Stacks 11 Ejemplo: Expresiones en notacion Polaca Inversa (cont) (3 + 5) * (7 - 2) puede escribirse: 3 5 + 7 2 - * Leyendo la expresión en RPN se realiza con las siguientes operaciones: Push 3 en el stack. Push 5 en el stack. El 5 está en el tope, es el último en entrar: (3, 5) Se aplica la operación + la cual saca los dos números del tope del stack, los suma y coloca el resultado en el tope: (8) Push 7 en el stack: (8, 7) Push 2 en el stack: (8, 7, 2) Se efectúa la operación – con los dos números en el tope. Éste contiene ahora (8, 5) Se efectúa la operación *el stack contiene ahora (40). 6: Stacks 12 Ejemplo: Expresiones en notacion Polaca Inversa (cont) Seudo-código While ( no se haya leído el símbolo fin de archivo EOF) { leer un símbolo; Si es número: empujar el valor del símbolo en el stack Si es un operador: { Efectuar dos pop en el stack; Ejecutar operación sobre los números; Empujar el resultado en el stack; } } Retornar contenido del tope del stack como resultado; 6: Stacks 13 Ejemplo: Conversión de in situ a Polaca Inversa Es útil poder convertir las expresiones infijas a RPN para poder evaluarlas en un stack. Para especificar el algoritmo es preciso establecer las reglas de precedencia de operadores: La más alta prioridad está asociada a los paréntesis, los cuales se tratan como símbolos Prioridad media tienen la operaciones de multiplicación y división La más baja la suma y resta. Se asume solamente la presencia de paréntesis redondos en expresiones. Como la notación polaca inversa no requiere de paréntesis, éstos no se sacarán hacia la salida. 6: Stacks 14 Ej: While ( no se haya leído el símbolo fin de archivo EOF) { leer un símbolo; Si es número: enviar hacia la salida; Si es el símbolo ’)’: no sacar del stack hacia la salida, hasta encontrar ‘(‘, el cual debe copiarse hacia la salida. Si es operador o el símbolo ’(‘: Si la prioridad del recién leído es menor o igual que la prioridad del operado ubicado en el tope del stack: { if( tope==‘(‘ ) empujar el operador recién leído; else { efectuar pop del operador y sacarlo hacia la hasta que la prioridad del operador recién leído mayor que la prioridad del operador del tope. salida sea Empujar el recién leído en el tope del stack. } } else empujar recién leído al tope del stack; } Si se llega a fin de archivo: vaciar el stack, hacia la salida. 6: Stacks 15 6-Stacks y Colas 6.1 Stacks: definiciones y operaciones 6.2 Stacks: implementación 6.3 Colas: definiciones y operaciones 6.4 Colas: implementación 6: Stacks 16 Definiciones y operaciones Una cola es una lista con restricciones. En ésta las inserciones ocurren en un extremo y los descartes en el otro (e.g. una cola en el banco) Si se conoce el máximo número de componentes que tendrán que esperar en la cola, se suele implementar en base a arreglos. Requiere dos variables o índices: cola que es un índice a donde insertar o encolar cabeza es un índice al elemento a descartar o desencolar 6: Stacks 17 Definiciones y operaciones A medida que se consumen o desencolan componentes, van quedando espacios disponibles en las primeras posiciones del arreglo. También a medida que se encolan elementos va disminuyendo el espacio para agregar nuevos elementos. Una mejor utilización del espacio se logra con un buffer circular, en el cual la posición siguiente a la última del arreglo es la primera del arreglo. 6: Stacks 18 Buffer circular Este buffer se puede implementar aplicando aritmética modular, si el anillo tiene N posiciones, la operación: cola = (cola + 1) % N, mantiene el valor de la variable cola entre 0 y N-1. Operación similar puede efectuarse para la variable cabeza cuando deba ser incrementada en uno. La variable cola puede variar entre 0 y N-1. Si cola tiene valor N-1, al ser incrementada en uno (módulo N), tomará valor cero. También se agrega una variable N con el numero de elementos encolados para poder distinguir entre la cola vacía y llena. 6: Stacks 19 6-Stacks y Colas 6.1 Stacks: definiciones y operaciones 6.2 Stacks: implementación 6.3 Colas: definiciones y operaciones 6.4 Colas: implementación 6: Stacks 20 Colas: Implementación usando arreglos circulares typedef int Item; // Item es un entero en este ejemplo static Item *q; // Puntero al arreglo de Items static int N, cabeza, cola, encolados; //Administran el anillo void QUEUEinit(int maxN) //maxN es el valor N-1 de la Fig { q = malloc((maxN+1)*sizeof(Item)); // Espacio para N items N = maxN+1; cabeza = 0; cola = 0; encolados=0; } /* La detección de cola vacía se logra con */ int QUEUEempty() { return (encolados == 0); } 6: Stacks 21 Colas: Implementación (cont) /* Si la cola no está vacía se puede consumir un elemento */ Item QUEUEget() { Item consumido= q[cabeza]; cabeza = (cabeza + 1) % N ; encolados--; return (consumido); } /* La detección de cola llena se logra con QUEUEfull( )*/ int QUEUEfull() { return( encolados == N); } 6: Stacks 22 Colas: Implementación (cont) void QUEUEput(Item item) /* Encolar un elemento */ { q[cola] = item; cola = (cola +1) % N; encolados++; } void QUEUEdestroy(void) /* Para recuperar el espacio */ { free ( q ); } Las funciones cola llena y vacía se podrían implementar con macros para reducir uso del stack. #define QUEUEempty() (encolados == 0) #define QUEUEfull() (encolados == N) 6: Stacks 23