Stacks) estructura LIFO (Last-In, First-Out / último en entrar

Anuncio
UNIDAD 2.
ESTRUCTURAS DE DATOS SECUENCIALES
1. Pilas (Stacks)
Una pila es una coleccion de elementos en la que sólo se pueden insertar y eliminar datos por uno de los
extremos de la lista. Al igual que toda estructura de datos, se consideran algunas operaciones básicas
como la inserción y el borrado, que se denominan comúnmente como operación "push" y "pop"
respectivamente.
Los elementos de una pila se eliminan en orden inverso al que fueron insertados. Es decir, el último
elemento que entra a la pila es el primer elemento que se saca. Esta estructura se le conoce como
estructura LIFO (Last-In, First-Out / último en entrar - primero en salir).
Existen numerosos casos prácticos en los que se utiliza el concepto pila: pila de platos (cuando se
necesita un plato limpio siempre se toma el primero de la pila), pila de monedas, pila de latas, etc.
Las pilas se pueden representar con arreglos y con listas. En el caso de los arreglos es necesario definir el
tamaño máximo, y una variable auxiliar que indique el tope de la pila, en el caso de las listas se manejan
espacios dinámicos denominados nodos.
Representación de una pila
Las pilas se pueden representar en un lenguaje de programación por medio de listas enlazadas o con
arreglos, para los cuales se debe definir el tamaño máximo de la pila y una variable auxiliar que debe
servir como apuntador al último elemento de la pila llamado Tope.
Representación en Memoria
Las pilas no son estructuras de datos fundamentales, es decir, no están definidas como tales en los
lenguajes de programación, ya que tienen que ser implementadas. Las pilas pueden representarse e
implementarse mediante el uso de:
Arreglos.
Listas enlazadas.
1
A las Pilas que se implementan con arreglos se les denominan Pilas Estáticas, debido a que su tamaño
difícilmente será modificado, a no ser que así se implemente. Por lo tanto es importante definir el tamaño
máximo de la pila antes de su uso, además es necesario considerar un apuntador al último elemento
insertado en la pila (tope) el cual denominaremos Sp (Stak pointer). La representación gráfica de una pila
es la siguiente:
Como utilizamos arreglos para implementar pilas, tenemos la limitante de espacio de memoria reservada.
Una vez establecido un máximo de capacidad para la pila, ya no es posible insertar más elementos.
Una posible solución a este problema es el uso de espacios compartidos de memoria. Supóngase que se
necesitan dos pilas , cada una con un tamaño máximo de n elementos. En este caso se definirá un solo
arreglo de 2*n elementos, en lugar que dos arreglos de n elementos.
En este caso utilizaremos dos apuntadores: SP1 para apuntar al último elemento insertado en la pila 1 y
SP2 para apuntar al último elemento insertado en la pila 2. Cada una de las pilas insertará sus elementos
por los extremos opuestos, es decir, la pila 1 iniciará a partir de la localidad 1 del arreglo y la pila 2
iniciará en la localidad 2n. De este modo si la pila 1 necesita más de n espacios (hay que recordar que a
cada pila se le asignaron n localidades) y la pila 2 no tiene ocupados sus n lugares, entonces se podrán
seguir insertando elementos en la pila 1 sin caer en un error de desbordamiento
Operaciones con pilas
Las operaciones principales son poner y quitar elementos:
ALGORITMO INSERTAR ELEMENTOS (PUSH)
Este algoritmo pone el DATO en PILA. Actualiza el valor de TOPE. MAX es una variable que indica el
máximo de elementos que puede almacenar la pila.
SI TOPE < MAX
TOPE = TOPE + 1
PILA[TOPE] = DATO
SI NO
“LA PILA ESTA LLENA”
FIN SI
2
ALGORITMO ELIMINAR ELEMENTOS (POP)
Este algoritmo saca el último elemento de la PILA. El elemento lo guarda en la variable DATO.
Actualiza el valor de TOPE.
SI TIPE > 0
DATO = PILA[TOPE]
TOPE = TOPE – 1
SI NO
“PILA VACIA”
Aplicaciones con pilas (postfix, prefix, infix)
Las pilas son útiles en el tratamiento de recursividad, en llamadas a subprogramas, ordenación y en el
manejo de expresiones aritméticas. En este último caso se utilizan para convertir expresiones en notación
infija a su equivalente en postfijo o prefijo:
Dada la expresión A+B, se dice que esta en notación infija y su nombre se debe a que el operador +
se encuentra entre los operandos A y B.
Dada la expresión AB+, se dice que esta en notación postfija y su nombre se debe a que el operador
+ se encuentra después de los operandos A y B.
Dada la expresión +AB, se dice que esta en notación prefija y su nombre se debe a que el operador +
se encuentra antes de los operandos A y B.
La transformación de expresiones de infijo a postfijo y prefijo sigue las mismas reglas de precedencia para
los signos de operación, es decir la potencia ($) tiene la mayor precedencia, después el producto y la
división, y al final la suma y la resta. Además es importante recordar que las expresiones encerradas entre
paréntesis, deberán ser resueltas anticipadamente. Recuerde que las operaciones como la suma, resta,
división y producto son asociativas por la izquierda, a diferencia de la potencia que es asociativa por la
derecha.
A continuación observamos una tabla con ejemplos de conversión de operaciones infijo a postfijo/prefijo:
Infijo
A+B
A+B-C
(A+B)*(C-D)
A$B*C-D+E/F/(G+H)
((A+B)*C-(D-E))$(F+G)
A-B/(C*D$E)
Postfijo
AB+
AB+CAB+CD-*
AB$C*D-EF/GH+/+
AB+C*DE--FG+$
ABCDE$*/-
Prefijo
+AB
-+ABC
*+AB-CD
+-*$ABCD//EF+GH
$-*+ABC-DE+FG
-A/B*C$DE
Tome en cuenta que la forma prefija de una expresión compleja no es la imagen espejo de la forma
postfija, como puede ser visto en el segundo ejemplo de la tabla.
La ventaja de utilizar expresiones en notación polaca postfija y prefija radica en que no son necesarios los
paréntesis para indicar el orden de operación, ya que este queda establecido por la ubicación de los
operadores con respecto a los operando.
3
Las pilas son estructuras de datos muy usadas para la solución de diversos tipos de problemas. Pero tal
vez el principal uso de estas estructuras es el tratamiento de expresiones matemáticas representadas en
prefijo y postfijo, que es la manera en como las computadoras pueden interpretar las mismas. Los
procesos para la conversión de expresiones de infijos a postfijo y prefijo pueden ser fácilmente
mecanizados a través de algoritmos computacionales.
Conversión de Expresiones en Infijo a Postfijo
Considere dos expresiones A+B*C y (A+B)*C, y sus respectivas expresiones postfijas ABC*+ y AB+C*. En
cada caso existe un orden especifico en el cual se respetan las precedencias de las operaciones que se
tienen que realizar, resulta obvio observar que la precedencia de los operadores es un aspecto muy
importante a considerar en el proceso de conversión, por ello asumiremos la existencia hipotética de una
función precede( op1, op2 ), donde op1 y op2 son los caracteres que representan los operadores. Esta
función regresa un valor verdadero si op1 tiene precedencia sobre op2, y falso para el caso contrario. Por
lo que precede( ‘*’, ‘+’ ) regresa verdadero, precede( ‘+’, ‘+’ ) regresa verdadero y precede( ‘+’, ‘*’ )
regresa falso.
Además asumiremos las siguientes reglas de precedencia para los paréntesis, ya que las operaciones
encerradas entre estos deberán ser realizadas previamente al resto de las operaciones, ya que su
precedencia es mayor:
precede(
precede(
precede(
precede(
‘(’, op ) = falso
op, ‘(’ ) = falso
op, ‘)’ ) = verdadero
‘)’, op ) = error
//
//
//
//
para
para
para
para
cualquier
cualquier
cualquier
cualquier
operador
operador
operador
operador
op
op menos ‘)’
op menos ‘(’
op, se considera error
A continuación mostraremos el algoritmo correspondiente para convertir expresiones en infijo a postfijo:
stackOp = una pila vacía
while ( no sea fin de cadena de entrada infija )
{
simbolo = siguiente carácter de entrada
if ( simbolo es un operando )
agregar simbolo a la cadena postfija
else {
while ( !vacia(stackOp) && precede(tope(stackOP),simbolo) )
{
topeSimbolo = extrae(stackOp)
agregar topeSimbolo a la cadena postfija
}
if ( vacia(stackOp) || simbolo != ')' )
inserta(stackOp, simbolo)
else
topeSimbolo = extrae(stackOp)
}
}
while ( !vacia(stackOp) )
{
topeSimbolo = extrae(stackOp)
agregar topeSimbolo a la cadena postfija
}
Consideremos ahora la evaluación de las dos expresiones mencionadas al principio.
4
Ejemplo 1: cadena infija A + B * C
símbolo
1
2
3
4
5
6
7
A
+
B
*
C
Cadena postfija
stackOp
A
A
A
A
A
A
A
+
+
+*
+*
+
B
B
BC
BC*
BC*+
Las líneas 1,3 y 5 corresponden al deletreo de los operándoos por lo que símbolo es inmediatamente
colocado en la cadena postfija. En la línea 2 un operador es deletreado y colocado en la pila que esta
vacío. En la línea 4 la precedencia del símbolo * es mayor que la del símbolo colocado al tope de la pila
(+); por lo que el nuevo símbolo es colocado en la pila. En la línea 6 y 7 la cadena de entrada es vacía, y
la pila es vaciada extrayendo su contenido y colocándolo en la cadena postfija.
La colocación de los elementos en la pila stackOp es en dirección derecha izquierda, por lo que los
elementos van siendo incrustados del lado derecho.
Ejemplo 2: cadena infija (A + B) * C
símbolo
1
2
3
4
5
6
7
8
(
A
+
B
)
*
C
Cadena postfija
A
A
A
A
A
A
A
B
B
B
B
B
stackOp
(
(
(+
(+
+
+
+C
+C*
*
*
En este ejemplo, cuando el paréntesis del lado derecho es encontrado los elementos de la pila son
extraídos hasta que el paréntesis izquierdo es encontrado, en este punto ambos paréntesis son
descartados. De esta manera los paréntesis forzan a un orden de precedencia diferente.
Evaluación de Expresiones en Postfijo
Cada operador en una cadena postfija se refiere a los dos operados previos en la cadena. Suponga que
cada vez que leemos un operando colocamos a éste en la pila. Cuando alcanzamos un operador, sus
operándoos estarán al tope de la pila. Nosotros podemos extraer estos dos elementos, realizar la
operación indicada en ellos y colocar el resultado en la pila de manera que quede disponible para ser
usado como operando en la siguiente operación.
A continuación presentamos un algoritmo que es capaz de evaluar expresiones postfijas:
stackOp = una pila vacía
while ( no sea fin de cadena de entrada postfija )
{
simbolo = siguiente carácter de entrada
if ( simbolo es un operando )
5
inserta(stackOp, simbolo)
else {
// el simbolo es un operador
op1 = extrae(stackOp)
op2 = extrae(stackOp)
valor = el resultado de aplicar símbolo a op1 y op2
inserta(stackOp, valor)
}
}
return extrae(stackOp)
Suponga que queremos evaluar la siguiente expresión en postfijo: 6 2 3 + - 3 8 2 / + *. La siguiente
tabla muestra los valores que se van generando en la evaluación de cada elemento de la cadena.
Cada operando es colocado en una pila de operándoos. De tal manera que el numero máximo de
elementos de la pila que es el numero de operándoos que aparecen en la entrada de la expresión. Sin
embargo el máximo de de elementos en la pila es menor al numero teóricamente necesario, ya que el
operador los remueve de la pila. En el ejemplo veremos que la pila nunca contiene mas de cuatro
elementos, a pesar de que aparecen seis operándoos en el ejemplo. Tome en cuenta que el ejemplo
supone la entrada de la cadena del tipo postfijo.
Símbolo
6
2
3
+
3
8
2
/
+
*
op1
2
6
6
6
6
8
3
1
op2
3
5
5
5
5
2
4
7
valor
5
1
1
1
1
4
7
7
stackOp
6
6,
6,
6,
1
1,
1,
1,
1,
1,
7
2
2, 3
5
3
3, 8
3, 8, 2
3, 4
7
2. Colas
Una cola es una lista de elementos en la que éstos se introducen por un extremo y se eliminan por otro.
Los elementos se eliminan en el mismo orden en el que insertaron. El primer elemento que entra en la
cola, es el primer elemento en salir. Las colas también reciben el nombre de estructuras FIFO (First-In,
First-Out) Ejemplos, una cola de personas esperando usar el teléfono público, comprar tortillas, una fila
de autos en un auto lavado, etc.
Las colas son una estructura con muy pocas operaciones disponibles ya que solo permiten añadir y leer
elementos y al igual que las pilas, se pueden representar con arreglos y listas. En el caso de los arreglos
es necesario definir el tamaño máximo para la cola y dos variables auxiliares; una de ellas para que
guarde la posición del primer elemento de la cola (Frente) y otra para que guarde la posición del ultimo
elemento (Final).
111
1
↑
Frente
222
333
2
444
3
......
4
↑
Final
Máximo
6
ALGORITMO INSERTAR ELEMENTOS
Este algoritmo inserta el elemento Dato al Final de la cola. Frente y Final son variables que indican el
inicio y fin de la cola, respectivamente. MAX es el máximo de elementos que puede almacenar la cola.
Frente y Final inician en 0.
if ( Final < MAX )
{
Final = Final + 1
Cola [ Final ] = Dato
if ( Final igual a 1 ) {
Frente = 1
}
}
else {
“Cola llena”
}
ALGORITMO ELIMINAR ELEMENTOS
Este algoritmo elimina el primer elemento de la cola y lo guarda en la variable Dato.
if ( Frente no es igual a 0 )
{
Dato = Cola [ Frente ]
if ( Frente igual a Final )
{
Frente = 0
Final = 0
}
else {
Frente = Frente + 1
}
} else {
“COLA VACIA”
}
Colas circulares
Para hacer uso mas eficiente de la posiciones disponible se trata a las colas como una estructura circular.
Para ello es necesario realizar varios cambios al esquema inicial, en primer lugar los valores para Frente y
Final deberán iniciar ambos en la posición máxima de la cola, debido a que el ultimo elemento de la cola
precede inmediatamente al primero al primero dentro de la cola bajo esta representación. En segundo
lugar es necesario sacrificar un elemento de la cola para no tener casos absurdos en el que Final y Frente
sean la misma posición aún cuando la cola este casi vacía.
El algoritmo de inserción deberá contemplar un caso especial donde el Frente es incrementado en una
unidad y paso seguido se verifica que Final es igual Frente, en ese caso el valor de Final ya fue
incrementado y como se verifico que la cola esta llena, es necesario manipular el valor de Final para que
sea reubicado en su posición anterior próxima.
7
ALGORITMO INSERTAR ELEMENTOS
Este algoritmo inserta el elemento Dato al final de la cola. Frente y Final son variables que indican el inicio
y fin de la cola. MAX es el máximo de elementos que puede almacenar la cola.
if ( Final igual a MAX )
Final = 1
else
Final = Final + 1
if ( Final igual a Frente )
“Cola llena”
else
Cola [ Final ] = Dato
ALGORITMO ELIMINAR ELEMENTOS
Este algoritmo elimina el primer elemento de la cola y lo guarda en la variable DATO.
if ( Frente = Final )
“COLA VACIA”
else {
if ( Frente igual a MAX )
Frente = 1
else {
Frente = Frente + 1
Dato = Cola [ Frente ]
}
}
8
Descargar