Resumen Modulo 2

Descargar como pdf o txt
Descargar como pdf o txt
Está en la página 1de 33

Modulo 2

Unidad 1
Pilas
¿Qué entendemos por ESTRUCTURA?, respuestas válidas podrían ser, por ejemplo:
• Una organización
• Un esquema
• Un ordenamiento

Por lo cual podemos inferir que una ESTRUCTURA de DATOS es un esquema que nos permite manipular datos a fin
de facilitarnos la resolución de un problema, para el cual aplicaremos algoritmos.

Es claro que un punto crítico en la solución de un problema esta justamente en encontrar una estructura de datos
acorde a este objetivo.
Y por extensión la facilidad que dicha estructura tenga en su tratamiento por el lenguaje elegido.

Podemos identificar a las estructuras según la forma en la cual se desempeña al momento de ejecutar un programa.

Por esto tenemos estructuras:


• ESTÁTICAS: donde su tamaño es fijo (como ser vectores, cadenas)
• DINÁMICAS: donde su tamaño es variable (como ser pilas, colas, listas)

También podemos subdividir a este tipo en:

• Lineal: aquellos en que el almacenamiento se hace en zonas contiguas


• No Lineal: aquellos en los cuales el almacenamiento puede hacerse en cualquier zona, donde el concepto de
“siguiente” es lógico y no físico

Una estructura de datos busca una representación apropiadas de los datos para ser eficaz y eficiente en el desarrollo de
nuestros algoritmos. A esta representación junto con las operaciones permitidas que realizamos sobre dichas
estructuras se denominan estructuras de datos.

Existen distintos tipo de estructuras de datos tales como: Pilas, Colas, Listas Enlazadas, Arboles, Arboles Binarios,
Tabla Hash, entre otros.

Estructura de Datos: Es una colección de datos que son útiles para la organización y pensados para facilitar las
operaciones que ella necesita.

¿Por qué Necesitamos Estructura de Datos?


Uno de los aspectos más importantes del paradigma de la Orientación a Objetos es la Reutilización de componentes.
Una vez implementadas nuestras Estructuras de Datos podemos usarlas una y otra vez en diversas aplicaciones.
Otro enfoque importante de la Orientación a Objetos es la separación de la interfaz de la implementación.
El usuario de la estructura de datos no necesita ver la implementación solo las operaciones disponibles. Estas
operaciones pueden ser desarrolladas por un experto en algoritmos y estructuras de datos.
Debemos pensar cuidadosamente en nuestras estructuras de datos sin tener en cuenta detalles de la implementación.
Esto permitirá interfaces más limpias, más flexibles y reutilizables y generalmente más fáciles de implementar. A este
proceso se lo denomina Abstracción.

Una de las características importantes de las estructuras de datos es la


reutilización.
Abstracción
Una forma de resolver un problema es analizar las características y condiciones del mismo y de este análisis armar un
modelo, el cual nos ayuda para llegar a una solución.

Ese modelo es un concepto abstracto, el cual fue creado por nosotros para poder resumir el problema planteado, de
una manera que nos resulta manejable.

Los pasos son por medio de acercamientos sucesivos al entorno real.

Partimos desde una visión de alto nivel y luego la vamos refinando, añadiendo detalles y consideraciones particulares,
hasta que obtenemos una descripción detallada o de “bajo nivel”.

Al llegar a esta instancia tenemos una descripción detallada, que puede ser implementada en cualquier lenguaje de
programación.

En la etapa de diseño, el profesional de sistemas, analiza cómo relacionara a los datos con las operaciones necesarias a
fin que pueda cumplir con los detalles que se especificaron en la descripción detallada del problema.

Lo hace independizándose de cómo terminara la implementación, se concentra en los aspectos propios de la resolución
del problema planteado a partir del modelo especificado.
Este proceso se conoce como abstracción.

Abstracción: Es describir los datos y las operaciones sin considerar los factores de su implementación.

Por ejemplo, para resolver un problema se necesitan utilizar variables con contenido decimal, no debe ser un problema
a resolver en este momento como o donde se almacenara esta información.
Por lo cual el programador, a esta etapa, la maneja con un tipo de datos abstracto (TDA).

VENTAJA
Independizar el diseño del programa de la implementación especifica
de los datos

Pilas
Una PILA es una lista lineal de elementos en la cual cada elemento solo puede ser insertado o eliminado por un
extremo denominado CIMA, es decir, los elementos se van a sacar de la pila en orden inverso al que se insertan (pila
LIFO, Last In, First Out -ultimo en entrar primero en salir-).
La Pila es una estructura de datos de acceso restrictivo a sus elementos, además esta estructura facilita el poder utilizar
el concepto de recursividad en sistemas.

Este tipo de estructura tiene las siguientes características:

• Es una estructura lineal


• Tiene una secuencia finita de elementos
• Cualquier inserción o eliminación se pueden efectuar en uno solo extremo (cima)

Las operaciones que podemos realizar son:

• Determinar si la pila está vacía.


• Determinar si la pila está llena.
• Insertar un nuevo elemento a la pila.
• Eliminar el elemento de la cima, en orden inverso a la inserción
• Crear una lista.
¿Cómo representamos una Pila?
La podemos representar cono un vector lineal

Dónde
• Tope o cima: indica la dirección del último elemento de la pila
• n: número máximo de elementos de la pila

Podemos identificar situaciones particulares:

• Si tope es igual a n significa que la pila está llena.


• Si tope es igual a 0 significa que la pila está vacía.

PILA
Las inserciones y borrados solo se realizan en uno de los extremos
llamado CIMA.

Una pila es una estructura de datos en la que el acceso está restringido al elemento más recientemente insertado. Se
comporta en buena medida como cualquier pila común de papeles, de platos o de periódicos. El último elemento
añadido a la pila se coloca en la parte superior y es fácilmente accesible, mientras que resulta más difícil acceder a los
elementos que han estado en la pila durante un cierto tiempo. Por tanto, la pila es apropiada si esperamos acceder solo
al elemento superior; todos los restantes elementos son inaccesibles.

Una pila restringe el acceso, limitándolo al elemento más recientemente insertado.

En una pila, las tres operaciones naturales


• Insertar -> Push
• remover -> Pop
• find (buscar) -> Top.

1 // Protocolo para Stack.


2
3 package weiss.nonstandard;
4
5 public interface Stack<AnyType>
6{
7 void push (AnyType x); // insertar
8 void pop ( ); // eliminar
9 AnyType top ( ); // encontrar
10 AnyType topAndPop ( ); // encontrar + eliminar
11
12 boolean isEmpty ( );
13 void makeEmpty ( );
14 }

Protocolo para la pila.


Introduciendo elementos en la pila y luego extrayéndolos, podemos utilizar la pila para invertir el orden de las cosas.

Las operaciones con la pila consumen un tiempo constante.

Cada operación con la pila debería consumir un tiempo constante, independientemente del número de elementos que
la pila contenga. Lo que hace que la pila sea útil son las numerosas aplicaciones en las que lo único que necesitamos
es acceder al elemento más recientemente insertado. Un uso importante de las pilas es en el diseño de compiladores.
Pilas y lenguajes informáticos
Los compiladores comprueban los programas para detectar errores sintácticos. Sin embargo, a menudo, la falta de un
solo símbolo.
Una pila resulta útil para comprobar si existen símbolos no equilibrados, porque sabemos que al encontrarnos con un
símbolo de cierre como ), este deberá corresponderse con el símbolo de apertura ( más reciente que todavía no esté
cerrado. Por tanto, introduciendo los símbolos de apertura en una pila, podemos comprobar fácilmente si un símbolo
de cierre tiene sentido.

Específicamente, tendremos el siguiente algoritmo:

1- Crear una pila vacía.


2- Leer los símbolos hasta el final del archivo.

a- Si el elemento sintáctico es un símbolo de apertura, introducirlo en la pila.


b- Si se trata de un símbolo de cierre y la pila está vacía, informar de que se ha producido un error.
c- En caso contrario, sacar un elemento de la pila. Si el símbolo extraído no es el símbolo de apertura
correspondiente, informar de un error.

3- Al final del archivo, si la pila no está vacía, informar de un error.

La pila se emplea para implementar llamadas a métodos en la mayoría de los lenguajes de programación.

Una aplicación final importante de la pila es la evaluación de expresiones en los lenguajes informáticos. En la
expresión 1+2*3, vemos que en el punto en que nos encontramos con *, ya hemos leído el operador + y los operandos
1 y 2. ¿El operador * opera sobre 2, o sobre 1+2? Las reglas de precedencia nos dicen que * opera sobre 2, que es el
operando más recientemente encontrado. Después de encontrar el 3, podemos evaluar 2*3 para obtener 6 y luego
aplicar el operador +. Este proceso sugiere que los operandos y los resultados intermedios deberían guardarse en una
pila. También sugiere que los operadores se guarden en la pila (ya que el + se conserva hasta después de evaluar *,
que tiene mayor precedencia). Un algoritmo que utiliza esta estrategia es el análisis de precedencia de operadores.

La API de colecciones proporciona una clase Stack para la pilas, pero no proporciona ninguna clase para colas. Java 5
añade una interfaz Queue.

Tanto para la pila como para la cola hay dos formas básicas de conseguir operaciones en tiempo constante. La primera
consiste en almacenar los elementos de manera contigua en una matriz, mientras que la segunda consiste en almacenar
los elementos de forma no contigua en una lista enlazada.

Implementaciones basadas en matriz dinámica


Vamos a utilizar una matriz simple para implementar la pila y la cola. Los algoritmos resultantes son extremadamente
eficientes y fáciles de programar.

Pilas
Se puede implementar con una matriz y un entero. El entero tos (top of stack, cima de la pila) proporciona el índice en
la matriz del elemento superior de la pila. Así, cuando tos es -1, la pila está vacía. Para introducir un elemento con la
operación push, incrementamos tos y colocamos el nuevo elemento en la posición tos de la matriz. Acceder al
elemento superior es por tanto trivial, y podemos realizar la extracción pop decrementando tos.
Figura 16.1 Cómo funcionan la rutinas de la pila.
(a) Pila vacía. (b) push(a). (c) push(b). (d) pop( ).

Figura 16.2 Esqueleto para la clase de pila basada en matriz.

La Figura 16.2 muestra el


esqueleto de la clase Stack basada
en matriz.

Las rutinas isEmpty y makeEmpty


son rutinas de una única línea,
como se muestra en la Figura 16.4
y sirven para ver si la pila está
vacía y para vaciarla.

Figura 16.3 El constructor de cero parámetros para la clase ArrayStack.


Especifica dos miembros de datos:
- theArray, que es una matriz que se expande
según sea necesario y que almacena los
elementos contenidos en la pila;
- topOfStack que proporciona el índice del
elemento que está actualmente en la cima de
la pila. Para una pila vacía este índice será -1.
Figura 16.4 Las rutinas isEmpty y makeEmpty para la clase ArrayStack.

Figura 16.5 El método push para la clase ArrayStack.

El método push se muestra en la Figura


16.5. Si no fuera por el mecanismo de
duplicación del tamaño de la matriz, la
rutina push estaría dada por la única línea
de código mostrada en la línea 9. Recuerde
que el uso del operador prefijo ++ significa
que se incrementa primero topOfStack y
luego se utiliza su nuevo valor para indexar
la matriz theArray.

Figura 16.6 Los métodos top y pop para la clase ArrayStack.

Figura 16.7 El método topAndpop para la clase ArrayStack.

El operador postfijo -- utilizado en


la Figura 16.7 indica que, aunque
se decrementa topOfStack, es su
valor anterior el que se utiliza para
indexar theArray.
El mecanismo de duplicación del tamaño de la matriz no afecta a largo plazo al rendimiento del algoritmo.

Si no hay mecanismo de duplicación del tamaño de la matriz, toda operación tarda un tiempo constante. Una
operación push que exija una duplicación del tamaño de la matriz requerirá un tiempo O (N ).

Una duplicación de la matriz que implique a N elementos deberá estar precedida por al menos N /2 inserciones con
push que no exijan una duplicación del tamaño de la matriz. En consecuencia, podemos cargar el coste O (N ) de la
duplicación a esas N /2 inserciones anteriores con push, lo que en la práctica equivale a incrementar el coste de cada
operación push en una cierta constante pequeña. Esta técnica se denomina amortización.

Actividad Unidad 1
Enunciado: Dada una pila de nombre “pila” con 3 elementos(1,2,3), se desea generar el código correspondiente a la
clase que permita obtener el siguiente resultado en su ejecución:

Respuesta:
import java.util.*;

public class Actividad1 {

public static void main(String[] args) {


Stack pila = new Stack();

pila.push(3);
pila.push(2);

pila.pop();
pila.pop();

pila.push(1);

System.out.println(pila.push(1));
System.out.println(pila.peek());

}
Unidad 2
Implementación de Pilas con Listas Enlazadas

Las características propias de la Pila nos permite utilizarlas en varias aplicaciones facilitándonos:

• Los compiladores comprueban la sintaxis de los programas.


• Controlar, desde el Sistema Operativo, la ejecución de todas las ordenes de un archivo batch.
• Recordar al programa principal el punto de llamada de un subprograma y retornar al mismo cuando este
termine.
• Formar cadenas de caracteres.
• Separar texto.
• Evaluar expresiones matemáticas.
• Deshacer la última operación realizada, por ejemplo en un procesador de texto u otros programas similares.

Las pilas pueden ser representadas por medio de una lista unidireccional o por un array lineal. Representaremos las
pilas por medio de un array lineal
Tendremos un puntero especial apuntando a la CIMA y una variable MAXPILA que determina el tamaño máximo de
la pila. Para controlar el OVER-UNDERFLOW hay que saber determinar el tamaño de la pila; porque si lo hacemos
demasiado grande no habrá OVERFLOW pero tendremos una pérdida importante de memoria y viceversa.

Operaciones para manipular una Pila


Las operaciones que debemos implementar en una estructura de datos de tipo pila son las siguientes:
• Creación: crea una pila vacía.
• Inserción: agrega un nuevo elemento en el cima
• apilar(x)
• Borrado: elimina el elemento de la cima, si la pila no esta vacía
• x = desapilar()
• Consulta:
• Pila Vacía
• Elemento en la cima
Básicamente existen 2 técnicas posibles para lograr implementar estas funciones en tiempo constante:

• Pilas implementadas con datos de forma contigua a través de un Array o Vector


• Pilas implementadas con datos de forma no contigua a través de una estructura de datos de tipo Lista Enlazada
Simple

Interfaz Pila
La interfaz pila tiene los siguientes métodos que deberán ser implementados tanto en la pila vector como en la pila
lista enlazada
public interface Pila {
public int tamanyo();

public boolean esVacia();

public void apilar(Object o);

public Object desapilar() throws DesbordamientoInferior;

public Object cima() throws DesbordamientoInferior;


}
Implementación de la Pila con un Vector
La primera implementación de la pila que veremos es utilizando un vector y un entero. El entero es la cima de la pila
(cdp) y es el índice del vector correspondiente al elemento situado en la cima de la pila. Así cuando cdp es
igual a -1, la pila está vacía.

Para apilar se incrementa cdp y se inserta el nuevo elemento en la posición cdp del vector.

El acceso a la cima de la pila resulta trivial y la operación de desapilar se realiza decrementando cdp.

public class PilaVector implements Pila {


private static final int N = 1000;
private int capacidad;
private Object[] S;
private int cima = -1;

public PilaVector() {
this(N);
}

public PilaVector(int cap) {


capacidad = cap;
S = new Object[capacidad];
}

public int tamanyo() {


return cima + 1;
}

public boolean esVacia() {


return cima < 0;
}

public Object cima()throws DesbordamientoInferior {


if( esVacia() )
throw new DesbordamientoInferior(“Pila vacía”);
return S[ cima ];
}
}

Implementación de la Pila con una Lista Enlazada


Una alternativa a la implementación contigua, empleando vectores, consiste en utilizar una lista enlazada. La cima de
la pila está representada por el primer elemento de la lista. Para implementar la operación apilar, creamos un nuevo
nodo y lo incluimos en la lista como primer elemento. La pila se representa por un único atributo cimaDePila es una
referencia que apunta la primer Nodo de la lista enlazada. El constructor de la clase PilaLista crea una lista vacía
inicializando cimaDePila a null.

La operación apilar requiere una única línea de código: la creación de un nuevo nodo NodoLista. El atributo contiene
el elemento x a insertar y el elemento siguiente para el nuevo nodo de la cima de pila inicial. El nuevo nodo creado
para a ser la nueva cima.

La operación desapilar también es sencilla, tras el test para detectar la pila vacía se actualiza cimaDePila de modo que
apunte al segundo elemento de la lista.
Clase Nodo

class Nodo {
Object dato;
Nodo siguiente;

// constructor
public Nodo(Object o, Nodo n) {
dato = o;
siguiente = n;
}
}

Clase PilaEnlazada

public class PilaEnlazada implements Pila {


private Nodo cima;
private int tamanyo;

public PilaEnlazada() {
cima = null;
tamanyo = 0;
}

public int tamanyo() {


return tamanyo;
}

public boolean esVacia() {


return (cima == null);
}

public int apilar(Object o) {


Nodo n = new Nodo(o, cima);
cima = n;
tamanyo++;
}

public Object cima() throws DesbordamientoInferior{


if( esVacia() )
throw new DesbordamientoInferior(“Pila Vacía”);
return cima.dato;
}

public Object desapilar() throws DesbordamientoInferior {


if (esVacia())
throw new DesbordamientoInferior(“Pila vacía”);
Object temp = cima.dato;
cima = cima.siguiente;
tamanyo--;
return temp;
}
}
Caso Práctico
Supongamos la siguiente situación, Ud. está desarrollando un sistema para una institución educativa, y necesita
almacenar en una estructura de datos líneas tipo Pila los alumnos, la implementación la realizaremos a través de
una lista enlazada simple. Las clases intervinientes son Personas, Alumnos y Materia. Alumno extiende de Persona, y
Alumno puede cursar una o varias Materias. Desde una clase Test que contienen el método main utilizaremos
una estructura de tipo Pila implementada con lista enlazada para almacenar los alumnos:

Clase Persona
package uesiglo21.Taller1.Model;

public abstract class Persona {


private int dni;
private String nomb;
private String apel;

// Constructor de la clase Persona


public Persona(int dni, String nomb, String apel) {
this.dni = dni;
this.nomb = nomb;
this.apel = apel;
}

// Métodos getter y setter


public int getDni() {
return dni;
}

public void setDni(int dni) {


this.dni = dni;
}

public String getNomb() {


return nomb;
}

public void setNomb(String nomb) {


this.nomb = nomb;
}

public String getApel() {


return apel;
}

public void setApel(String apel) {


this.apel = apel;
}

public String toString() {


String aux;
aux = "D.N.I: " + dni + "\nNombre: " + nomb + "\nApellido: " + apel;
return aux;
}

}
Clase Alumno
package uesiglo21.Taller1.Model;

public class Alumno extends Persona {


private String legajo;

// Se utiliza un array de java de 15 elementos de máximo


private Materia Mat[] = new Materia[15];

// Constructor de la clase alumno


public Alumno(int dni, String nomb, String apel, String legajo) {
super(dni, nomb, apel);
this.legajo = legajo;

// Inicializa las materias en null


for (int i = 0; i < Mat.length; i++) {
Mat[i] = null;
}

public String getLegajo() {


return legajo;
}

public void setLegajo(String legajo) {


this.legajo = legajo;
}

public Materia[] getMat() {


return Mat;
}

public void setMat(Materia[] mat) {


Mat = mat;
}

// Asigna una materia al alumno y la guarda en el array


public boolean cargarAlumno(Materia m) {
for (int i = 0; i < Mat.length; i++) {
if (Mat[i] == null) {
Mat[i] = m;
return true;
}
}
return false;
}

public String toString() {


String aux;
aux = super.toString();
aux += "\nLegajo: " + legajo;
aux += "\nMaterias: ";
for (int i = 0; i < Mat.length; i++) {
aux += "\n" + Mat[i];
}
return aux;
}

}
Clase Materia
package uesiglo21.Taller1.Model;

public class Materia {


private String denom;
private int semestre;

// Constructor de la clase materia


public Materia(String denom, int semestre) {
this.denom = denom;
this.semestre = semestre;
}

public String getDenom() {


return denom;
}

public void setDenom(String denom) {


this.denom = denom;
}

public int getSemestre() {


return semestre;
}

public void setSemestre(int semestre) {


this.semestre = semestre;

public String toString() {


String aux;
aux = "Denominacion: " + denom + " - Semestre: " + semestre;
return aux;
}

Interfaz Pila

package uesiglo21.Taller1.Model;

public interface Pila {


public int tamanio();

public boolean esVacia();

public void apilar(Object o);

public void desapilar() throws DesbordamientoInferior;

public Object cima() throws DesbordamientoInferior;

public Object cimaYDesapilar() throws DesbordamientoInferior;

}
Clase NodoPila
package uesiglo21.Taller1.Model;

public class NodoPila {


private Object dato;
NodoPila siguiente;

// El constructor del nodo recibo el dato a guardar y el nodo siguientes


public NodoPila(Object O, NodoPila siguiente) {
this.dato = O;
this.siguiente = siguiente;
}

public NodoPila getSiguiente() {


return siguiente;
}

public void setSiguiente(NodoPila siguiente) {


this.siguiente = siguiente;
}

public Object getDato() {


return dato;
}

public void setDato(Object dato) {


this.dato = dato;
}

Clase PilaLista
package uesiglo21.Taller1.Model;

public class PilaLista implements Pila {


private NodoPila cima;
private int tamanio;

// Constructor sin parametros


public PilaLista() {
cima = null;
tamanio = 0;
}

public int tamanio() {


return tamanio;
}

public boolean esVacia() {


if (cima == null) {
return true;
} else {
return false;
}
}

// inserta un objeto en la pila


public void apilar(Object o) {
// apila el Nuevo nodo
cima = new NodoPila(o, cima);
tamanio++;
}
// Elimina el último elemento ingresado
public void desapilar() throws DesbordamientoInferior {
if (esVacia()) {
throw new DesbordamientoInferior("CIMA");
}
cima = cima.getSiguiente();
tamanio--;
}

// devuelve la cima (el elemento más reciente)


public Object cima() throws DesbordamientoInferior {
if (esVacia()) {
throw new DesbordamientoInferior("CIMA");
}
return cima.getDato();
}

// método que desapila y devuelve la nueva cima


public Object cimaYDesapilar() throws DesbordamientoInferior {
if (esVacia()) {
throw new DesbordamientoInferior("CIMA");
}
cima = cima.getSiguiente();
tamanio--;
if (esVacia()) {
throw new DesbordamientoInferior("CIMA");
}
return cima.getDato();
}
}

Clase Test
package uesiglo21.Taller1.View;
import uesiglo21.Taller1.Model.Alumno;
import uesiglo21.Taller1.Model.DesbordamientoInferior;
import uesiglo21.Taller1.Model.Materia;
import uesiglo21.Taller1.Model.PilaLista;
public class Test {
public static void main(String[] args) throws DesbordamientoInferior {
try {
// Creamos 3 Materias
Materia m1 = new Materia("ADE", 1);
Materia m2 = new Materia("ACO", 2);
Materia m3 = new Materia("SOR", 3);
// Creamos 4Alumnos
Alumno a1 = new Alumno(31549674, "Jose", "Gomez", "SOF-000090");
Alumno a2 = new Alumno(31549675, "Pablo", "Ramirez", "SOF-000091");
Alumno a3 = new Alumno(31549676, "Mauro", "Lopez", "SOF-000093");
Alumno a4 = new Alumno(31549677, "Pedro", "Gonzalez", "SOF-000094");
// Asociamos los alumnos a las materias
a1.cargarAlumno(m1);
a1.cargarAlumno(m2);
a1.cargarAlumno(m3);
a2.cargarAlumno(m1);
a2.cargarAlumno(m2);
a2.cargarAlumno(m3);
a3.cargarAlumno(m1);
a3.cargarAlumno(m2);
a3.cargarAlumno(m3);
a4.cargarAlumno(m1);
a4.cargarAlumno(m2);
a4.cargarAlumno(m3);
// creamos una pila de tipo lista vacia (Sin elementos)
PilaLista pl = new PilaLista();
// Apilamos los4 alumnos Anteriores
System.out.println("--------Apilar---------");
pl.apilar(a1);
System.out.println("----");
System.out.println(pl.cima().toString());
pl.apilar(a2);
System.out.println("----");
System.out.println(pl.cima().toString());
pl.apilar(a3);
System.out.println("----");
System.out.println(pl.cima().toString());
pl.apilar(a4);
System.out.println("----");
System.out.println(pl.cima().toString());
System.out.println("-----Cima-----");
System.out.println(pl.cima().toString());
// Desapilamos el ultimo element
System.out.println("\n---------Desapilar--------");
pl.desapilar();
System.out.println("---Nueva Cima----");
System.out.println(pl.cima().toString());
// Desapilamos y hacemos cima
System.out.println("\n---------Desapilar y Cima---------");
System.out.println("---Nueva Cima----");
System.out.println(pl.cimaYDesapilar().toString());
// Probamos el desbordamiento cuando la pila se queda sin elementos
System.out.println("\n-----Desbordamiento-----");
pl.desapilar();
pl.desapilar();
pl.desapilar();
} catch (Exception e) {
System.out.println("Se ha producido un error al manipular la Pila");
}
}

Estructuras de datos Weiss 16.2


Implementaciones con lista enlazada
La ventaja de la lista enlazada es que la cantidad adicional de memoria requerida es solamente de una referencia por
cada elemento. Por el contrario, una implementación basada en una matriz contigua utiliza una memoria adicional
igual al número de elementos de la matriz vacíos (más algo de memoria adicional durante la fase de duplicación del
tamaño). La ventaja de la lista enlazada puede ser significativa en otros lenguajes. En Java esta ventaja es mínima.

Para que la implementación sea competitiva con las implementaciones basadas en matriz contigua, debemos ser
capaces de realizar las operaciones básicas con la lista enlazada en un tiempo constante. Sencillo de conseguir, porque
los cambios en la lista enlazada están restringidos a los elementos situados en los dos extremos (anterior y posterior)
de la lista.

Pilas
La clase para las pilas puede implementarse como una lista enlazada en la que el elemento superior de la pila está
representado por el primer elemento de la lista.

Implementación de la clase Stack mediante lista enlazada.

Al implementar la clase de pila, la cima de la pila se representa mediante el primer elemento de una pila enlazada.
Para implementar una operación de inserción push, creamos un nuevo nodo en la lista y lo conectamos como nuevo
primer elemento de la misma. Para implementar una operación de extracción pop, simplemente hacemos avanzar
la cima de la pila hasta el segundo elemento de la lista (si existe). Una pila vacía estará representada por una lista
enlazada vacía. Cada operación se lleva a cabo en un tiempo constante, porque al restringir las operaciones al primer
nodo, hemos hecho que todos los cálculos sean independientes del tamaño de la lista.

La Figura 16.19 proporciona el esqueleto de la clase.

El constructor no está escrito


explícitamente, ya que de manera
predeterminada obtenemos una pila vacía
configurando topOfStack como NULL.
makeEmpty y isEmpty son por tanto
triviales y se muestran en las líneas 19 a
22.

Las líneas 39 a 49 proporcionan la declaración


de tipos para los nodos de la lista. Un nodo
ListNode estará compuesto por dos miembros
de datos: element que almacena el elemento y
next que almacena una referencia al siguiente
ListNode de la lista enlazada.
Proporcionamos constructores para ListNode
que se pueden utilizar para ejecutar tanto
ListNode<AnyType> p1 = new
ListNode<AnyType>( x );
como
ListNode<AnyType> p2 = new
ListNode<AnyType>( x, ptr2 );

Una opción consiste en anidar ListNode en la clase Stack. Aquí, hemos utilizado la alternativa, ligeramente peor, de
hacerlo una clase de nivel superior pero que solo tiene visibilidad de paquete, permitiendo así la reutilización de la
clase para la implementación de la cola. La propia pila está representada por un único miembro de datos, topOfStack,
que es una referencia al primer ListNode de la lista enlazada.
Figura 16.20 Las rutinas push y pop para la clase ListStack.

En la Figura se muestran dos rutinas. La operación


push es esencialmente una línea de código en la que
asignamos un nuevo ListNode cuyo miembro de
datos contiene el elemento x que queremos insertar.
La referencia next para este nuevo nodo es el valor
topOfStack original. Este nodo pasa entonces a
convertirse en el nuevo topOfStack. Hacemos todo
esto en la línea 7.
La operación pop también es simple. Después de la
comprobación obligatoria de que la pila no está
vacía, hacemos que topOfStack apunte al segundo
nodo de la lista.

Figura 16.21 Las rutinas top y topAndPop para la clase ListStack.

top y topAndPop son rutinas muy sencillas


y se implementan como se muestra en la
Figura

Colas
La cola puede implementarse mediante una lista enlazada, siempre y cuando mantengamos referencias tanto a front
como a back, es decir, al primer y el último elemento de la lista.

Figura 16.22 Implementación mediante lista enlazada de la clase de cola.


La clase ListQueue es similar a
la clase ListStack. En la Figura
se muestra el esqueleto de la
clase ListQueue.

Figura 16.24 Constructor para la clase ListQueue basada en lista enlazada.


El único aspecto nuevo aquí es
que mantenemos dos referencias
en lugar de una. La Figura 16.24
muestra los constructores de la
clase ListQueue.
Figura 16.25 Las rutinas enqueue y dequeue para la clase ListQueue.
La Figura implementa tanto enqueue como
dequeue. La rutina dequeue es lógicamente
idéntica a una operación pop en una pila
(en realidad, a popAndTop). La rutina
enqueue tiene dos casos distintos. Si la cola
está vacía, creamos una cola de un solo
elemento invocando new y haciendo que
tanto front como back hagan referencia al
único nodo existente. En caso contrario,
creamos un nuevo nodo con un valor de
datos x, lo añadimos al final de la lista y
luego reconfiguramos el final de la lista
para que apunte a este nuevo nodo

introducir en la cola el primer elemento es


un caso especial, porque no hay ninguna
referencia next a la que pueda asociarse un
nuevo nodo.
Hacemos todo esto en la línea 10

Introducir en la cola el primer elemento constituye un caso especial, porque no hay ninguna referencia next a la que
pueda asociarse un nuevo nodo.

Figura 16.27 Rutinas de soporte para la clase ListQueue.

Los métodos restantes para la clase


ListQueue son idénticos a las rutinas
correspondientes de ListStack.
Comparación de los dos métodos
Tanto las versiones basadas en matriz como las basadas en lista enlazada requieren un tiempo constante por cada
operación. Por tanto, son tan rápidas que es poco probable que lleguen a constituir el cuello de botella de cualquier
algoritmo, así que, a ese respecto, raramente importa qué versión utilicemos.

Las versiones basadas en matriz de estas estructuras de datos serán probablemente más rápidas que sus equivalentes
basadas en lista enlazada, especialmente si existe una estimación precisa de la capacidad requerida. Si se proporciona
un constructor adicional para especificar la capacidad inicial y la estimación es correcta, no llega a realizarse ninguna
operación de duplicación de tamaño.
Asimismo, el acceso secuencial proporcionado por un matriz suele ser más rápido que el acceso potencialmente no
secuencial ofrecido por el mecanismo de asignación dinámica de memoria.

La implementación basada en matriz presenta dos desventajas.


En primer lugar, para las colas, la implementación basada en matriz es algo más compleja que la implementación
basada en lista enlazada, debido a la combinación del mecanismo de encadenamiento circular y de duplicación del
tamaño de la matriz. Nuestra implementación del mecanismo de duplicación del tamaño de la matriz no es lo más
eficiente posible por lo que una implementación más rápida de la cola requeriría unas cuantas líneas adicionales de
código. Incluso la implementación matricial de la pila utiliza unas cuantas líneas más de código que su equivalente
basada en lista enlazada.

La segunda desventaja afecta a otros lenguajes, aunque no a Java. Al duplicar el tamaño de la matriz, requerimos
temporalmente el triple de espacio que el número de elementos de datos sugiere.
La razón es que, al duplicar la matriz, necesitamos disponer de un medio para almacenar tanto la matriz antigua como
la nueva (de tamaño doble). Además, para un tamaño pico de la cola, la matriz estará entre un 50 y un 100 por ciento
llena, en promedio, estará llena al 75 por ciento, de modo que por cada tres elementos de la matriz habrá una posición
vacía. El espacio desperdiciado será por tanto de un 33 por ciento como promedio, y del 100 por cien cuando la matriz
solo esté llena a la mitad.

En Java, cada elemento de la matriz es simplemente una referencia. En otros lenguajes, como C ++, los objetos se
almacenan directamente, en lugar de mediante referencia. En estos lenguajes, el espacio desperdiciado podría ser
significativo, si lo comparamos con el de la versión basada en lista enlazada, que solo utiliza una referencia adicional
por cada elemento.

Falta Actividad 2
Unidad 3
Colas
La cola restringe el acceso, limitándolo al elemento insertado menos recientemente.

Otra estructura de datos simple es la cola, la cual restringe el acceso limitándolo al elemento menos recientemente
insertado. En muchos casos, ser capaz de encontrar y/o eliminar el elemento más recientemente insertado es
importante. Pero en otra serie de casos, no solo no tiene ninguna importancia sino que es precisamente lo que no
tenemos que hacer. En un sistema multiproceso, por ejemplo, cuando se envían trabajos a una impresora, esperamos
que se imprima en primer lugar el trabajo menos reciente o más antiguo. Este orden no solo resulta justo, sino que
también es obligatorio para garantizar que el primer trabajo de impresión no esté esperando indefinidamente. Esa es la
razón por la que podemos esperar encontrarnos colas de impresión en todos los sistemas de gran tamaño.

Las operaciones básicas soportadas por las colas son las siguientes:
• enqueue, o inserción en la parte final de la cola.
• dequeue, o eliminación del elemento situado al principio de la cola.
• getFront, o acceso al elemento situado al principio de la cola.

Históricamente, dequeue y getFront han estado combinadas en una sola operación. Esto se hace haciendo que dequeue
devuelva una referencia al elemento que ha eliminado.

Modelo de cola: la introducción de datos se realiza mediante enqueue,


La consulta mediante getFront y el borrado mediante dequeue.

Las operaciones con una cola y las operaciones con una pila están restringidas de forma similar, cabe esperar que
también necesiten un tiempo constante por cada consulta, y efectivamente es así. Todas las operaciones básicas con la
cola tardan un tiempo O (1).

Pilas y colas en la API de Colecciones


La API de Colecciones proporciona una clase Stack para la pilas, pero no proporciona ninguna clase para las colas.
Los métodos de Stack son push, pop y peek. Sin embargo, la clase Stack amplía Vector y es más lenta de lo necesario;
como Vector, su uso ya no se recomienda y puede sustituirse por operaciones con List. Antes de Java 1.4, el único
soporte de java.util para las operaciones con colas era utilizar una LinkedList (por ejemplo, addLast, removeFirst y
getFirst). Java 5 añade una interfaz Queue para las colas. Sin embargo, necesitamos seguir empleando métodos
de LinkedList. Los nuevos métodos son add, remove y element.

Colas, Conceptos Básicos (Canvas)

Maneja el concepto FIFO El primer elemento en entrar es el primero en salir.

Las características de esta estructura de datos son:


• INSERCIÓN: los elementos que la forman por un extremo llamado FINAL
• ELIMINACIÓN: solo se realiza por un extremo llamado FRENTE

Es fácil acceder al primero de la cola, es el que está disponible y también es fácil ingresar a la cola solo hay que
colocarse detrás del último.
Una cola es una lista de elementos en la que la eliminación de éstos de dicha lista sólo se puede hacer por un extremo
de la cola (FRENTE) y las inserciones se van a realizar por otro extremo al que llamaremos (FINAL).
COLAS
ELIMINACIÓN: sólo por el frente
INSERCIÓN: sólo por el final.

Tenemos innumerables casos de uso de Colas en la vida diaria, bancos, cajas de supermercados, cines,
otros.
En informática la asociamos a colas de impresión, colas de prioridades.

Implementación de las colas en memoria


Las colas se pueden enlazar:
- listas circulares unidireccionales enlazadas.
- arrays lineales circular.
Para eliminar un elemento de la cola incrementamos FRENTE en 1 y para añadir se los incrementamos a
FINAL.
El problema que se plantea muchas veces es como insertar un elemento en la cola cuando está ocupada la
última posición del array.
La solución será que el array COLA sea circular y así en el caso de que FINAL=FRENTE insertaríamos el
siguiente elemento en COLA[1] siempre que no esté ocupada.

Para saber si está o no ocupada se verá con FRENTE. (frente<>1)


De forma análoga, si FRENTE=FINAL el siguiente elemento que borra es FRENTE(1).
En el procesamiento debemos contemplar las siguientes situaciones:
• INSERTAR: controlar el OverFlow
• BORRAR: controlar el UnderFlow
• VACIO, cuando Frente = 0

Procesamiento de las colas Inserción

Si ((FRENTE=1 o FINAL=N) ó (FRENTE=FINAL + 1))


Entonces OVERFLOW
sino Si (FRENTE=0)
entonces FRENTE=1
FINAL =1
sino Si (FINAL=N)
entonces FINAL 1
sino FINAL  FINAL + 1
fin_si
fin_si
COLA(FINAL)  ELEMENTO
fin_si
Borrado
Si (FRENTE=0)
entonces UNDERFLOW
sino ELEMENTO  COLA(FRENTE)
si (FRENTE=FINAL)
entonces FRENTE  0
FINAL  0
sino si ( FRENTE=N)
entonces FRENTE 1
sino FRENTE FRENTE + 1
fin_si
fin_si
fin_si

función VACIA(COLA:array[1..N] of CHAR,FRENTE:entero):boolean

inicio
si (FRENTE=0)
entonces VACIA  verdadero
sino VACIA falso
fin_si
fin

Algoritmos a fondo zsnajdedler

Estructura Cola (FIFO)


La cola (queue) es también una estructura lineal restrictiva en la que solo podremos poner y sacar
elementos respetando la siguiente restricción: el primer elemento en llegar a la cola será también el
primero en salir (FIFO, First In First Out).

Lista enlazada circular


Una lista circular es una lista enlazada en la que el último nodo apunta al primero.
Veamos gráficamente tres casos: una lista circular vacía, una lista circular con un único elemento y una
lista circular con más de un elemento.

Fig. 11.37 Lista enlazada circular.

Dado que en una lista circular el último nodo tiene una referencia al primero resultará más provechoso
mantener a p apuntando al último nodo de la lista ya que desde allí podremos acceder al primero, que
estará referenciado por p->sig.

Así, en una lista circular siempre tenemos referenciado el último nodo que agregamos (p) e,
indirectamente, también tenemos referenciado el primero (p->sig).
Implementar una cola sobre una lista circular
Volviendo al ejemplo de la caja en el supermercado, podemos ver que la gente que llega a la cola se ubica
al final. En cambio, la cajera siempre atiende al primero, que luego se retira de la cola para dejar su lugar
al segundo y así sucesivamente.

El análisis anterior demuestra que, para la implementación de la estructura cola, necesitamos una lista
enlazada con dos punteros: uno al primer nodo y uno al último. Justamente, esta característica la
encontramos en una lista circular.

Luego, para encolar un elemento lo agregaremos al final de la lista y para desencolar eliminaremos al
primero de la lista.

Analicemos una cola inicialmente vacía.

Ahora encolamos el valor 3:

Encolemos el valor 5 y luego el 8:

Fig. 11.38 Estructura Cola implementada sobre una lista circular.

Como vemos, el final de la lista siempre está apuntado por p y el inicio está referenciado por el p->sig.

El primer valor que encolamos es el 3 y es el que debe salir si queremos desencolar un elemento. Para
esto, según nuestro ejemplo, tenemos que hacer que el siguiente de 8 apunte al siguiente de 3 (es decir, al
5). Si con una variable aux apuntamos al siguiente de p entonces para desencolar simplemente hacemos
que el siguiente de p apunte al siguiente de aux y luego liberamos aux.

Fig. 11.39 Desencolar un elemento.


Operaciones encolar y desencolar
void encolar(Nodo** p, int v)
{
Nodo* nuevo = (Nodo*)malloc(sizeof(Nodo));
nuevo->valor=v;
if( *p==NULL ){
nuevo->sig=nuevo;
}else{
nuevo->sig=(*p)->sig;
(*p)->sig=nuevo;
}
*p=nuevo;
}

Veamos ahora cómo desencolar considerando el caso particular que se da al desencolar el último
elemento de la cola. Esta situación la identificamos cuando resulta que (*p)->sig es igual a p.

int desencolar(Nodo** p)
{
int ret=(*p)->sig->valor;
if( *p==(*p)->sig ){
free(*p);
*p=NULL;
}else{
Nodo* aux = (*p)->sig;
(*p)->sig=aux->sig;
free(aux);
}
return ret;
}

Veamos ahora el siguiente programa:

int main()
{
Nodo* p=NULL;
// encolamos varios elementos
encolar(&p,1);
encolar(&p,2);
encolar(&p,3);

// desencolamos un elemento (sale el 1)


printf("%d\n",desencolar(&p));

// desencolamos un elemento (sale el 2)


printf("%d\n",desencolar(&p));
La salida será:
// encolamos más elementos 1
encolar(&p,4); 2
encolar(&p,5); 3
4
encolar(&p,6); 5
// desencolamos un elemento (sale el 3) 6
printf("%d\n",desencolar(&p));

// desencolamos mientras queden elementos en la cola


while( p!=NULL )
{
printf("%d\n",desencolar(&p));
}
return 0;
}
Pilas y colas
Java provee la clase Stack cuya funcionalidad es la de una pila. En cambio, las colas se manejan con
implementaciones de la interface Queue. LinkedList, por ejemplo, implementa esta interface.
Veamos un ejemplo que muestra cómo utilizar la clase Stack con sus metodos push(apilar),
pop(desapilar) e isEmpty (está vacio).

package libro.cap15.stack;
import java.util.Stack;
public class TestStack
{
public static void main(String[] args)
{
Stack<String> pila = new Stack<String>();
pila.push("uno");
pila.push("dos");
pila.push("tres");
while( !pila.isEmpty() )
{
System.out.println(pila.pop() );
}
}
}

Veamos un ejemplo que muestra cómo utilizar la interface Queue que define los metodos add (encolar),
poll (desencolar) e isEmpty (está vacio)

package libro.cap15.queue;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Stack;
public class TestQueue
{
public static void main(String[] args)
{
Queue<String> cola = new LinkedList<String>();
cola.add("uno");
cola.add("dos");
cola.add("tres");
System.out.println( cola.poll() );
System.out.println( cola.poll() );
cola.add("cuatro");
cola.add("cinco");
cola.add("seis");
while( !cola.isEmpty() )
{
System.out.println( cola.poll() );
}
}
}
Actividad Unidad 3

Unidad 4
Implementación de Colas con listas enlazadas

En la estructura de datos de tipo cola también tenemos 2 formas de implementarla, al igual que en las pilas, podemos
utilizar un vector que almacena los elementos de forma contigua y la segunda consiste en almacenar los elementos de
forma no contigua por medio de una lista enlazada.
Las operaciones que debemos implementar en una estructura de datos de tipo Cola son las siguientes:
• Creación: crea una cola vacía.
• Inserción: agrega un nuevo elemento en la cola insertar(x).
• Borrado: elimina el elemento más antiguo, si la cola no está vacía.
x = quitarPrimero()
• Consulta: Cola Vacía

Interfaz Cola
La interfaz cola tiene los siguientes métodos que deberán ser implementados tanto en la cola vector como
en la cola lista enlazada

public interface Cola {

public boolean esVacia();

public void vaciar();

public void insertar(Object o);

public Object primero() throws DesbordamientoInferior;

public Object quitarPrimero() throws DesbordamientoInferior;

Colas Implementadas con Vector


La forma más sencilla de implementar una cola consiste en almacenar sus elementos en un vector, colocando el
elemento en cabeza en la primera posición del mismo (es decir, en el índice 0). Si fin representa la posición del último
elemento de la cola, entonces para insertar un elemento no tenemos más que incrementar fin, insertando el elemento
en esa posición.
La operación quitarPrimero es muy costosa, debido a la exigencia de posición los elementos de la cola desde el
principio del vector, con lo cual forzamos a la rutina quitarPrimero a desplazar la posición de todo los elementos del
vector, una vez eliminado el primero. Para resolver el problema, basta con incrementar cabeza cuando se realiza una
operación quitarPrimero, en lugar de desplazar todos los elementos. Entonces cuando la cola tiene un único elemento,
fin y cabeza representa la posición en el vector de dicho elemento. Siguiendo esta línea en una lista vacía fin y cabeza
se representan con -1.
Para vectores de longitud fija como los arrays en Java, esto podría causar que no quede más espacio libre en la cola.
Sin embargo podría existir espacio libre a reutilizar. Todas las posiciones anteriores a cabeza están vacías, por lo tanto
se podrían reciclar.
Con tal objetivo se introduce la circularidad: cuando fin y cabeza rebasan la posición del vector, se reposicionan en el
principio del mismo. Este nombre recibe el nombre de implementación circular.

public class ColaVector implements Cola {

private static final int N = 1000;


private int capacidad;
private Object[] S;
private int cabeza = 0;
private int fin = -1;
private int tamanyoActual = 0;

public ColaVector() {
this(N);
}

public ColaVector(int cap) {


capacidad = cap;
S = new Object[capacidad];

// Método que inserta un nuevo elemento


public void insertar( Object o )
throws DesbordamientoInferior
{
if (tamanyo() == capacidad)
throw new DesbordamientoInferior(“Cola llena”);
fin = incrementar( fin );
S[fin] = o;
tamanyoActual++;
}

// Método que quita el primer elemento y devuelve la cabeza


public Object quitarPrimero()throws DesbordamientoInferior{
if( esVacia() )
throw new DesbordamientoInferior(“Cola vacía”);
tamanyoActual--;
Object el = S[ cabeza ];
cabeza = incrementar( cabeza );
return el;
}

public Object primero() throws DesbordamientoInferior{


if( esVacia() )
throw new DesbordamientoInferior(“Cola vacía”);
tamanyoActual--;
return S[ cabeza ];
}
}
Colas con prioridad

“La cola con prioridad solo permite acceder al elemento mínimo”.

En un entorno multiusuario, el planificador del sistema operativo debe decidir cuál de entre varios procesos ejecutar.
En general, a cada proceso solo se le permite ejecutarse durante un periodo de tiempo fijo. Un algoritmo no muy
adecuado para implementar este tipo de procedimiento implicaría la utilización de una cola. Los trabajos se colocarían
inicialmente al final de la cola. El planificador tomaría una y otra vez el primer trabajo de la cola, lo ejecutaría hasta
que finalizara o se consumiera su límite de tiempo y lo volvería a colocar al final de la cola si no lo ha terminado. Esta
estrategia no es apropiada porque los trabajos cortos deben esperar, lo que hace que parezca que necesitan mucho
tiempo para ejecutarse. los trabajos cortos (es decir, los que emplean menos recursos) deberían tener precedencia
sobre los trabajos que ya hayan consumido una gran cantidad de recursos. algunos trabajos que hacen un uso intensivo
de los recursos, como por ejemplo los trabajos ejecutados por el administrador del sistema, pueden ser importantes y
también deberían tener precedencia.
Si asignamos a cada trabajo un número para medir su prioridad, entonces el número más pequeño (páginas impresas,
recursos utilizados) tenderá a indicar una mayor importancia. Por tanto, desearíamos poder acceder al elemento más
pequeño de una colección de elementos y eliminarlo de la misma. Para hacer esto utilizamos las operaciones findMin
y deleteMin. La estructura de datos que soporta estas operaciones es la denominada cola con prioridad y soporta
únicamente el acceso al elemento mínimo.
La cola con prioridad es una estructura de datos fundamental, antes de Java 5 no existía ninguna implementación de
ella en la API de Colecciones.

PriorityQueue es una clase que implementa la interfaz Queue. Así, insert, findMin y deleteMin se expresan
mediante llamadas a add, element y remove. PriorityQueue puede construirse sin ningún parámetro, con un
comparador o con otra colección compatible.

La Figura 6.41 ilustra el uso de este tipo de colas.

Ya que la cola con prioridad solo soporta las operaciones deleteMin y findMin, cabría esperar que su rendimiento
fuera un compromiso entre la cola de tiempo constante y el conjunto de tiempo logarítmico.

La cola básica con prioridad soporta todas las operaciones en un tiempo logarítmico de caso peor, utiliza solo una
matriz, soporta la inserción en un tiempo promedio constante, es simple de implementar y se conoce con el nombre de
montículo binario. Esta estructura es una de las estructuras de datos más elegantes que se conocen.
Una implementación alternativa que soporta una operación decreaseKey adicional es el denominado montículo de
emparejamiento
De todos modos, la implementación de PriorityQueue en Java 5 es suficiente para la mayoría de las aplicaciones de
colas con prioridad.
Una aplicación importante de las colas con prioridad es en la simulación dirigida por sucesos.

Una simulación dirigida por sucesos consiste en el procesamiento de una serie de sucesos consecutivos. Ejemplos de
sucesos aquí son: (1) la llegada de un cliente y (2) la marcha de un cliente, liberando a un cajero. En cualquier
momento, tendremos una colección de sucesos esperando a tener lugar. Para ejecutar la simulación, necesitamos
determinar cuál es el suceso siguiente; dicho suceso será aquel cuyo instante de materialización sea mínimo. Por tanto,
usaremos una cola con prioridad que extraiga el suceso con un tiempo mínimo para procesar la lista de sucesos de
manera eficiente.

Analizador de símbolos equilibrados


En ocasiones la falta de un símbolo (como la falta de un finalizador de comentario */) puede causar que el
compilador desparrame cientos de líneas de errores sin llegar a identificar el error real. Por ejemplo, la
secuencia [{}] es correcta, pero [([)] no lo es.
Una herramienta útil para ayudar a la depuración de mensajes de error del compilador es un programa que
compruebe si los símbolos están equilibrados.
En esta situación es útil una pila porque sabemos que cuando encontramos un símbolo de terminación
como }, debe emparejarse con el { anterior más reciente aún no emparejado. Podemos emplear el siguiente
algoritmo:

1- Inicializar una pila vacía.


2- Leer símbolos hasta el final del fichero.
a- Si el símbolo es de apertura, apilarlo en la pila.
b- Si el símbolo es de terminación y la pila está vacía, producir un error.
c- En otro caso, desapilar la cima de la pila. Si el símbolo desapilado no es el correspondiente
símbolo de apertura, producir un error.
3. Al finalizar el fichero, si la pila no está vacía, producir un error.
Clase Pila
public class Pila {
class Nodo {
char simbolo;
Nodo sig;
}
private Nodo raiz;
Pila() {
raiz = null;
}
public void insertar(char x) {
Nodo nuevo;
nuevo = new Nodo();
nuevo.simbolo = x;
if (raiz == null) {
nuevo.sig = null;
raiz = nuevo;
} else {
nuevo.sig = raiz;
raiz = nuevo;
}
}
public char extraer() {
if (raiz != null) {
char informacion = raiz.simbolo;
raiz = raiz.sig;
return informacion;
} else {
return Character.MAX_VALUE;
}
}
public boolean vacia() {
if (raiz == null) {
return true;
} else {
return false;
}
}
}

Clase Formula
Esta clase contiene la pantalla principal está construida con swing y el método balanceada()

import javax.swing.*;
import java.awt.event.*;
public class Formula extends JFrame implements ActionListener {
private JTextField tf1;
private JButton boton1;

public Formula() {
// Presenta la pantalla Principal
setLayout(null);
tf1 = new JTextField("{2*(4-5)-{3*4}-[4-5]}");
tf1.setBounds(10, 10, 230, 30);
add(tf1);
boton1 = new JButton("Verificar fórmula.");
boton1.setBounds(10, 70, 180, 30);
add(boton1);
boton1.addActionListener(this);
}

public void actionPerformed(ActionEvent e) {


// El botón llama a la funcion balanceada()
if (e.getSource() == boton1) {
if (balanceada()) {
setTitle("Está correctamente balanceada.");
} else {
setTitle("No está correctamente balanceada.");
}
}
}
/*
* La funcion utiliza una estructura de datos de tipo Pila De forma muy simple
* se puede determinar el balanceo de la formula En el ejemplo si detecta los
* símbolos que inicio ( [ { Inserta en la pila Si detecta los símbolos ) ] }
* entonces es un cierre, extrae y por lo tanto debe haber un inicio anterior.
*/
public boolean balanceada() {
Pila pila1;
pila1 = new Pila();
String cadena = tf1.getText();
for (int f = 0; f < cadena.length(); f++) {
if (cadena.charAt(f) == '(' || cadena.charAt(f) == '[' || cadena.charAt(f)
== '{') {
pila1.insertar(cadena.charAt(f));
} else {
if (cadena.charAt(f) == ')') {
if (pila1.extraer() != '(') {
return false;
}
} else {
if (cadena.charAt(f) == ']') {
if (pila1.extraer() != '[') {
return false;
}
} else {
if (cadena.charAt(f) == '}') {
if (pila1.extraer() != '{') {
return false;
}
}
}
}
}
}
if (pila1.vacia()) {
return true;
} else {
return false;
}
}

Resumen
En esta unidad hemos visto ESTRUCTURAS DE DATOS y los conceptos de:
• Abstracción, que es describir los datos y las operaciones sin considerar los factores de su implementación
• Pila como una lista lineal de elementos en la cual cada elemento solo puede ser insertado o eliminado por un
extremo denominado CIMA.
• Pilas y los lenguajes de programación, donde aprendimos las operaciones y los algoritmos típicos para
ejecutarlos.
• Colas, que es una lista de elementos en la que la eliminación sólo se puede hacer por un extremo de la cola
(FRENTE) y las inserciones se van a realizar por otro extremo llamado (FINAL).
• Lista enlazada o encadenada que es un conjunto de elementos en los que cada elemento contiene la posición ó
dirección del siguiente elemento de la lista

Lectura complementaria
Colas en Java
Los elementos de la cola se añaden y se eliminan de tal manera que el primero en entrar es el primero en salir. La
adición de elementos se realiza a través de una operación llamada encolar (enqueue), mientras que la eliminación se
denomina desencolar (dequeue). La operación de encolar inserta elementos por un extremo de la cola, mientras que la
de desencolar los elimina por el otro.
El siguiente interfaz muestras las operaciones típicas para colas:

También podría gustarte