EDMA2E.Cap03.Pilas
EDMA2E.Cap03.Pilas
eees
Estructuras de Datos
eees
2ª Edición
y Métodos Algorítmicos
213 Ejercicios resueltos
2ª Edición
ects ects
y Métodos Algorítmicos
Estructuras de Datos
E
ste libro intenta llenar un hueco en el panorama editorial de-
EEES EEES
ECTS
dicado a libros académicos en el campo de la Informática. Si
ECTS
bien existen varios libros “de teoría” dedicados a algoritmos
o estructuras de datos (muchos de ellos traducciones de originales
en inglés), la cantidad de libros “de problemas (resueltos)” sobre
eees
estos temas es muy escasa.
eees
El libro se divide en dos partes: la primera se dedica a las estruc-
turas de datos (pilas, colas, listas, árboles binarios y generales,
árboles de búsqueda y tablas, colas con prioridad y grafos) y la
ects
segunda a los métodos algorítmicos (divide y vencerás, método
ects
voraz, programación dinámica, vuelta atrás y ramificación y poda),
si bien es imposible separar por completo ambas materias, y esto
se nota en algunos de los ejercicios que se incluyen. Cada capítu-
EEES
lo empieza con una breve introducción al tema correspondiente,
donde se fijan los conceptos y notaciones que se utilizan en el
Y. Ortega Mallén
uno seguido de la solución correspondiente. En general, la solu-
ECTS
ción que se ofrece no es la única posible en absoluto y, de hecho,
N. Martí Oliet
ECTS y Métodos Algorítmicos
en algunos ejercicios se presenta más de una solución cuando la
A. Verdejo
variación es suficientemente interesante.
eees
Tras el éxito de la primera edición de 2003, se publica ahora esta
segunda edición en la que se han corregido todas las erratas co-
ects
y titulaciones la implantación de los grados, el contenido del libro
ects
continúa siendo fundamental en la formación de los graduados en
diferentes titulaciones relacionadas con la Informática.
2ª Edición
ECTS
www.garceta.es
Yolanda Ortega Mallén
Alberto Verdejo
3
Pilas
[\
3.1. Introducción
En la vida cotidiana suele ocurrir que las tareas se nos acumulan de forma que siempre nos dedicamos a
la tarea más reciente, dejando aplazadas las más antiguas, es decir, siguiendo el criterio de el último en entrar
es el primero en salir.
En este capítulo vamos a considerar una estructura de datos lineal cuya característica principal es que el
acceso a los elementos se realiza en orden inverso al de su almacenamiento. Aunque se las puede denominar
estructuras LIFO (Last In, First Out en inglés), el nombre más usual, que es el que vamos a utilizar, es pilas. El
comportamiento de las pilas es totalmente independiente del tipo de los datos almacenados en ellas, por lo
que se trata de un tipo de datos parametrizado.
La ventaja de las pilas es que el acceso a la estructura, tanto para su modificación (inserción y borrado) co-
mo para la consulta de los datos almacenados, se realiza en un único punto (la cima de la pila), lo que facilita
implementaciones sencillas y eficientes. A pesar de su sencillez, es una estructura con múltiples aplicaciones
en el diseño de algoritmos, como la evaluación de expresiones o la implementación de la recursión.
3.1. Especificar un TAD para describir las pilas con elementos pertenecientes a un tipo dado como pará-
metro, con las siguientes operaciones:
crear la pila vacía,
apilar un elemento,
desapilar el elemento en la cima,
consultar el elemento en la cima, y
determinar si la pila es vacía.
72 Estructuras de datos y métodos algorítmicos
Solución
especificación PILAS[ELEM]
usa BOOLEANOS
tipos pila
operaciones
pila-vacía : −→ pila { constructora }
apilar : elemento pila −→ pila { constructora }
desapilar : pila −→ p pila
cima : pila −→ p elemento
es-pila-vacía? : pila −→ bool
variables
e : elemento
p : pila
ecuaciones
desapilar(pila-vacía) = error
desapilar(apilar(e, p)) = p
cima(pila-vacía) = error
cima(apilar(e, p)) = e
es-pila-vacía?(pila-vacía) = cierto
es-pila-vacía?(apilar(e, p)) = falso
fespecificación
3.2. Extender la especificación de las pilas del ejercicio 3.1 con las siguientes operaciones:
calcular el número de elementos en una pila (profundidad),
consultar el elemento del fondo de una pila,
obtener la inversa de una pila,
duplicar una pila p, de forma que cada elemento de p aparezca apilado dos veces seguidas,
conservando el mismo orden relativo que en p,
concatenar dos pilas, colocando los elementos de la segunda sobre los de la primera, y
entremezclar dos pilas, donde la pila resultante se obtiene apilando alternativamente los ele-
mentos de las pilas argumento, empezando por el fondo de la primera pila.
Pilas 73
Solución
De forma similar a la operación cima (véase el ejercicio 3.1), la consulta del elemento en el fondo
de una pila solamente tiene sentido si la pila no es vacía, por lo que esta operación es parcial.
La especificación del comportamiento de todas estas nuevas operaciones se hace, en general, por
distinción de casos sobre las dos constructoras de las pilas (pila-vacía y apilar), pero para facilitar la
especificación de inversa, utilizaremos una operación auxiliar apilar-inversa con dos argumentos de
tipo pila, que coloca la inversa de la primera sobre la segunda.
especificación PILAS+[ELEM]
usa PILAS[ELEM], NATURALES
operaciones
profundidad : pila −→ nat
fondo : pila −→ p elemento
inversa : pila −→ pila
duplicar : pila −→ pila
concatenar : pila pila −→ pila
entremezclar : pila pila −→ pila
operaciones privadas
apilar-inversa : pila pila −→ pila
variables
e, f : elemento
p, q : pila
La profundidad de una pila vacía es cero; cada vez que se apila un elemento, la profundidad de
la pila se incrementa en uno.
ecuaciones
profundidad(pila-vacía) = 0
profundidad(apilar(e, p)) = 1 + profundidad(p)
El acceso a la cima de la pila es directo, de ahí la sencillez de las ecuaciones dadas en la especi-
ficación de las pilas para cima y desapilar (ejercicio 3.1); pero para poder acceder al fondo de la pila
hay que “avanzar” a través de los elementos apilados hasta alcanzar el primero que se apiló. Por eso
se distinguen dos casos cuando la pila no es vacía: si hay solo un elemento o hay más de uno.
fondo(pila-vacía) = error
fondo(apilar(e, p)) = e ⇐ es-pila-vacía?(p)
fondo(apilar(e, p)) = fondo(p) ⇐ ¬es-pila-vacía?(p)
Para apilar la inversa de una pila sobre otra, basta con ir desapilando los elementos en la cima
de la primera y apilarlos sobre la segunda.
apilar-inversa(pila-vacía, p) = p
apilar-inversa(apilar(e, p), q) = apilar-inversa(p, apilar(e, q))
Entonces, para invertir una pila, nos limitamos a apilar su inversa sobre una pila vacía.
Para duplicar los contenidos de una pila, basta apilar dos veces la cima de la pila sobre el resto
de la pila, el cual ha sido previamente duplicado.
74 Estructuras de datos y métodos algorítmicos
5 8 0 3 ... contenido
índice-cima
duplicar(pila-vacía) = pila-vacía
duplicar(apilar(e, p)) = apilar(e, apilar(e, duplicar(p)))
concatenar(p, pila-vacía) = p
concatenar(p, apilar(e, q)) = apilar(e, concatenar(p, q))
Una opción alternativa es colocar sobre la primera pila la inversa de la segunda pila invertida,
utilizando las operaciones apilar-inversa e inversa (nótese el cambio en el orden de los argumentos).
concatenar(p, q) = apilar-inversa(inversa(q), p)
Para entremezclar dos pilas, hay que comparar su profundidad, pues el último elemento a apilar
será el de la cima de la pila con mayor profundidad y, en el caso de que ambas sean igual de
profundas, corresponderá a la cima de la segunda pila.
3.3. Diseñar una representación estática del TAD de las pilas utilizando vectores e implementar las ope-
raciones especificadas en el ejercicio 3.1.
Solución
La idea es utilizar un vector contenido donde vamos colocando los elementos de la pila, de izquier-
da a derecha según se van apilando, y un índice que apuntará a la cima, como muestra gráficamente
la figura 3.1, en la cual se representa la pila de naturales
tipos
pila = reg
contenido[1..N ] de elemento
índice-cima : 0..N
freg
ftipos
3.4. Implementar dos pilas sobre un mismo vector, de forma que el desbordamiento de cualquiera de las
pilas solo se produzca en el caso de que el vector esté completamente ocupado.
Solución
P1 P2
... contenido
cima1 cima2
avanzando desde el extremo izquierdo, mientras que la otra lo hace desde el extremo derecho, como
ilustra el gráfico de la figura 3.2. De esta forma, el soporte estará completamente lleno cuando los
dos índices tengan valores consecutivos.
Así pues, el tipo representante es el siguiente:
tipos
doble-pila = reg
contenido[1..N ] de elemento
índice-cima[1..2] de 0..N + 1
freg
ftipos
Inicialmente ambas pilas están vacías; el índice a la cima de la primera estará más allá del extre-
mo izquierdo (0), mientras que el índice a la cima de la segunda estará más allá del extremo derecho
(N + 1).
A la hora de apilar un nuevo elemento, la cima se desplaza una posición: hacia la derecha en el
caso de la primera pila (+1) y hacia la izquierda en el caso de la segunda pila (−1). En cambio, para
desapilar, el desplazamiento de la cima se realiza en el sentido contrario: hacia la izquierda en el
caso de la primera pila (−1) y hacia la derecha en el caso de la segunda pila (+1). Evitamos el uso
de una instrucción condicional mediante la exponenciación del valor −1: (−1)1 = −1 y (−1)2 = +1.
En los cuatro algoritmos que siguen, el argumento i de tipo 1..2 indica sobre cuál de las dos pilas
tiene lugar la acción correspondiente.
3.5. Diseñar una representación dinámica del TAD de las pilas e implementar las operaciones especifica-
das en el ejercicio 3.1.
Solución
Una pila puede representarse mediante una estructura lineal enlazada, de forma que la cima
corresponda al extremo directamente accesible de la estructura, con lo que se consigue que el coste
en tiempo de todas las operaciones sea constante.
La figura 3.3 muestra, utilizando dicha representación, una pila de enteros donde se han apilado,
sucesivamente, los números −5, 2 y 3.
El tipo representante se define de la siguiente manera:
tipos
enlace-pila = puntero a nodo-pila
nodo-pila = reg
valor : elemento
sig : enlace-pila
freg
pila = enlace-pila
ftipos
La pila vacía corresponde al caso en el que el enlace exterior no apunta a ninguna estructura.
3.6. Usando la representación dinámica del ejercicio 3.5, programar una función copiar-pila que dada una
pila p devuelva un puntero a una estructura que represente la misma pila que p, pero ocupando
posiciones de memoria diferentes; y un procedimiento anular-pila cuyo efecto sea liberar todo el
espacio de memoria ocupado por la pila p antes de la llamada, dejando como nuevo valor de p la
pila vacía.
Solución
Puesto que la representación dinámica de una pila consiste en una estructura lineal enlazada,
remitimos al lector a la solución dada para el ejercicio 2.8 donde precisamente se describen sen-
dos algoritmos para copiar y anular una estructura lineal enlazada. Tan solo se necesitaría cambiar
nombres de los algoritmos y los tipos de los parámetros para que sean de tipo pila.
3.7. Diseñar una representación de las pilas cuando los elementos son dígitos, utilizando números natu-
rales, e implementar las operaciones especificadas en el ejercicio 3.1.
Solución
Apartado (a)
La idea es utilizar números naturales representados en base 10, de forma que el dígito más signi-
ficativo (situado más a la izquierda) corresponde al fondo de la pila, y el dígito menos significativo
(situado más a la derecha) corresponde a la cima. Por tanto, el tipo representante es el siguiente:
tipos
pila-d1 = nat
ftipos
Obviamente, no todo número natural representa una pila, ya que en este primer apartado no se
admiten dígitos 0 en las pilas. La implementación de las operaciones se detalla a continuación. En
primer lugar, la pila vacía se representa con el número cero.
Para apilar un nuevo dígito debemos añadirlo por la derecha del número que representa la pila,
es decir, sumamos el dígito al número multiplicado por 10.
Para desapilar debemos eliminar el dígito menos significativo, es decir, dividimos por 10 el nú-
mero que representa la pila; el resto de dicha división corresponde a la cima de la pila.
Suponiendo que las operaciones aritméticas sobre naturales tengan un coste constante, el coste
en tiempo de todas las operaciones también es constante.
80 Estructuras de datos y métodos algorítmicos
Apartado (b)
El problema al admitir el dígito 0 en las pilas es que el número 0 representa tanto la pila vacía
como una pila con uno o varios ceros. En realidad, tenemos problemas para representar cualquier
pila en cuyo fondo haya uno o más dígitos 0, puesto que estos corresponderían a ceros a la izquierda,
que no se consideran en la representación decimal de los naturales. Por esta razón, representaremos
cada pila con un registro con dos números naturales: el primero indica el número de dígitos 0 en el
fondo de la pila, y el segundo representa el resto de los dígitos en la pila. Así, el tipo representante
es el siguiente:
tipos
pila-d2 = reg
ceros-fondo : nat
dígitos : nat
freg
ftipos
La implementación de las operaciones es la siguiente, donde hay que tener cuidado con los ceros
en el fondo de la pila. Por lo demás, las operaciones siguen las ideas de la implementación vista en
el apartado anterior.
Como en el apartado anterior, si suponemos que las operaciones aritméticas sobre naturales
tienen un coste constante, el coste en tiempo de todas las operaciones es constante también.
3.8. Tenemos como dato un vector D[1..N ] que contiene registros, cada uno de los cuales consta de tres
campos con información sobre grandes desastres:
Los registros del vector están ordenados cronológicamente, del más antiguo al más reciente, pero el
tercer campo está vacío en todos los registros. El objetivo es completar el vector, rellenando el tercer
campo de cada registro a partir de la información que contiene el vector.
(a) Escribir un algoritmo que no utilice ninguna estructura auxiliar y tenga un coste en tiempo en
Θ(N 2 ).
(b) Escribir un algoritmo que utilice una pila como estructura auxiliar y tenga un coste en tiempo
en Θ(N ).
Solución
tipos
desastre = reg
fecha : fecha
víctimas : nat+
último : fecha
freg
ftipos
Vamos a representar con el valor especial sin-fecha de tipo fecha el hecho de que no hay ningún
desastre anterior con más víctimas; en particular, esto es siempre cierto para el primer desastre.
82 Estructuras de datos y métodos algorítmicos
Apartado (a)
Dado que el vector ya está ordenado por fechas, basta recorrerlo desde cada posición i hacia la
posición 1, para encontrar el último desastre con más víctimas. Si no existe tal desastre, se llegará a
la “posición” 0.
El coste en tiempo de este algoritmo con dos bucles anidados está efectivamente en Θ(N 2 ).
Apartado (b)
Para reducir el tiempo de ejecución, se recurre a la utilización de una estructura auxiliar. En con-
creto, se utiliza una pila, como sugiere el enunciado, de forma que en la misma queden almacenados
solamente los desastres anteriores con un número de víctimas mayor que el último desastre consi-
derado. Los desastres se apilan de tal forma que quedan ordenados cronológicamente, y forman una
sucesión decreciente en el número de víctimas, estando el menor en la cima. El desastre de turno se
compara con los desastres almacenados en la pila, empezando por la cima (que contiene el desastre
en la posición anterior en el vector), hasta encontrar un desastre con un número de víctimas mayor.
Los desastres con un número de víctimas menor o igual pueden descartarse definitivamente, ya que
no pueden ser el último desastre con más víctimas para ninguna de las posiciones del vector que
quedan por considerar. Si la pila se queda vacía, es que no hay ningún desastre anterior con más
víctimas. En cualquier caso, se apila el desastre en estudio para así abordar la siguiente iteración.
Obsérvese que dicha inserción mantiene la propiedad invariante de que los elementos en la pila
forman una sucesión decreciente.
punto de interés
... ...
cima-iz cima-dr
Nótese el uso de anular-pila para liberar la memoria ocupada por la estructura auxiliar al finalizar
el proceso de completar el vector.
El coste en tiempo de este algoritmo es lineal con respecto al número de desastres, ya que cada
desastre entra en la pila exactamente una vez, y solo se puede desapilar una vez.
3.9. Una secuencia es una estructura lineal con un punto de interés donde se realizan las modificaciones y
las consultas. Especificar el tipo de las secuencias representando una secuencia con un par de pilas,
incluyendo las siguientes operaciones:
Solución
Como indica el enunciado, representamos una secuencia mediante un par de pilas: la primera
contiene la parte de la secuencia a la izquierda del punto de interés, y la segunda contiene el punto
de interés y la parte de la secuencia a la derecha de este. La figura 3.4 ilustra esta idea. Conviene
el uso de estructuras LIFO, porque siempre trabajaremos en el mismo extremo de cada parte de la
secuencia (la zona de interés), y que será la cima de la pila correspondiente.
Tenemos, por tanto, una operación constructora libre que construye una secuencia a partir de dos
pilas. Sin embargo, como esta operación constructora es parte de la representación, tiene un cierto
carácter “privado”, de forma que conviene que el usuario de las secuencias desconozca su existencia,
pudiendo utilizar solo las operaciones enumeradas en el enunciado, las cuales estarán definidas en
términos de esta constructora.
Algunas de las operaciones son parciales. En concreto, no se puede avanzar, ni eliminar, ni con-
sultar el elemento actual en el punto de interés si estamos al final de la secuencia.
Como no se exige ningún requisito especial para los elementos de una secuencia, consideramos
la forma más simple de parámetro.
84 Estructuras de datos y métodos algorítmicos
especificación SECUENCIAS[ELEM]
usa PILAS[ELEM], BOOLEANOS
tipos secuencia
operaciones
crear : −→ secuencia
insertar : secuencia elemento −→ secuencia
eliminar : secuencia −→ p secuencia
actual : secuencia −→ p elemento
avanzar : secuencia −→ p secuencia
reiniciar : secuencia −→ secuencia
fin? : secuencia −→ bool
es-sec-vacía? : secuencia −→ bool
operaciones privadas
〈 _, _ 〉 : pila pila −→ secuencia { constructora }
variables
e : elemento
s : secuencia
iz, dr : pila
Al crear una secuencia no existe ningún elemento en el punto de interés, y ambas partes, izquier-
da y derecha, están vacías.
ecuaciones
crear = 〈 pila-vacía, pila-vacía 〉
Al insertar un elemento (por delante del punto de interés) lo colocamos en la parte izquierda; el
punto de interés no cambia.
Para eliminar el elemento en el punto de interés tenemos que quitar el primer elemento en la
parte derecha, es decir, el elemento en la cima de la segunda pila.
Para reiniciar la secuencia, debemos pasar todos los elementos de la parte izquierda hacia la parte
derecha.
primero anterior
punto de
interés
sig sig sig sig sig
e1 ...
ek−1 ek ...
en
3.10. Desarrollar una implementación dinámica del TAD de las secuencias especificado en el ejercicio 3.9,
de manera que todas las operaciones sean ejecutables en tiempo constante.
Solución
La especificación de las secuencias se dio en términos de dos pilas (parte izquierda y parte dere-
cha). Resulta sencillo obtener una implementación de las secuencias mediante pilas, ya sean imple-
mentadas de forma estática (véase el ejercicio 3.3) o de forma dinámica (véase el ejercicio 3.5), pero
en ese caso el coste en tiempo de la operación reiniciar es lineal con respecto al tamaño de la secuen-
cia, ya que es preciso mover todos los elementos de la pila izquierda hacia la pila derecha. En cambio,
la solución que mostramos a continuación, que implementa una secuencia mediante una única es-
tructura lineal enlazada, consigue un coste constante para todas las operaciones de la especificación.
Para ello, necesitamos poder acceder en tiempo constante al primer elemento de la secuencia, para
poder reiniciar en tiempo constante. También necesitamos acceder en tiempo constante al punto de
interés. Como los elementos se insertan por delante del punto de interés (véase la especificación en el
ejercicio 3.9), tenemos que acceder también al nodo anterior al punto de interés. Para no tener que
distinguir el caso especial en el que el punto de interés está en el primer elemento, resulta convenien-
te añadir un nodo “fantasma” a la cabecera de la estructura. Dicho nodo no contiene información real
alguna, pero con su mera existencia se consigue que todo nodo que contiene un elemento real tenga
un nodo anterior. Con todo ello, una secuencia vendrá definida por dos punteros, uno que apunta
al primer nodo de la estructura (el nodo fantasma) y otro que apunta al nodo anterior al punto de
interés. Dicha representación se ilustra mediante el gráfico de la figura 3.5. Así la parte izquierda
comprende desde el nodo siguiente al fantasma hasta el nodo apuntado por anterior; mientras que
la parte derecha va desde el nodo siguiente al apuntado por anterior hasta el final de la estructura.
Con todo esto, el tipo representante es el siguiente:
86 Estructuras de datos y métodos algorítmicos
tipos
enlace-secuencia = puntero a nodo-secuencia
nodo-secuencia = reg
valor : elemento
sig : enlace-secuencia
freg
secuencia = reg
primero, anterior : enlace-secuencia
freg
ftipos
Al crear una secuencia vacía solo creamos el nodo fantasma, adonde apuntarán los dos punteros
de la secuencia.
Para insertar un elemento en la secuencia hay que crear un nuevo nodo, que se enlaza en la
estructura por detrás del nodo apuntado por anterior. Dicho nodo pasa a ser el anterior al punto de
interés.
Las siguientes operaciones se basan en usar adecuadamente la información de anterior. Las tres
primeras dan error si nos encontramos al final de la secuencia.
Para eliminar un elemento se suprime el nodo situado detrás del apuntado por anterior.
Para consultar el elemento actual en la secuencia, se accede al nodo siguiente al apuntado por
anterior.
Para avanzar en la secuencia es suficiente trasladar el puntero anterior al nodo siguiente.
Para reiniciar la secuencia basta trasladar el puntero anterior al nodo fantasma en la cabecera.
Estamos al final de una secuencia si no hay nodos tras el nodo apuntado por anterior.
si no
p := (s.anterior)↑.sig
(s.anterior)↑.sig := p ↑.sig
liberar(p)
fsi
fproc
fun actual(s : secuencia) dev e : elemento { Θ(1) }
si (s.anterior)↑.sig = nil entonces error(Final de la secuencia)
si no e := ((s.anterior)↑.sig)↑.valor
fsi
ffun
proc avanzar(s : secuencia) { Θ(1) }
si (s.anterior)↑.sig = nil entonces error(Final de la secuencia)
si no s.anterior := (s.anterior)↑.sig
fsi
fproc
proc reiniciar(s : secuencia) { Θ(1) }
s.anterior := s.primero
fproc
fun fin?(s : secuencia) dev b : bool { Θ(1) }
b := ((s.anterior)↑.sig = nil)
ffun
3.11. Una expresión aritmética construida con los operadores binarios +, -, *, / y constantes (representa-
dos cada uno por un carácter) se dice que está en forma postfija si, o bien es una única constante,
o bien consiste en dos expresiones en forma postfija una tras otra, seguidas inmediatamente por
un operador. A continuación se presenta un ejemplo de una expresión escrita en la notación infija
habitual junto con su correspondiente forma postfija:
Diseñar un algoritmo iterativo que calcule el valor de una expresión dada en forma postfija. Se su-
pone que la expresión viene representada como una secuencia de caracteres (véase el ejercicio 3.9),
y que hay disponibles una función valor que asocia a cada constante su valor numérico (real) y una
función aplicar que realiza la operación aritmética indicada sobre dos números reales dados.
88 Estructuras de datos y métodos algorítmicos
Solución
Para evaluar una expresión en forma postfija, vamos a utilizar como estructura auxiliar una pila
(de números reales) donde iremos almacenando el valor de los operandos de la expresión y de los
resultados intermedios. Se comienza el proceso con una pila vacía y se va recorriendo la secuencia
(que contiene la expresión) de izquierda a derecha. Cada vez que se pasa por una constante, se apila
su valor. Cada vez que se pasa por un operador, se desapilan los dos valores más arriba en la pila,
se componen con el operador (teniendo en cuenta el orden), y se apila el resultado. Al acabar el
proceso la pila contendrá un solo número, que es el valor de la expresión. Aunque el contenido de
la secuencia dada como argumento no se modifica, el punto de interés va cambiando al hacer el
recorrido, por lo que implementamos el algoritmo mediante un procedimiento.
3.12. Implementar una función que compruebe si una sucesión de paréntesis abiertos y cerrados está
“equilibrada”, es decir, a cada paréntesis abierto le corresponde uno cerrado, estando bien anidados.
Por ejemplo, ((()())()) está equilibrada, pero (()( no lo está. Se supone que la sucesión viene
dada en una secuencia de caracteres (véase el ejercicio 3.9). Generalizar el ejercicio suponiendo que
además de paréntesis, la sucesión contiene corchetes y llaves que también deberán estar equilibrados.
Así, {([])}() está equilibrada, pero ({]{ no lo está.
Solución
En el caso en el que solo haya paréntesis abiertos y cerrados, basta con recorrer la secuencia
de caracteres manteniendo en un contador cont el número de paréntesis abiertos aún no cerrados.
Si en la posición actual de la secuencia se encuentra un paréntesis abierto, incrementamos cont; si
encontramos un paréntesis cerrado y cont > 0, restamos uno al contador y continuamos. Pero si
encontramos un paréntesis cerrado y cont = 0, entonces la secuencia no está equilibrada. Además, al
terminar de recorrer la secuencia, el contador debe ser cero para que la misma esté equilibrada. El
coste en tiempo de este algoritmo es lineal con respecto al tamaño de la secuencia.
Cuando además de paréntesis tenemos llaves y corchetes, necesitamos una estructura auxiliar
para guardar los separadores abiertos todavía no cerrados, en el orden en el que han aparecido en
la secuencia. La estructura auxiliar que necesitamos es una pila, ya que los separadores se cierran
en sentido inverso al que se abrieron. Cuando en la secuencia encontramos un separador abierto,
este se apila; cuando encontramos uno cerrado, comprobamos si es la pareja de la cima de la pila,
siempre que esta no sea vacía. Si la pila es vacía o los separadores no forman pareja, la secuencia de
entrada no está equilibrada. Además, al terminar de recorrer la secuencia, la pila debe quedar vacía
para que la misma sea equilibrada.
Nótese que si la secuencia está equilibrada, la pila auxiliar quedará vacía al final del proceso,
pero si no está equilibrada, pueden quedar caracteres en la pila, por lo que es oportuno anular la
pila auxiliar.
A pesar de la necesidad de manejar la estructura auxiliar, el coste en tiempo de este algoritmo
también es lineal con respecto al tamaño de la secuencia, como en el caso anterior.