Cap´ıtulo 5 Colas

Anuncio
Capı́tulo 5
Colas
Las colas al igual que las pilas son un tipo especial de listas en las cuales los
elementos se insertan por un lado y se eliminan por el otro. Es decir se sacan
en el mismo orden en que entraron. En ocasiones suele referirse a ellas como
estructuras FIFO (first in, first out).
En la vida diaria tenemos las filas en las taquillas, en el banco, en el super
etc. En computación se usan mucho por ejemplo, en sistemas operativos para
listar los archivos que se desean imprimir (sin matar a nadie) denominadas
colas de impresión, los buffers de lectura, etc. en la teorı́a de gráficas, etc.
5.1
Implementación
La interfaz podrı́a ser:
interface Encolable {
public boolean estáVacı́a();
public void limpiar() ;
public void agregar(Object dato) ;
public void eliminar() ;
public Object tomar();
public java.util.Iterator elementos();
}
Donde los métodos
• agregar. Inserta Un nuevo elemento colocandola al final de la cola. La
posición de los otros elementos no se altera.
1
5.1. IMPLEMENTACIÓN
2
• eliminar. Elimina el elemtno que está al inicio de la cola. Si la cola
está vacı́a se genera un error. (NoSuchElementException).
• tomar. Devuelve el valor del primer elemento de la cola, sin alterar el
estado de ésta. Este método no altera el estado de la cola.
• estáVacı́a. Permite saber si la pila está vacı́a.
• limpiar. Vacia el contenido de una pila.
Una implementación usando estructuras ligadas se presenta a continuación:
/**
* Cola usando nodos
* Programo: Amparo López Gaona
* Marzo 2006
*/
public class Cola implements Encolable{
protected Nodo inicio;
protected Nodo fin;
private final Comparator prueba;
/** Construye la cola */
public Cola() {
inicio = null;
fin = null;
prueba = new DefaultComparator();
}
/** Construye una cola con el comparador proporcionado. */
public Cola(Comparator c) {
inicio = null;
fin = null;
prueba = c;
}
/** Prueba que la cola esté vacı́a.
* @return true si está vacı́a y false en otro caso.
5.1. IMPLEMENTACIÓN
3
*/
public boolean estáVacı́a() {
return inicio == null;
}
/** Crea una cola vacı́a.
public void limpiar() {
inicio = fin = null;
}
*/
/** Inserta en la cola, siempre por el final
* @param dato - elemento que será insertado
*/
public void agregar(Object dato) {
if (inicio == null)
inicio = fin = new Nodo(dato);
else {
Nodo temp = new Nodo(dato);
fin.sgte = temp;
fin = temp;
}
}
/** Devuelve el primer elemento de la cola
* @return Object - elemento del inicio de la cola
*/
public Object tomar() {
if (inicio == null) return null; //La cola está vacı́a
return inicio.elemento;
}
/** Elimina el primer elemento de la cola
public void eliminar() {
if (inicio != null)
inicio = inicio.sgte;
}
public java.util.Iterator elementos() {
return new MiIterador();
*/
5.2. EJEMPLO
4
}
// Falta el iterador y el comparador por omisión
5.2
Ejemplo
En esta sección se presenta el clásico problema del consumidor productor,
sólo que muy reducido. El productor produce una cantidad de productos
y los deposita en una banda (cola). El consumidor consume, de la banda,
alguna otra cantidad de productos, esperando hasta que en la banda haya la
cantidad suficiente.
Si los productores y los consumidores son independientes ¿cómo se comunican/sincronizan entre sı́?. R. vı́a colas.
Cada vez que se produce un artı́culo éste se coloca en una cola sin preocuparse del momento en que será consumido ni por quién. El consumidor
consume de la cola y no se preocupa ni cuándo ni por quién fue producido.
El ejemplo concreto consiste en simular el trabajo en un panaderı́a que
expende cajas con tres tipos de panes: conchas, donas y cuernos. Para ello
se tienen tres tipos de panaderos cada uno especializado en producir un
tipo de pan. Cada pan es colocado en una cola común y del otro lado un
empacador los toma para colocarlos en una caja. La cantidad de elementos
que se guardan en la caja está determinada por el peso de los mismos y la
capacidad máxima de las cajas.
Para resolver este problema se requieren varias clases: una para los panes,
otra para las cajas, otra para los panaderos, otra para los empacadores y otra
más para la panaderı́a.
Un pan se simula con su peso y nombre. Los únicos métodos que tiene
son un constructor y el toString que devuelve el nombre del pan.
class Pan {
private final double peso;
privata final String nombre;
public Pan (double pp, String n) { peso = pp; nombre = n; }
public double peso() {return peso;}
public String toString () { return nombre; }
}
La clase de las cajas tiene el peso de la caja en cada momento y una
5.2. EJEMPLO
5
lista en la que se guardan los panes. (Aquı́ podrı́a ir cualquier estructura
incluyendo arreglo dinámico). Los métodos que tiene además del constructor
son uno para ir guardando los panes en la caja, otro que proporciona el peso
actual de la caja y otro método para mostrar lo que se tiene en ella, para ello
se utiliza el iterador de la clase Lista.
class Caja {
private double pesoC;
private Lista panes;
private int pesoMaximo;
// peso actual de la caja
// panes
// Peso maximo de la caja
public Caja () {
pesoC = 0;
panes = new Lista();
pesoMaximo = 120;
}
public Caja (int n) {
pesoC = 0;
panes = new Lista();
pesoMaximo = n;
}
public void guardaPan (Pan p) {
panes.insertar(p);
pesoC += p.peso;
}
public double peso () { return pesoC; }
public double pesoMax() { return pesoMaximo;}
public String toString () {
java.util.Iterator e = panes.elementos();
String resultado = "Caja con ";
while (e.hasNext())
resultado = resultado + e.next()+" ";
5.2. EJEMPLO
6
return resultado;
}
}
La clase Panadero simula el comportamiento de un panadero, para ello se
requiere especificar el tipo de pan que fabrica, el tiempo que le toma hacerlo
y la cola en donde dejara el pan. Aquı́ se introduce un nuevo concepto: el de
hilos. Hasta ahora toda la programación ha sido en forma secuencial desde la
primera instrucción del main hasta la última. Ahora se va a escribir una clase
que puede trabajar de manera concurrente, es decir, un poco independiente
del flujo del programa principal. En java esto se conoce como un hilo. 1
Este nuevo programa o proceso requiere para su ejecución un método run
que es el equivalente al main, en el sentido de inicialización, para ejecución
no-concurrente.
class Panadero extends Thread {
private Cola banda;
private Pan pas;
private int tiempo;
public Panadero (Cola c, Pan p, int t) {
banda = c;
pas = p;
tiempo = t;
}
public void run () {
while (true) {
banda.agregar(pas);
try { sleep(tiempo);}
catch(Exception e) { }
}
}
//Agrega un pan a la banda
//Tiempo que tarda en hacer un pan
}
La clase Empacador también va a trabajar de manera concurrente, aquı́ sólo
necesita una cola como estructura del objeto. En el método run se simula el
1
Un hilo es un programa que corre dentro de otro.
5.2. EJEMPLO
7
llenado de la caja. Aquı́ se tiene la instrucción synchronized que especifica que en la cola denominada banda se hará el trabajo concurrente, para no
intentar sacar de ella un pan mientras algún panadero está colocando alguno.
class Empacador extends Thread {
private Cola banda;
private final int peso;
public Empacador (Cola c, int p) {
banda = c;
peso = p;
}
public void run () {
int contadorCajas = 0;
Caja miCaja = new Caja(peso);
while (contadorCajas <= 0) {
synchronized (banda) {
if (! banda.estáVacı́a()) {
Pan p = (Pan) banda.tomar();
double pesoF = miCaja.peso() + p.peso();
if (pesoF > miCaja.pesoMax()) {
System.out.println(miCaja+"\n\n");
miCaja = new Caja(peso);
}
miCaja.agregaPan(p);
}
} } }
La clase Panaderı́a se encarga de poner a trabajar a los panaderos y al
empacador. Para ello se crea cada objeto y luego se llama al método start
que a su vez se encarga de llamar implı́citamente al método run.
class Panaderia {
public static void main (String [ ] args)
Cola colaPastas = new Cola();
Panadero p1 = new Panadero (colaPastas,
Panadero p2 = new Panadero (colaPastas,
Panadero p3 = new Panadero (colaPastas,
Panadero p4 = new Panadero (colaPastas,
{
new
new
new
new
Pan(10.0,
Pan(25.0,
Pan(20.0,
Pan(30.0,
"galleta"), 10);
"dona"), 70);
"cuerno"), 50);
"concha"), 60);
5.2. EJEMPLO
8
Empacador c = new Empacador(colaPastas,250);
p2.start(); p3.start(); p4.start(); p1.start(); c.start();
}
}
Un ejemplo de ejecución de este programa es:
Caja con dona cuerno concha galleta galleta galleta galleta galleta
cuerno galleta concha galleta dona galleta galleta galleta
Caja con cuerno galleta concha galleta galleta dona galleta cuerno
galleta galleta galleta concha galleta galleta cuerno
Caja con dona galleta galleta galleta concha galleta cuerno galleta
galleta galleta dona galleta galleta concha cuerno galleta
Caja con galleta galleta galleta dona galleta cuerno concha galleta
galleta galleta galleta cuerno galleta galleta dona concha
Caja con galleta galleta galleta cuerno galleta galleta concha galleta
dona galleta cuerno galleta galleta galleta galleta concha galleta
...
5.2.1
Backtracking
El backtrack es una técnica que consiste en permitir elegir entre varias posibilidades un camino de solución y en caso de no ser correcto regresar al
punto donde se tomó la decisión.
Ejemplo: Encontrar la salida en un laberinto, como el siguiente:
+--+--+--+--+--+
|F |
__
|
| |__
__| |
|__ __
|__
|
| | | |
|
|___________|_S|
5.2. EJEMPLO
9
La representación del laberinto puede realizarse mediante el siguiente
código:
0
0000
1
2
3
4
5
0001
0010
0011
0100
0101
6
0110
7
0111
8
9
10
11
12
13
14
15
0100
0101
1010
1011
1100
1101
1110
1111
Figura 5.1:
Con ello, el programa puede hacerse general leyendo de un archivo la
configuración del laberinto.
El laberinto del ejemplo usando la configuración descrita queda como
sigue:
+--+--+--+--+--+
|14|12 5_ 4 6 |
|10|9_ 4 3_|10|
|9_ 5_ 2 |13 2 |
|14|14|10|12 2 |
|9__1__1__3_|11|
Para el programa se utiliza un arreglo visitado que marca el orden en
que se visitan las celdas. Este arreglo tiene dos propósitos: dar una forma de
indicar si una celda ya se ha visitado y proporcionar una representación del
orden en el que se realiza la búsqueda.
/**
* Programa que muestra la salida de un laberinto (si la hay)
* @author ALG
* @version Octubre 2006
*/
public class Laberinto {
private int largo;
private int ancho ;
5.2. EJEMPLO
private int [][] paredes = {
{14, 12, 5, 4, 6},
{10, 9, 4, 3, 10},
{9, 5, 2, 13, 2},
{14, 14, 10, 12, 2},
{9, 1, 1, 3, 11},
};
private int [][] visitado;
public Laberinto () {
largo = 5;
ancho = 5;
visitado = new int[5][5];
for (int i = 0; i < ancho; i++)
for (int j = 0; j < largo; j++)
visitado[i][j] = 0;
}
public Laberinto (String file) throws IOException {
DataInputStream in = new DataInputStream(
new FileInputStream(file));
ancho = in.readInt();
largo = in.readInt();
paredes = new int[ancho][largo];
visitado = new int[ancho][largo];
for (int i = 0; i < ancho; i++)
for (int j = 0; j < largo; j++) {
paredes[i][j] = in.readInt();
visitado[i][j] = 0;
}
}
private void resolverLaberinto () {
Deque que = new Deque();
que.agregarÚltimo(new Punto(largo-1, ancho-1));
int contVisitas = 0;
while (! que.estáVacı́a()) {
Punto p = (Punto) que.tomarÚltimo();
10
5.2. EJEMPLO
que.eliminarÚltimo();
if (visitado[p.obtenerX()][p.obtenerY()] == 0) {
visitado[p.obtenerX()][p.obtenerY()] = ++contVisitas;
mostrar();
if ((p.obtenerX() == 0) && (p.obtenerY() == 0))
return; // Se llegó a la meta
ponVecinos(p.obtenerX(), p.obtenerY(), que);
try {Thread.sleep(200);} catch (Exception e) { }
}
}
System.err.println("No hay solución");
}
private void ponVecinos (int x, int y, Deque que) {
if ((paredes[x][y] & 1) == 0)
que.agregarÚltimo(new Punto(x+1, y));
if ((paredes[x][y] & 2) == 0)
que.agregarÚltimo(new Punto(x, y+1));
if ((paredes[x][y] & 4) == 0)
que.agregarÚltimo(new Punto(x-1, y));
if ((paredes[x][y] & 8) == 0)
que.agregarÚltimo(new Punto(x, y-1));
}
public void mostrar() {
System.out.println("El laberinto original tiene:");
for(int i = 0; i < largo; i++){
for (int j = 0; j < ancho; j++)
System.out.print(paredes[i][j]+ " ");
System.out.println();
}
System.out.println("\nSolución:\n");
for(int i = 0; i < largo; i++){
for (int j = 0; j < ancho; j++)
System.out.print(visitado[i][j]+ " ");
System.out.println();
}
11
5.2. EJEMPLO
12
}
public static void main (String [ ] args) {
Laberinto mundo = new Laberinto();
mundo.mostrar();
mundo.resolverLaberinto();
}
}
Se utiliza una deque para mantener las celdas que están en espera de ser
investigadas. Se inicializa con la celda inicial (la esquina inferior derecha) del
laberinto.
Se van removiendo elementos de la deque, si es un punto que no se ha
visto, se marca como vistado. Luego todos los vecions de la celada actual se
coloca en la deque.
El método ponerVecinos busca las celdas válidas verificando si los lados
de la variable paredes están abiertos.
Seguimiento del programa:
Si se tuviera en la deque el elemento (3,4):
+-------+
I-> | (3,4) |
+-------+
<-F
Se saca el elemento y se introducen sus vecinos:
+-------+-------+-------+
I-> | (3,3) | (2,4) | (4,4) | <-F
+-------+-------+-------+
Al introducir los vecinos no se verifica que hayan sido visitados previamente, esto se hace al sacarlos.
Siempre se saca un sólo nodo y se introducen sus vecinos. Dos pasos más
adelante se tiene el siguiente contenido:
+-------+-------+-------+-------+-------+-------+
I-> | (4,1) | (3,2) | (4,3) | (3,3) | (2,4) | (4,4) | <-F
+-------+-------+-------+-------+-------+-------+
Descargar