| |
Boletín de problemas 9
Fecha de entrega: viernes
3 de mayo de 2002
Problema 1. Argumentos de línea
de comandos (10%)
Un modo
de obtener datos de entrada del usuario es analizar
los argumentos de la línea de comandos del método
main() (véase
página 99 del texto). El
siguiente método main de ejemplo imprime cada argumento de
línea de comandos en una línea separada.
static
void main(String argv[]) {
for
(int i=0; i<argv.length; i++)
System.out.println(argv[i]);
}
Escriba
un programa Java corto llamado IntSummer
para dar entrada a una lista de enteros con la línea
de comandos. Haga que el programa almacena los enteros
en un array de ints, calcule la suma
de los enteros, y muestre el resultado de la suma con
println. Asuma
que la entrada de datos tiene el formato correcto.
Si utiliza
una terminal UNIX o MS-DOS para ejecutar el programa,
puede especificar los argumentos de comando de línea
a su programa Java compilado de este modo:
unix%
java IntSummer 1 2 3 4 5 6 7
Si ejecuta
el programa desde Forte, puede definir los argumentos
de la línea de comandos desde el menú
"Build", en la opción de menú "Set Arguments".
A partir
de ahí, puede ejecutar el programa como siempre.
Problema 2. Características
del robot de embalaje. (90%)
Descripción del problema
En el boletín
de problemas 7,
hay un robot en la estación de embalaje cuyo
cometido es poner los equipos en los palets antes de
que se envuelvan en película retráctil
y se envíen. Ha
de saber cuántos equipos, de clases diferentes,
puede poner en un palet. Los
equipos que se embalan en película retráctil
y se colocan sobre un palet son para un pedido específico
de un cliente, que suelen ser impredecibles y muy variables. El
robot necesita cierta inteligencia para tomar la decisión
de embalaje en tiempo real.
Aunque
se trata de un problema tridimensional, lo reduciremos
a una dimensión (como se suele hacer en la práctica),
del siguiente modo:
Tenemos
un palet de longitud M, y un conjunto de equipos, cada
uno de una longitud Li. Queremos
saber qué subconjunto de equipos se pueden
poner en este palet. Tras
colocar un subconjunto en el primer palet, repetimos
a continuación el cálculo para los equipos
restantes hasta que se colocan en el palet. Normalmente,
el último palet no se llenará del todo.
(En algunos casos, muchos o incluso todos los palets
no se llenan del todo). Asumimos que la altura y la
anchura de los equipos es suficientemente similar como
para no tenerse en cuenta. Aunque parece una asunción
desproporcionada, suele ser viable en la práctica,
especialmente si elegimos bien la orientación
del embalaje de cada equipo (sobre su base, sobre su
lateral, sobre su extremo).
En esta
tarea, vamos a escribir un programa para que determine
los equipos que se van a colocar en el primer palet.
La generalización para determinar la ubicación
de los palets del resto de los equipos no es muy complicada.
Utilizaremos
un árbol para representar este problema y para
estructurar su solución.
Dispondremos las longitudes Li en
orden no decreciente. Cada longitud Li
estará representada por un nodo
en un árbol. El
nodo raíz del árbol será un nodo
'vacío', es decir, un nodo 'nulo'.
Desde cada
nodo, generaremos potencialmente dos hijos:
-
Un nodo izquierdo,
correspondiente a la longitud siguiente que está
dentro de la solución
-
Un nodo derecho,
correspondiente a la longitud siguiente que no está
dentro de la solución
La siguiente
figura muestra el enfoque general.
En esta
figura, asumimos que tenemos un array de equipos
con 6 longitudes: {5, 10, 12, 13, 15, 18}. La longitud
del palet es 30. En
cada nodo, mostramos la longitud de los equipos
que ya están en la solución y la de todos
los equipos que todavía hay que calcular. Así,
en el nodo raíz, hay una longitud 0 en la solución
y 73 unidades de longitud que todavía hay que
tener en cuenta. Utilizamos un array o Vector
x para localizar la solución en cada punto del
árbol.
Cuando xi= 1, la longitud Li
está en la solución; si xi=0,
la longitud Li no está en la solución.
|
|
 |
En la figura,
comenzamos colocando arbitrariamente la primera longitud,
5, en la solución.
Lo hacemos definiendo x0= 1.
En este nodo, el hijo izquierdo de la raíz,
tenemos 5 unidades en la solución, y 68 que hay
que tener en cuenta. Luego
ponemos la longitud 10 en la solución, generando
así el siguiente hijo de la izquierda del árbol.
En este nodo (15,58), hay 15 unidades en la solución
y 58 que hay que calcular. A continuación, podríamos
considerar poner la longitud 12 en la solución
para generar el nodo (27, 46); sin embargo, no podríamos
profundizar en esta rama del árbol porque la
siguiente longitud, 13, haría que la suma de
la solución actual, 27, más 13 excediese
el objetivo, 30. El
nodo (27, 46) se muestra en gris porque el algoritmo
descrito más abajo poda esta rama (mediante la
regla de poda núm. 2) en lugar de generar el
nodo.
A continuación
generamos el hijo derecho del nodo (15,58) excluyendo
la longitud 12 de la solución, para obtener el
nodo (15,46). Pensamos en la posibilidad de añadir
la longitud 13 (x3=1) para obtener el nodo
(28,33), pero de nuevo no podemos avanzar, porque la
siguiente longitud, 15, haría que el resultado
de la suma sobrepasase 30. Al igual que antes, podamos
el nodo izquierdo y no generamos el nodo (28,33). Volvemos
al nodo (15,46) y excluimos la longitud 13, definiendo
x3=0. Esto genera un hijo derecho (15,33), e incluyendo la siguiente
longitud, 15, alcanzamos una solución. Esta solución
tiene x0=1, x1=1, x2=0,
x3=0 y x4=1; en otras palabras,
incluye las longitudes 5, 10 y 15.
Puede repasar
el resto del ejemplo para ver como el algoritmo avanza
generando hijos izquierdos hasta que no hay más
soluciones, y entonces retrocede, genera un hijo derecho
(si es posible), y continúa. El
algoritmo acaba por atravesar el árbol entero
que ha generado y concluye. Observe que no se genera el árbol de decisión
completo, que en este ejemplo tendría una profundidad
de 6 en todas sus rutas, porque podemos finalizar la
generación de nodos antes, cuando estamos seguros
de que no existen más soluciones en una ruta
utilizando un conjunto de reglas de poda.
En un nodo,
se pueden aplicar tres reglas de poda para determinar
si se deberían generar más hijos. Si hacemos
que:
s=
suma de las longitudes ya en la solución, (primera
cantidad mostrada en los nodos)
r=
suma de las longitudes que hay que calcular, (segunda
cantidad)
Lk=
longitud del nodo k (k indica el nodo actual)
M=
suma objetivo de las longitudes
Así,
las tres reglas de podado se pueden enunciar como:
- Si
(s + Lk =M), muestra la solución
- Si
(s + Lk + Lk+1 <M), genera
hijo izquierdo (xk= 1)
- Si
(s + r - Lk >= M) y (s + Lk+1
<=M), genera hijo derecho (xk=0)
La regla
1 es muy sencilla: consiste en que si la solución
en este nodo, más la longitud actual, equivale
al objetivo, se muestra el resultado.
La regla
2 enuncia que si la solución en este punto, más
la longitud actual, más la siguiente longitud,
suman menos que el objetivo, se genera un hijo izquierdo
y se continúa. (Recuerde que las longitudes van
en orden no decreciente, y no existen longitudes más
cortas que Lk+1)
La regla
3 tiene dos partes.
Parte a:
la solución en este punto, más las longitudes
restantes, pero sin incluir la longitud actual, ha de
ser mayor que el objetivo. Si
la solución en este punto, más las longitudes
restantes sin la longitud actual, es menor que
el objetivo, no puede existir una solución en
esta ruta.
Parte b:
la solución en este punto, más la siguiente
longitud, pero sin incluir la longitud actual, ha de
ser menor que el objetivo. Si
la solución en este punto, más
la longitud siguiente, sobrepasa el objetivo, no puede
existir una solución en esta ruta.
Las reglas
1 y 2 se excluyen mutuamente. La
regla 3 se aplica a todos los nodos, al margen de las
reglas 1 y 2. Si
ninguna de las reglas se satisfacen en un nodo , quiere
decir que ha alcanzado el final de una ruta. Retroceda
hasta el nodo padre y pruebe con las reglas que aún
no ha utilizado. (Por lo general, la primera vez que
creó el nodo padre ya comprobó las reglas
1 y 2, por lo que, en este punto, sólo necesitará
verificar la regla 3). Una vez que haya retrocedido
hasta la raíz, ya ha terminado.
Estas reglas
de poda dependen de que las longitudes estén
ordenadas en orden no decreciente. (La
semana próxima hablaremos del orden; para esta
tarea asuma que los datos de entrada ya están
ordenados).
Si no utiliza
reglas de poda, incluso un problema pequeño con
n=16 speakers generará
216 (2n) o 65.536 nodos. Una
solución para 30 o 40 artículos sería
informáticamente inviable, incluso en una gran
computadora. Incluso
con las reglas de poda, pueden ocurrir supuestos peores
que requieran cálculos O(2n). Por regla general, al resolver problemas específicos
con datos conocidos por todos, estos supuestos se pueden
evitar.
Trabajo
Escriba
un programa que calcule el subconjunto de un conjunto
de longitudes que, sumadas todas, arrojan un número
determinado. Puede diseñarlo como desee, siempre
que se atenga a los siguientes requisitos: en
primer lugar, que el programa utilice argumentos
de línea de comando para especificar la suma
deseada y la lista de longitudes, en
otra palabras, el primer argumento, argv[0], debería
ser la anchura del palet M, y el argv[i] (donde i>0)
la longitud de los equipos en orden no decreciente (Li); en
segundo lugar, que el programa convierta las longitudes
de entrada en números enteros y los almacene
en un array o Vector de enteros; en tercer lugar,
que el programa muestre todos los subconjuntos de las
longitudes que, sumadas, arrojan el resultado deseado;
puede utilizar System.out.println. Por
último, tras mostrar las soluciones, que el programa
muestre el número de nodos visitados en el árbol,
y el número total de nodos potenciales (2n),
donde n es el número de equipos.
Asuma que
las longitudes de entrada son en orden no decreciente,
y que el objetivo M es factible (L0<=
M y la suma de todas Li >= M.) No
es obligatorio, pero, si lo desea, puede añadir
esta comprobación a los datos de entrada.
Lo que
sigue a continuación es una serie de sugerencias.
Puede crear una clase SubsetTree
con un constructor y un método privado visit().
El método visit()
puede aceptar los argumentos r y s mostrados
en la figura, y un argumento k que muestre el
nivel en el árbol. El
método visit()
pone en práctica las reglas de poda al visitar
cada nodo del árbol; puede escribirse como un
método recursivo que se llama a sí mismo
con valores apropiados de r, s y k
a medida que atraviesa el árbol. La
clase SubsetTree
puede tener un método público sum() que invoque el método visit()
en el nodo raíz.
Puede utilizar
un array o una representación de lista
concatenada para almacenar el árbol. Si es usted
valiente, se puede evitar el almacenaje del árbol:
la combinación del array o el vector X y los argumentos r,
s y k del método visit()
puede localizar donde se encuentra en el árbol
sin almacenar explícitamente ningún modo.
Fase de prueba
Haga pruebas
con las longitudes {1, 2, 3, 4, 6, 10, 12, 15, 20, 30,
40, 45, 50} y varias M.
Debate
En este
ejercicio, hemos elaborado una lista con todas las posibles
soluciones para empaquetar el primer palet. En
una situación real, tal vez convendría
elegir una solución que tuviese el menor número
posible de artículos (o los más largos)
para llenar el primer palet. A
continuación, podría eliminar esos artículos
de la lista y ejecutar de nuevo el mismo algoritmo para
determinar qué artículos poner en el palet,
etc.
Nota extra
Si desea obtener un máximo
de 40 puntos extra, escriba una interfaz gráfica
de usuario (IGU) para el programa principal del boletín
de ejercicios. Tiene la posibilidad de hacerlo en cualquier
boletín de problemas del 6 al 10; pero sólo
en uno de ellos. En general, es libre de diseñar
la IGU que usted quiera; como si se tratase de un ejercicio
en una 'hoja de papel en blanco'. No podrá utilizar
System.out.println en su solución; todas las
entradas y salidas de datos deben hacerse con Swing.
Si opta por crear la interfaz gráfica de usuario
ha de entregar dos soluciones:
- El trabajo general, sin la IGU,
tal y como se describe en el encargo, lo que nos permite
calificar la parte principal de trabajo sin tener que
preocuparnos por los posible errores introducidos por
la IGU.
- La solución completa con
la IGU. En esta segunda entrega sólo se tendrá
en cuenta, a efectos de calificación, la IGU,
hasta un máximo de 40 puntos. Sólo se
calificará la IGU (y sus interfaces inmediatas
con el resto del código). Recibirá entre
0 y 40 puntos; aunque reciba 0 su nota general en el
trabajo no se verá perjudicada o reducida.
Requisitos específicos para
el boletín de problemas 9: su programa debe
mostrar la solución gráficamente, igual
que en la primera figura de esta tarea. Puede, aunque
no es obligatorio, mostrar el árbol de decisión.
Si opta por mostrarlo, sólo es necesario que
lo haga en los árboles pequeños. No puede
utilizar la línea de comando para la entrada
de datos.
Entrega del trabajo
Requisitos
- Problema 1: solamente por correo
electrónico. No es necesario entregarlo en
papel.
- Problemas 2 y 3: copia en papel
y copia electrónica de TODOS los códigos
fuente (todos los archivos con la extensión
.java).
- Incluya en TODOS los archivos
que presente su nombre, el nombre de usuario, la sección,
el nombre del profesor ayudante, el número
de trabajo y una relación de las personas con
las que haya comentado el boletín de problemas.
- NO entregue copias electrónicas
o en papel de código compilado (archivos .class).
Sanciones
- Por pérdida de copia
en papel: -10% de la puntuación del boletín
de problemas.
- Por pérdida de copia
electrónica: -30% de la puntuación del
boletín de problemas.
- Por entrega fuera del plazo:
-20% de la puntuación del boletín de
problemas si es un día de retraso. Por
más de un día de retraso = NO SE PUNTUARÁ
|