Objetivos: Mostrar un algoritmo de ordenamiento simple, basado en el uso de colas.
Temas:
Se necesita una función leeCola que reciba como argumento el nombre de un archivo que contiene reales, lea su contenido y construya una cola con los reales leídos en el archivo. La función debe retornar la cola construida. Si el archivo reales.txt contiene los números:
5.3, 1.2, 8.0, 4.5
Solución:
Queue leeCola(String arch) {
Queue cola= new Queue();
TextReader lect= new TextReader(arch);
while (true) {
double x= lect.readDouble();
if (lect.eofReached()) {
break;
}
cola.put(x);
}
lect.close();
return cola;
}
Se necesita un procedimiento MuestraCola que despliega en pantalla los valores reales almacenados en una cola. El procedimiento debe dejar la cola en el mismo estado que la encontró.
Primera solución errada
Este procedimiento despliega el contenido de la cola, pero también
deja la cola vacía, después de la invocación de muestraCola.
Esto se puede apreciar con el siguiente programa que usa leeCola
y muestraCola:
void muestraCola(Queue cola) {
while (! cola.isEmpty())
print(cola.getDouble()+" ");
println();
}
La invocación de leeCola construye una cola con los
reales 5.3, 1.2, 8.0 y 4.5 (en ese orden). La cola es referenciada
por la variable reales. Luego, la primera invocación
de muestraCola despliega:
Queue reales= leeCola("reales.txt");
println(reales.isEmpty()); // false
muestraCola(reales);
println(reales.isEmpty()); // true
5.3 1.2 8.0 4.5
Esto se puede entender mejor recurriendo a la metáfora de los robots. La cola es un robot y la variable reales almacena el número del celular de ese robot. Al invocar el procedimiento muestraCola se le pasa como argumento es número de ese celular. El procedimiento envía órdenes al robot para que extraiga los elementos de la cola. El robot que recibe estas órdenes es el mismo que es conocido por medio de la variable reales. De modo que cuando el procedimiento retorna y entonces se pregunta al robot si la cola que administra esta vacía, éste responde que sí.
Segunda solución errada
A medida que se extraen los elementos de la cola, regresarlos al final de la cola:
Esta solución es incorrecta porque al agregar los elementos al final la
cola nunca quedará vacía y por lo tanto el ciclo será eterno, desplegando
en pantalla varias veces el contenido de la cola.
void muestraCola(Queue cola) {
while (! cola.isEmpty()) {
double x= cola.getDouble();
print(x+" ");
cola.put(x); // resgresamos el valor al final de la cola
}
println();
}
Tercera solución errada
La idea es construir una segunda cola e ir dejando los elementos
en esa cola.
Esta solución también es incorrecta, porque aún cuando los elementos
quedan resguardados en la cola aux, esta cola es desconocida
desde fuera del procedimiento. La variable reales usada
como argumento en la invocación continuará referenciado el mismo
robot al cual se ordenó extraer todos sus elementos.
void muestraCola(Queue cola) {
Queue aux= new Queue();
while (! cola.isEmpty()) {
double x= cola.getDouble();
print(x+" ");
aux.put(x); // resguardamos los elementos en aux
}
println();
}
Un típico error que se comete es agregar una línea que retorne la cola:
Esto es un error porque muestraCola ha sido definida
como un procedimiento y no como una función, y por lo tanto, no
puede retornar ningún valor.
return aux;
Entre paréntesis, se podría definir que muestraCola sea una función mediante:
y luego reemplazar la invocación por:
Queue muestraCola(Queue cola) {
Queue aux= new Queue();
...
return aux;
}
Esto cambia la variable reales, almacenando el número del celular
del nuevo robot que fue construido por la función muestraCola.
Sin embargo, esta solución es poco elegante, porque los que usen la
función (los que la invoquen) correrán el riesgo de olvidar la asignación
y escribir simplemente:
reales= muestraCola(reales);
lo cual es legal en Java, pero deja la variable reales referenciando
una cola vacía.
muestraCola(reales);
Primera solución correcta
La idea es que después de desplegar el contenido de la cola, se restaure su contenido, obteniéndolo de la cola aux.
Esto dejará la cola aux vacía, pero no importa, porque esa
cola es desconocida desde el exterior del procedimiento.
void muestraCola(Queue cola) {
Queue aux= new Queue();
while (! cola.isEmpty()) {
double x= cola.getDouble();
print(x+" ");
aux.put(x); // resguardamos los elementos en aux
}
println();
// ahora restauramos el contenido de cola
while (!aux.isEmpty())
cola.put(aux.getDouble());
}
Segunda solución correcta
Esta solución se basa en la segunda solución errada, que agregaba los elementos al final de la cola, pero usa un método adicional que entrega el número de elementos que hay en una cola:
Con este método podemos hacer que sólo se hagas las iteraciones
que corresponden al número de elementos que hay originalmente en la cola:
int tamanno= cola.size(); // número de elementos en espera
void muestraCola(Queue cola) {
int tamanno= cola.size();
int i= 1;
while (i<=tamanno) {
double x= cola.getDouble();
print(x+" ");
cola.put(x); // regresamos el valor al final de la cola
i= i+1;
}
println();
}
Problema
Escriba una función double minimo(Queue cola) que recibe una cola con números reales y extrae y retorna el mínimo de la cola. Por ejemplo, si la cola tiene los valores:
9 2 7 1 3 6
9 7 2 3 6
Solución correcta
Por simplicidad supondremos que la cola no está vacía. Para resolver el problema usaremos una variable real min que mantendrá el mínimo parcial a medida que se visitan los elementos de la cola. Entonces, procedemos de la siguiente forma:
double minimo(Queue cola) {
int tamanno= cola.size();
double min= cola.getDouble();
int i= 1;
while (i<tamanno) {
double x= cola.getDouble();
if (x>min) {
cola.put(x);
}
else {
cola.put(min);
min= x;
}
i= i+1;
}
return min;
}
Se necesita un procedimiento que recibe como parámetro una cola con números reales y la ordene de menor a mayor. Por ejemplo, si la cola reales contiene:
5.0, 3.0, 9.0, 7.0, 2.0
2.0, 3.0, 5.0, 7.0, 9.0
La idea es ir extrayendo el mínimo elemento de la cola y dejándolo en una cola auxiliar hasta que se vacíe la cola original. Al final, la cola auxiliar estará ordenada. Entonces, se transfieren todos los elementos de la cola auxiliar a la cola inicial, manteniendo el orden en el proceso.
Aplicación: ordenamiento de un conjunto de números reales contenidos
en un archivo.
void ordenar(Queue cola) {
// Construimos la cola auxiliar
Queue aux= new Queue();
// Hacemos un ciclo extrayendo el mínimo hasta vaciar la cola
while (! cola.isEmpty())
aux.put(minimo(cola)); // el mínimo lo agregamos al final de aux
// Ahora aux está ordenada.
// Transferimos los elementos de aux a cola.
while (! aux.isEmpty())
cola.put(aux.getDouble());
}
El algoritmo aquí presentado funciona correctamente, pero veremos más
adelante que resulta muy ineficiente porque:
Queue reales= leeCola("reales.txt");
ordenar(reales);
muestraCola(reales);
Número de | Tiempo de |
---|---|
elementos | ordenamiento |
100 | menos de 1 seg. |
500 | 5 seg. |
1000 | 20 seg. |
2000 | 80 seg. |
100000 | 2 dias |
Observación:
El ordenamiento es el problema más frecuente en computación. De hecho, el 50% del tiempo de uso de un computador se destina a realizar ordenamientos de datos.
Pronto veremos mecanismos de ordenamientos mucho más eficientes.
Tarea
Escriba un programa que lea el archivo "nombres.txt" que contiene
nombres de personas y los muestre en pantalla ordenados alfabéticamente.
Por ejemplo si el contenido del archivo es:
Defina una versión recursiva del procedimiento ordenar.
Para ello, utilice el siguiente algoritmo:
Caso base:
Si cola esta vacía, se puede considerar que está ordenada,
de modo que ordenar retorna de inmediato, sin hacer nada.
Invocaciones recursivas:
pedro
ana
juan
patricia
diego
ana
diego
juan
patricia
pedro
Este ejercicio puede resultar difícil desde un punto de vista conceptual,
pero si Ud. logra realizarlo, Ud. ha comprendido perfectamente el
concepto de recursividad.