Vous êtes sur la page 1sur 27

nombre

DNI: 00000000A
Tel. 001 002 003
Email: nombre@email.com
Centro Asociado: xxx

PED 1
MENSAJERA
URGENTE
PREDA - CURSO 2013/14
Prctica 1 de la asignatura Programacin y Estructuras de Datos
Avanzadas (cd. 71902019)

NDICE DE CONTENIDO
Contenido
Enunciado de la practica: Mensajera urgente ______________________________________________________ 1
Esquema algortmico utilizado y como se aplica al problema. _____________________________________ 2
Demostracion de optimalidad ________________________________________________________________________ 4
Coste computacional del algoritmo __________________________________________________________________ 5
Alternativas al esquema utilizado ____________________________________________________________________ 6
Divide y Venceras ______________________________________________________________________________________________ 6
Vuelta Atras ____________________________________________________________________________________________________ 6
Ramificacion y poda ___________________________________________________________________________________________ 8

Datos de prueba ______________________________________________________________________________________ 14


Codigo fuente del programa que implementa el algoritmo _______________________________________ 17
Clase Express.java ___________________________________________________________________________________________ 18
Clase Fichero.java ____________________________________________________________________________________________ 21
Clase Entrega.java____________________________________________________________________________________________ 24

PED 1: MENSAJERA URGENTE PREDA 2013


Enunciado de la practica: Mensajera urgente
Una empresa de mensajera urgente realiza buena parte de su transporte por carretera. Dispone de un
servicio expres, en el que se compromete con el cliente a llevar el paquete a su destino sin hacer ninguna
otra entrega en el camino. La empresa conoce el numero de kilometros que puede hacer sin repostar
cada vehculo de su parque movil. Tambien dispone de un mapa de carreteras que le permite calcular las
distancias que hay entre las gasolineras en todas las posibles rutas. As, dado un punto de origen y otro
de destino, el transportista puede parar a repostar el menor numero de veces posible. Se trata de
disenar un algoritmo voraz que determine en que gasolineras debe parar el transportista para lograr el
objetivo de repostar el menor numero de veces posible.
Sea n el numero de kilometros que puede realizar sin repostar el vehculo que se va a utilizar para
realizar el transporte expres. Suponemos que en la ruta entre el punto de recogida del paquete y el
punto de destino existen G gasolineras numeradas del 0, en el punto de recogida, a G-1 en el punto de
destino. Suponemos tambien que se dispone de un vector con las distancias entre la gasolinera i-1 e i.
Notese que el problema tendra solucion si ningun elemento del vector tiene un valor mayor que n, ya
que en caso contrario el vehculo no podra llegar de una gasolinera a otra que distara mas de n
kilometros. Hay que tener en cuenta que hay una unica ruta posible.

Pagina 1

PED 1: MENSAJERA URGENTE PREDA 2013


Esquema algortmico utilizado y como se aplica al problema.
En un algoritmo voraz, para resolver un determinado problema, se va escogiendo la solucion optima en
cada paso individual, sin reconsiderar esta decision en ningun paso individual posterior, con la
esperanza de llegar a una solucion general optima. Normalmente este tipo de algoritmos se aplican en
los problemas de optimizacion.
El esquema general de un algoritmo voraz es el siguiente:
fun Voraz(c: conjuntoCandidatos): conjuntoCandidatos
sol 0
mientras c 0 solucion(sol) hacer
x seleccionar(c)
c c\{x}
si factible(sol {x}) entonces
sol sol {x}
fsi
fmientras
si solucion(sol) entonces devolver sol
sino imprimir(no hay solucin)
fsi
ffun

La estrategia voraz aplicada al problema de la mensajera urgente consiste en no parar mientras se


pueda recorrer la distancia necesaria para llegar a la proxima gasolinera. La descripcion del algoritmo
aplicada a este problema, por pasos, sera la siguiente:
1.
2.

3.

4.
5.

Crear un conjunto de candidatos (gasolineras en las que parar a repostar), vaco.


Mediante la funcion de seleccin elegir el mejor candidato de los disponibles. Es decir escoger la
gasolinera lo mas lejana posible partiendo del punto actual, siempre y cuando la distancia recorrida
para llegar sea inferior a la distancia total que se puede recorrer sin repostar.
Anadir el candidato escogido en el punto 2 al conjunto de candidatos. Este candidato siempre sera
factible puesto que la distancia recorrida para llegar a el siempre sera menor o igual que la distancia
total que se puede recorrer sin repostar, tal y como garantiza la funcion de seleccin.
Comprobar si el conjunto de candidatos es una solucion del problema, es decir, comprobar si hemos
llegado al punto de destino, y si aun quedan mas candidatos.
Si el conjunto de candidatos no es una solucion al problema, volver al punto 2.

Siguiendo estos pasos, se puede ya esbozar el algoritmo voraz que utilizaremos para resolver el
problema, que sera el siguiente:
tipo Vector = matriz[1..G-1] de booleano
fun express(DG: matriz[1..G-1] de natural, n,G: natural): Vector
var
solucion: Vector
fvar
para i 1 hasta G1 hacer
solucion[i] falso
fpara
km 0

Pagina 2

PED 1: MENSAJERA URGENTE PREDA 2013


para i 1 hasta G-1 hacer
si km+DG[i] n entonces
solucion[i] falso
sino
solucion[i] verdadero
km 0
fsi
km km+DG[i]
fpara
dev solucion
ffun

Donde n es la distancia maxima que puede recorrerse sin repostar, G el numero de gasolineras del
trayecto incluyendo el punto de origen y de destino como tales, y DG es un vector cuyo elemento i-simo
contiene la distancia entre la gasolinera i y la i+1. En el vector solucin cada elemento i-simo sera
verdadero o falso en funcion de si debe repostarse o no en la gasolinera i. Este vector no contempla
elementos para las gasolineras de los puntos de origen y destino ya que estan implcitos en la solucion
(se parte con el deposito lleno y siempre se llega (se para) al destino).

Pagina 3

PED 1: MENSAJERA URGENTE PREDA 2013


Demostracion de optimalidad
Demostraremos que este algoritmo, basado en un esquema voraz, conduce siempre a una solucion
optima del problema. Siendo s el numero de gasolineras en las que hay que repostar devuelto por el
algoritmo, y t el numero de gasolineras en las que hay que repostar para cualquier otra solucion, se trata
de demostrar que s k para cualquier caso.
Sea X = {x1, x2, , xs} la solucion obtenida mediante la aplicacion del algoritmo voraz descrito
anteriormente, donde cada elemento del conjunto indica una gasolinera en la que se debe repostar. Sea
tambien Y = {y1, y2, , ys} una solucion cualquiera para el mismo problema.
Consideremos k como el subndice de la primera aparicion de elementos en las soluciones de modo que
xk yk, y supongamos que k = 1. Como partimos siempre con el deposito lleno y el algoritmo voraz hace
que paremos por primera vez en la gasolinera mas alejada a la cual se pueda llegar sin repostar, en este
caso en concreto solo queda la posibilidad de que x1 > y1.
Para probar ahora que x2 y2, supongamos por reduccion al absurdo que y2 > x2. Para que en la solucion
Y se consiga ir desde y1 hasta y2, tiene que darse que la distancia entre ambas gasolineras sea menor o
igual que n (siendo n la distancia maxima que se puede recorrer sin repostar). Por lo tanto, y como x1 >
y1, desde x1 hasta y2 tambien debe haber una distancia menor o igual que n. Si esto ultimo fuera cierto e
y2 > x2, el algoritmo voraz hubiera escogido a la gasolinera y2 como componente de la solucion X en lugar
de la x2.
Repitiendo este proceso, se va obteniendo que xk yk k hasta que se llega al destino, quedando
demostrado que la estrategia voraz consigue una solucion optima, ya que esto implica que |X| |Y|. Es
decir que el numero de elementos de la solucion X (numero de gasolineras en las que repostar), sera
menor o igual que el numero de elementos de cualquier otra solucion.

Pagina 4

PED 1: MENSAJERA URGENTE PREDA 2013


Coste computacional del algoritmo
Es casi trivial observar que el coste de este algoritmo es lineal con respecto al numero de gasolineras.
Tenemos una funcion no recursiva con un solo bucle que se ejecuta G-1 veces, siendo G el numero de
gasolineras. El resto de operaciones en la funcion (las que no estan dentro del bucle), se llevan a cabo
una unica vez y por lo tanto tienen un coste constante que se puede despreciar en el computo general.
Es decir, el coste temporal de este algoritmo es O(G), pero como ademas el bucle siempre se ejecutara
G-1 veces, podemos concluir que su coste esta exactamente en (G).

Pagina 5

PED 1: MENSAJERA URGENTE PREDA 2013


Alternativas al esquema utilizado
Creo que sera imposible encontrar un esquema algortmico que, aplicado a la resolucion de este
problema, resultase en un coste temporal menor que el resultante de aplicar un esquema voraz. De
todos modos, posibles alternativas seran las siguientes.

DIVIDE Y VENCERS
La estrategia de Divide y Vencers se basa en la resolucion recursiva del problema, descomponiendolo en
subproblemas de su mismo tipo, hasta que estos llegan a ser lo suficientemente sencillos como para
resolverse directamente.
Basicamente, los pasos que se han de dar en la aplicacion de este esquema son:
1.
2.
3.

Plantear el problema de forma que pueda ser descompuesto en subproblemas del mismo tipo, pero
de menor tamano.
Resolver independientemente todos los subproblemas, bien directamente si es posible, o bien de
forma recursiva, volviendo a dividirlos en subproblemas mas pequenos.
Combinar las soluciones obtenidas en el paso anterior para obtener la solucion del problema
original.

Dicho esto, habra una forma de aplicar este esquema al problema de mensajera urgente que estamos
tratando.
En un primer paso, siempre que el problema no se pueda resolver de forma directa, tendramos que ir
dividiendo el vector DG, que contiene las distancias entre gasolineras, hasta dar con subvectores que
puedan ser recorridos de principio a fin sin repostar. Ademas, para garantizar una solucion optima
(menor numero posible de gasolineras en las que repostar), no se podra dividir el problema en
subproblemas de forma arbitraria, sino que habra que ir seleccionando esas divisiones de forma
cuidadosa. La unica forma de garantizar que las divisiones sean las adecuadas, es precisamente aplicar
un esquema voraz para seleccionarlas dividiendo el vector en dos subvectores, uno conteniendo un
tramo que pueda recorrerse sin repostar desde el punto de partida, y otro conteniendo el resto. A
continuacion, si ese vector con el resto tampoco puede resolverse de forma directa volver a dividirlo a su
vez de la misma forma, y as continuamente hasta alcanzar el destino. Finalmente, la solucion general
sera la formada por el conjunto de todos los elementos que esten en la ultima posicion de cada uno de
los subvectores que se han ido generando.
En el fondo, aplicando este esquema de esta forma (que es la unica que garantiza una solucion optima)
estamos haciendo lo mismo que si usaramos un algoritmo voraz. Y pese a que ambos tengan el mismo
coste temporal O(n), seguramente una implementacion siguiendo el esquema de Divide y Vencers sera
mas complicada de realizar en la practica.

VUELTA ATRS
En el esquema de vuelta atrs se recurre a una busqueda exhaustiva de las soluciones, generando un
grafo (generalmente representado en forma de arbol) que contenga todas las soluciones posibles, para

Pagina 6

PED 1: MENSAJERA URGENTE PREDA 2013


luego recorrerlo y quedarse una o varias de las soluciones encontradas. Como realizar esta busqueda es
costosa, lo que se hace para tratar de minimizarla, es terminar la exploracion de un camino en el arbol
tan pronto se sepa que dicho camino no puede llevar a una solucion.
Una forma de aplicar este esquema al problema de la mensajera urgente, sera ir generando un arbol
cuyo nodo raz fuese el punto de origen del recorrido a realizar. A este nodo le anadiramos como hijos
de primer nivel todas las gasolineras que se pudieran alcanzar desde el sin tener que repostar, es decir
todas las gasolineras cuya distancia desde el punto de origen fuese menor o igual a n. A continuacion a
estos nodos hijos de nivel 1, les anadiramos a cada uno de ellos a su vez los hijos que fuesen necesarios
siguiendo el mismo criterio, y as continuamente hasta llegar al punto de destino.
Para tratar de explicar mejor como se generara un arbol a partir de este esquema, veamos como se
llegara a la solucion de un problema de ejemplo si lo aplicaramos. Supongamos un total de cinco
gasolineras, que la distancia maxima que puede recorrerse sin repostar es de 400, y que las distancias
entre gasolineras son las siguientes: 300, 100, 50, 100.
Aplicando este esquema, generaramos el siguiente arbol:

En el se ha incluido la distancia que habra que recorrer para alcanzar un nodo desde otro. Tambien a la
izquierda de cada nodo, en color gris, aparece el orden en el que se van expandiendo los diferentes
nodos segun el algoritmo, siendo el orden como si de un recorrido en profundidad se tratase.
En cuanto a los costes, supongamos el caso peor que pueda darse, que sera cuando se pueda llegar a
cualquier gasolinera xi+j del trayecto desde la gasolinera xi. O, dicho de otro modo, cuando la distancia
entre el punto de origen y el de destino puede recorrerse sin tener que parar a repostar. Para
representar el arbol considerando el caso peor, necesitaremos un total de 2G-1 nodos. Por lo tanto, los
costes tanto espacial como temporal si usaramos este esquema para resolver el problema, seran O(2G-1),

Pagina 7

PED 1: MENSAJERA URGENTE PREDA 2013


para G gasolineras. Pero este es el coste asociado a la generacion del arbol. Despues habra que
recorrerlo en busca de la solucion, y recorrer un arbol en profundidad puede tener un coste entre O(n2),
si el arbol esta representado mediante una matriz de adyacencia, y O(n + a), si esta representado
mediante listas de adyacencia (donde a es la suma de las longitudes de las listas de adyacencia).
En cualquier caso, este algoritmo es mucho mas costoso que el planteado usando un esquema voraz,
tanto espacial como temporalmente. Solo sera oportuno su uso en el caso de que necesitaramos
encontrar todas las soluciones optimas posibles, y no solo una de ellas.

RAMIFICACIN Y PODA
Segun el libro de texto de la bibliografa basica de la asignatura (Programacion y Estructuras de Datos
Avanzadas, Lourdes Araujo Serna, p. 185), este esquema ser menos eficiente que el voraz cuando ambos
se pueden utilizar para realizar la optimizacin, por lo que su aplicacin est indicada slo cuando no se
conoce un algoritmo voraz vlido para el problema. Por lo tanto deberamos descartar su uso
inmediatamente.
De todos modos y como ejercicio meramente informativo, vamos a suponer que no hemos sido capaces
de demostrar la optimalidad del algoritmo voraz, y a plantear a grandes rasgos como se podra aplicar
este esquema a la resolucion del problema.
Para poder plantear el algoritmo lo primero que se necesita es definir las cotas tanto inferior o
estimacion optimista, como superior o estimacion pesimista. Sean d la distancia que hay entre la
gasolinera que estemos tratando y el destino final, y n la distancia maxima que puede recorrerse sin
repostar.
Una estimacion optimista sobre el numero de gasolineras en las que parar a repostar, sera (d/n) + p,
donde p sera el numero de paradas que lleva realizadas hasta ese momento. Sera una estimacion
demasiado optimista, inferior al valor alcanzable en la practica, ya que de lo que se trata es de
minimizar el numero de paradas del trayecto. Hay que anadir que utilizando esta funcion se esta
considerando el punto de destino como parada, aunque luego en la solucion final del problema se
descarte.
En cuanto a la estimacion pesimista, usaremos el numero de paradas que resultaran de aplicar el
algoritmo voraz, incluyendo tambien punto de destino como parada. Ya sabemos que en realidad esta es
una solucion optima al problema, pero como digo supondremos que no hemos sido capaces de
demostrar su optimalidad, y por lo tanto no sabemos si puede mejorarse esta cota o no.
Vamos a utilizar tambien un montculo de mnimos ordenado segun el valor de la estimacion optimista
calculada para cada uno de sus nodos. Cada nodo del montculo representara una gasolinera y una
solucion parcial del recorrido, indicando en que gasolineras se ha parado a repostar antes de llegar
hasta la que se este tratando en el nodo actual. Esta solucion parcial del nodo estara representada a su
vez por un vector booleano, indicando con un 1 o un 0 en su posicion i-esima si se debe parar o no en la
gasolinera i. Ademas, tambien incluiremos en el registro de cada nodo su estimacion optimista, el ndice
de la gasolinera que estamos tratando (k), el numero de paradas que se han hecho hasta el momento, la

Pagina 8

PED 1: MENSAJERA URGENTE PREDA 2013


distancia que se puede recorrer sin necesidad de repostar desde el punto en el que estemos, y la
distancia que nos separa de la proxima gasolinera.
Por lo tanto, cada elemento del montculo sera un registro con la siguiente informacion:
k (iteracion en la que ha sido generado)
ruta (vector booleano)
paradas
estOpt (estimacion optimista)
dist (distancia que se puede recorrer sin repostar)
sig (distancia hasta la proxima gasolinera)
La cota inicial de poda a aplicar sera la estimacion pesimista calculada para el primer nodo, es decir
desde la primera gasolinera. A partir de este nodo inicial iremos construyendo el montculo,
anadiendole como hijos los nodos con las soluciones parciales correspondientes a la siguiente
gasolinera de la ruta, uno considerando que se debe parar en ella, y otro considerando que no se debe
parar. Si ninguno de los hijos debe podarse, lo anadimos al montculo y continuamos el proceso hasta
llegar a la solucion. Cabe anadir que aparte de la condicion de poda normal cuando la estimacion
optimista supere a la cota, hay que anadir otra consistente en podar si no se puede llegar a la siguiente
gasolinera.
Para ver el funcionamiento de este algoritmo sin necesidad de escribir el pseudocodigo que lo
implementa, pasemos a tratarlo con el mismo ejemplo que en el apartado anterior. Tenemos cinco
gasolineras y las distancias que separan unas de otras es, respectivamente, de 300, 100, 50 y 100. La
distancia maxima que puede recorrerse sin repostar es de 400.
Lo primero sera calcular la cota superior general, que como ya hemos dicho es el resultado de aplicar el
algoritmo voraz al punto de partida. En este caso en concreto es 2 (se para unicamente en la segunda
gasolinera e incluimos el punto de destino como parada). A continuacion habra que construir el primer
nodo:

A este nodo le anadimos dos hijos, uno considerando que se debe parar en la primera gasolinera, y otro
considerando lo contrario, de la siguiente manera:

Pagina 9

PED 1: MENSAJERA URGENTE PREDA 2013

Tras generar estos dos primeros nodos, vemos que en ninguno su estimacion optimista supera la cota
generada inicialmente, y por lo tanto no hay que podar. Para el nodo 2, debemos calcular de nuevo la
estimacion pesimista puesto que se ha realizado una parada, cuyo resultado es 2. Como no es inferior a
la cota actual, no hay que actualizarla (ademas dejo senalado ya que esto no sucedera en todo el
desarrollo, puesto que sabemos que utilizando un algoritmo voraz llegamos a la solucion optima, as que
no volvere a comentarlo). De estos dos nodos, el de menor estimacion optimista es el N1, por lo que sera
el siguiente en tratarse.

El nodo N3 debe podarse puesto que, a pesar de que su estimacion optimista no supere la cota, resulta
imposible alcanzar desde la gasolinera que representa a la siguiente sin hacer la parada. Por lo tanto nos

Pagina 10

PED 1: MENSAJERA URGENTE PREDA 2013


queda el nodo N4, que representa una parada en la gasolinera 2, y cuya estimacion optima resulta de
1,375, por lo que al insertarlo en el montculo sera el siguiente en ser tratado:

El nodo N6, que representa una ruta en la cual se para en las gasolineras 2 y 3, obtiene una estimacion
optimista de 2,25, un valor superior (peor) a la cota y por lo tanto debe podarse, ya que nunca
alcanzaremos una solucion optima a traves de la solucion parcial que representa. En cambio el nodo N6
s tiene una estimacion optima inferior a la cota, debe introducirse en el montculo y como es el de
menor valor de los todava no tratados, sera el siguiente en procesarse:

Pagina 11

PED 1: MENSAJERA URGENTE PREDA 2013


Expandiendo este nodo llegamos al final del recorrido, ya no se pueden seguir expandiendo nodos. La
ultima gasolinera o destino es de parada obligatoria, por lo que directamente el nodo N6 se descarta
como solucion. El nodo N7 es una solucion con 2 paradas, por lo que la cota de poda queda inalterable
como era de esperar y no permite la poda del nodo N2 que tenamos pendiente tras la primera
expansion. As que pese a haber llegado a una solucion, debemos continuar con la expansion a partir de
dicho nodo por si se alcanzase alguna mejor:

En esta nueva expansion podamos el nodo N9 puesto que su estimacion optimista supera a la cota, y
continuamos expandiendo el nodo N8.

Pagina 12

PED 1: MENSAJERA URGENTE PREDA 2013


Nos encontramos en el mismo caso que al expandir el nodo N2. Debemos podar el nodo N11 y seguir
expandiendo el N10.

En este punto volvemos a llegar al final del recorrido, y al igual que en la vez anterior descartamos
directamente el nodo que no contempla el punto de destino como parada. El nodo restante N13
representa otra posible solucion que ademas tiene el mismo numero de paradas, 2.
Con este esquema hemos llegado por un lado a la misma solucion optima a la que se llegara aplicando
un esquema voraz, representada por el nodo N7, y a otra solucion diferente pero igual de optima,
representada por el nodo N13. En una pararemos en la segunda gasolinera de la ruta, y en la otra en la
primera, y ambas llegaran al destino realizando una unica parada (sin contar la del destino, tal y como
se pide en el enunciado del problema).
Este montculo que vamos construyendo tiene tantos niveles como gasolineras del problema, G, y en el
caso peor, cada nodo se ramificara en otros dos nodos por lo que se construiran un total de 2 G 1
nodos, y esto implica que el coste temporal de este algoritmo sera O(2G).
Es un coste algo peor que el obtenido usando un algoritmo de vuelta atras, y mucho peor que el del
algoritmo voraz, tal y como era de esperar, por lo que no sera recomendable usarlo bajo ninguna
circunstancia.

Pagina 13

PED 1: MENSAJERA URGENTE PREDA 2013


Datos de prueba
Basicamente la forma de comprobar el funcionamiento del programa ha consistido en dos tipos de
pruebas. Por un lado, se ha comprobado que el programa interpreta bien los datos contenidos en el
fichero de entrada y calcula bien la solucion, y ademas se ha verificado que maneja bien los ficheros de
entrada que esten mal formados o con datos erroneos, es decir que los valida antes de utilizar sus datos.
Por otro lado, se han ejecutado pruebas en las que se iba incrementando el numero de gasolineras a
tratar por el programa, con el fin de corroborar en la practica que el coste temporal del algoritmo
concuerda con el esperado teoricamente, que recordemos es O(G), lineal con respecto al numero de
gasolineras a tratar.
Probablemente lo unico relevante para este informe sea el segundo tipo de pruebas, con las que se
intenta respaldar empricamente el coste temporal teorico que tiene el algoritmo empleado. La forma de
proceder con estas pruebas ha sido la siguiente:
1.
2.
3.

Generar un archivo con un numero dado de gasolineras, y con distancias aleatorias entre ellas.
Ejecutar el programa usando este archivo como entrada y recuperar el tiempo que tarda el
algoritmo en resolver el problema.
Repetir los pasos 1 y 2 diez veces, descartar los tiempos maximo y mnimo obtenidos, y hacer una
media con el tiempo de los ocho restantes.

Este proceso se ha seguido para tratar problemas con un numero ascendente de gasolineras. Por
ejemplo, la salida de una de estas pruebas para ficheros con 10 gasolineras es la siguiente:
0.019282 ms
2 4 6 7 8
0.005598 ms
1 2 3 4 6 7
0.005287 ms
1 2 3 4 5 6
0.004976 ms
1 2 3 4 5 6
0.005287 ms
1 2 3 5 8
0.005287 ms
1 3 5 7 8
0.005287 ms
2 5 6 7 8
0.005287 ms
1 3 5 6 7
0.006842 ms
1 2 4 6 7 8
0.004976 ms
1 2 3 4 6

con 10 gasolineras.
con
8
con
8
con
8
con

10 gasolineras.
10 gasolineras.
10 gasolineras.
10 gasolineras.

con 10 gasolineras.
con 10 gasolineras.
con 10 gasolineras.
con 10 gasolineras.
con 10 gasolineras.

De los 10 tiempos obtenidos descartaramos el mayor y el menor, y con el resto hacemos una media, con
lo que nos queda que para 10 gasolineras, el tiempo de ejecucion es de 0,005481 ms.
Los datos obtenidos mediante esta batera de pruebas son los que se muestran en las tablas siguientes:

Pagina 14

PED 1: MENSAJERA URGENTE PREDA 2013


Gasolineras
5
10
20
50
100
250
500
1.000
2.000

Tiempo
0.003732 ms
0.005287 ms
0.010885 ms
0.02488 ms
0.043541 ms
0.091747 ms
0.187848 ms
0.351126 ms
0.629788 ms

Gasolineras
1.000.000
1.500.000
2.000.000
2.500.000
3.000.000
4.000.000
5.000.000
6.000.000
7.500.000

Tiempo
14.033853 ms
21.37173 ms
28.35600 ms
34.723864 ms
40.566118 ms
57.804581 ms
64.351887 ms
76.599008 ms
95.942984 ms

Al ir recopilando los tiempos me he encontrado con un problema. A partir de considerar cierto numero
de gasolineras, el tiempo misteriosamente deja de incrementarse de forma lineal. Y no es que empeore
precisamente, sino al contrario. Si tomamos por ejemplo el tiempo que tarda la ejecucion con 100
gasolineras, 0,043 ms, si se siguiese una progresion lineal directa respecto al numero de gasolineras, al
considerar 1.000.000 de gasolineras este tiempo debera ser del orden de 0,043x10.000 = 430 ms. Pero
como se ve en la segunda tabla, el tiempo obtenido en la practica ha sido de 14,03 ms.
En realidad no se a que es debido. Como para valores grandes de G (el numero de gasolineras), se debe
acceder a vectores con muchos elementos, pense que podra ser debido al tiempo que lleva acceder a
estos elementos, as que he probado a utilizar diferentes estructuras de datos para almacenar los
vectores tanto de las gasolineras como de las soluciones, usando arrays, listas, colas/pilas, e incluso
hashmaps. Se supone que en una cola o pila los tiempos para insertar un elemento o acceder a su
cabeza/cola son constantes, O(1), pero aun as el problema sigue presente.
La cada brusca del tiempo de ejecucion ocurre, en mi equipo de pruebas, cuando consideramos un valor
de G a partir de las 500.000 gasolineras aproximadamente. He buscado informacion sobre el tema pero
no he encontrado nada, aunque estoy casi convencido de que tiene que ver de algun modo con la forma
en la que java trata las estructuras de datos utilizadas.
Teniendo esto presente, se puede observar que para valores de G que esten dentro de un rango de
similar orden de magnitud, el tiempo de ejecucion es efectivamente lineal con respecto a G, O(G) tal y
como se haba previsto teoricamente. En las graficas de la pagina siguiente se aprecia perfectamente.

Pagina 15

PED 1: MENSAJERA URGENTE PREDA 2013

Tabla 1
0,7
0,6
0,5
0,4
0,3
0,2
0,1
0
0

500

1000

1500

2000

2500

Tabla 2
120

100

80

60

40

20

0
0

Pagina 16

1.000.000

2.000.000

3.000.000

4.000.000

5.000.000

6.000.000

7.000.000

8.000.000

PED 1: MENSAJERA URGENTE PREDA 2013


Codigo fuente del programa que implementa el algoritmo
Para implementar el programa que resuelva el problema segun las especificaciones del enunciado, he
utilizado tres clases diferentes para separar las diferentes tareas que hay que realizar. Los nombres de
estas clases son Express, Fichero y Entrega. La clase Express es la principal y la que ejerce de controlador
del programa, invocando a las otras dos segun sea necesario. La clase Fichero se usa para todo lo
relacionado con la lectura y validacion del fichero de entrada, y para guardar la salida del algoritmo en el
fichero de salida. Por ultimo la clase Entrega es la que contiene el algoritmo voraz en s mismo.
El diagrama de clases del programa con los diferentes metodos de cada clase, tanto publicos como
privados, se muestra a continuacion.

Pagina 17

PED 1: MENSAJERA URGENTE PREDA 2013


CLASE EXPRESS.JAVA
package preda.ped1;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.List;
/**
* Clase principal del programa.
* Valida los argumentos de la lnea de comandos que se usan al ejecutar el programa.
* Utiliza los objetos de las clases Fichero y Entrega para procesar los ficheros y
* ejecutar el algoritmo correspondiente, respectivamente.
*
* Autor:
* Asignatura: Programacin y Estructuras de Datos Avanzadas (PED 1)
*/
public class Express {
/** Ruta completa del fichero de entrada */
private String inputFile;
/** Ruta completa del fichero de salida */
private String outputFile;
/** Distancia en Km que se puede recorrer sin repostar */
private int N;
/** Nmero total de gasolineras del problema a resolver */
private int G;
/** Cola (FIFO) que contendr las distancias en Km entre dos gasolineras adyacentes */
private ArrayDeque<Integer> DG;
/** Objeto que permitir trabajar con los ficheros */
private Fichero fichero;
/** Objeto que contiene la lgica del algoritmo utilizado para resolver el problema */
private Entrega entrega;
/** Indica si debemos mostrar la traza de la ejecucin del algoritmo */
public static boolean DEBUG = false;
/**
* Constructor de la clase. Utiliza el objeto <fichero> para recuperar,
* desde el archivo de entrada indicado, los diferentes valores de las variables
* necesarias para resolver el problema (N, G y las distancias). A continuacin se
* pasan estos valores al objeto <entrega> donde sern tratados segn el algoritmo
* seleccionado para resolver le problema, y se recibir la solucin
* correspondiente.
* @param ficheros Lista que contiene el nombre de los archivos de entrada y de
*
salida, en este orden.
* @param trazar Booleano que indica si se debe trazar la ejecucin del algoritmo.
*/
public Express(List<String> ficheros, boolean trazar) {
this.DEBUG = trazar;
this.inputFile = ficheros.get(0);
this.outputFile = (ficheros.size() == 2) ? ficheros.get(1) : null;
fichero = new Fichero(inputFile);
this.N = fichero.getMaxKm();
this.G = fichero.getNumGasolineras();
this.DG = fichero.getDistancias();
entrega = new Entrega(DG, N, G);
String informe = entrega.getOutput();
System.out.println(informe);
if (outputFile != null) {
fichero.saveOutput(outputFile, informe);
}
}

Pagina 18

PED 1: MENSAJERA URGENTE PREDA 2013


/**
* Finaliza la ejecucin del programa con el mensaje indicado.
* @param mensaje El mensaje a mostrar.
* @param status 0: sin error. 1: con error.
* @param help Boolean. Debe mostrarse la ayuda?
*/
private static void die(String mensaje, int status, boolean help) {
System.out.println(mensaje);
if (help) mostrarAyuda();
System.exit(status);
}
/**
* Mtodo que imprime en pantalla informacin sobre cmo ejecutar el programa
* mediante y los parmetros que ste acepta.
*/
private static void mostrarAyuda() {
System.out.println();
System.out.println("java Express [-t] [-h] [entrada] [salida]");
System.out.println("java -jar Express.jar [-t] [-h] [entrada] [salida]");
System.out.println();
System.out.println(" -h
Muestra esta ayuda.");
System.out.println(" -t
Nuestra la traza del algoritmo. Si se " +
"especifica un fichero de");
System.out.println("
salida tambin se guardar en dicho fichero.");
System.out.println(" entrada
Ruta hasta el archivo que contiene los datos " +
"del problema. Puede");
System.out.println("
ser una ruta absoluta, " +
"o indicar nicamente el nombre del archivo,");
System.out.println("
en cuyo caso ste se buscar en el directorio " +
"de trabajo actual.");
System.out.println(" salida
Opcional. Ruta hasta el archivo en el que se " +
"guardarn los");
System.out.println("
resultados obtenidos. Se puede especificar una" +
"ruta absoluta, o");
System.out.println("
slo el nombre del archivo, " +
"en cuyo caso ste se crear en el");
System.out.println("directorio de trabajo actual.");
}
/**
* Lanzador del programa.
* Valida los argumentos que se pasan mediante la lnea de comandos, e invoca al
* objeto principal de la aplicacin (<Express>) de la forma adecuada en funcin de
* esos parmetros.
* @param args Los argumentos de la lnea de comandos.
*/
public static void main (String[] args) {
boolean trazar = false;
List<String> ficheros = new ArrayList<String>();
if (args.length > 4 && args.length < 1) {
die("Error. No se han introducido argumentos vlidos.", 1, true);
}
for (String arg : args) {
if (arg.startsWith("-") && !(arg.equals("-t") || arg.equals("-h"))) {
die("Argumento no vlido: "+arg, 1, true);
}
else if (arg.equals("-t")) {
trazar = true;
}
else if (arg.equals("-h")) {
die("Muestra en pantalla una solucin al problema de Mensajera Urgente.",
0, true);
}
else {
ficheros.add(arg);
}
}

Pagina 19

PED 1: MENSAJERA URGENTE PREDA 2013


if (ficheros.isEmpty())
die("Error. No se ha especificado el fichero de entrada.", 1, true);
else {
for(int i=0; i<10; i++)
new Express(ficheros, trazar);
}
}
}

Pagina 20

PED 1: MENSAJERA URGENTE PREDA 2013


CLASE FICHERO.JAVA
package preda.ped1;
import java.io.*;
import java.util.ArrayDeque;
/**
* Esta clase se utiliza para el tratamiento de los ficheros tanto de entrada como de
* salida. En cuanto al de entrada comprueba que existe en la localizacin indicada y
* si es as, valida su contenido de acuerdo a los criterios del problema.
* Autor:
* Asignatura: Programacin y Estructuras de Datos Avanzadas (PED 1)
*/
public class Fichero {
/** Distancia mxima en Km que se puede recorrer */
private int N;
/** Nmero total de gasolineras en el recorrido */
private int G;
/** Cola con la distancia en Km entre dos gasolineras adyacentes */
private ArrayDeque<Integer> DG;
/** Directorio desde donde se est ejecutando el programa */
private final String currentDir = System.getProperty("user.dir");
/**
* Constructor de la clase.
* Comprueba si se le ha pasado una ruta absoluta hacia el fichero de entrada.
* Valida los datos contenidos en el fichero y de no superar la validacin termina
* la ejecucin del programa.
* Se incluye este proceso directamente en el constructor y no en un mtodo
* separado porque siempre que creemos este objeto ser con este fn.
* @param inputFile El nombre del archivo de entrada
*/
public Fichero(String inputFile) {
File f = new File(inputFile);
if(f.isAbsolute()) {
// Se ha dado una ruta completa hasta el archivo.
if(!f.exists())
System.err.println("No se encuentra el archivo de entrada indicado: " +
""+inputFile);
System.exit(1);
} else {
// No se ha dado una ruta completa, comprobar si el archivo se encuentra en
// el mismo directorio que el programa.
if(f.exists())
inputFile = currentDir + File.separator + inputFile;
else {
System.err.println("No se encuentra el archivo de entrada indicado: " +
""+inputFile);
System.exit(1);
}
}
// Una vez se ha comprobado la existencia del archivo,
// lo leemos y comprobamos que est bien formado.
File input;
FileReader fr = null;
BufferedReader br = null;
try {
input = new File(inputFile);
fr = new FileReader(input);
br = new BufferedReader(fr);

Pagina 21

PED 1: MENSAJERA URGENTE PREDA 2013


try {
input = new File(inputFile);
fr = new FileReader(input);
br = new BufferedReader(fr);
// Primera lnea contiene la distancia mxima que se puede recorrer
String linea = br.readLine();
this.N = Integer.parseInt(linea.trim());
if(N < 1)
throw new IllegalArgumentException("La distancia mnima que hay que " +
"recorrer debe ser superior a cero. En el archivo se indica " +
"que es de "+N+" km.");
// Segunda lnea contiene el nmero total de gasolineras
linea = br.readLine();
this.G = Integer.parseInt(linea.trim());
if(G < 2)
throw new IllegalArgumentException("Debe haber un mnimo de dos " +
"gasolineras. Se ha/n encontrado "+G);
// Las siguientes G-1 lneas deben contener las distancias entre
// gasolineras adyacentes, siendo estas entre 1 y N Km
this.DG = new ArrayDeque<Integer>(G-1);
int distancia;
while((linea = br.readLine()) != null) {
distancia = Integer.parseInt(linea.trim());
if(distancia > N || distancia < 1)
throw new IllegalArgumentException("Se ha encontrado una distancia " +
"con valor de "+distancia+". El mximo posible es de "+N+" " +
"y el mnimo de 1.");
DG.add(distancia);
}
if (DG.size() != (G-1))
throw new IndexOutOfBoundsException("Se han encontrado "+DG.size()+" " +
"lneas indicando distancias entre gasolineras. Tiene que " +
"haber "+(G-1)+" distancias.");
} catch (IllegalArgumentException e) {
System.err.println("El fichero est mal formado.");
System.err.println(e);
System.exit(1);
} catch (IndexOutOfBoundsException e) {
System.err.println("El fichero est mal formado. Posiblemente tenga lneas " +
"de ms referentes a las distancias entre gasolineras.\r\nSe " +
"esperaban "+(G-1)+" lneas. Fichero: " + inputFile);
System.err.println(e);
System.exit(1);
} catch(Exception e) {
System.err.println("Error. No se ha podido leer el fichero: " + inputFile);
System.err.println(e);
} finally {
try {
if(br != null) br.close();
if(fr != null) br.close();
} catch(Exception e2) {
System.err.println("Error. Ocurri un error cerrando el fichero: " +
inputFile);
System.err.println(e2);
}
}
}

Pagina 22

PED 1: MENSAJERA URGENTE PREDA 2013


/**
* Mtodo que sirve para guardar una cadena de texto en el fichero indicado.
* @param outputFile Nombre del fichero en el que guardar la cadena de texto.
* @param informe La cadena de texto que se desea guardar.
*/
public void saveOutput(String outputFile, String informe) {
File f = new File(outputFile);
if(!f.isAbsolute()) {
// No se ha dado la ruta completa, asumimos que el informe debe guardarse
// en el mismo directorio que la aplicacin.
outputFile = currentDir + File.separator + outputFile;
}
BufferedWriter writer = null;
try {
writer = new BufferedWriter(new FileWriter(outputFile));
writer.write(informe);
System.out.println("Fichero de salida guardado en: "+outputFile);
} catch (IOException e) {
System.err.println("Ocurri un error guardando el fichero: "+outputFile);
System.err.println(e);
} finally {
try {
if(writer != null)
writer.close();
} catch (IOException e2) {
e2.printStackTrace();
}
}
}
/**
* Devuelve la distancia mxima que puede recorrerse sin repostar,
* segn lo indicado en el fichero.
* @return Distancia mxima que puede recorrerse sin repostar.
*/
public int getMaxKm() {
return this.N;
}
/**
* Devuelve el nmero total de gasolineras que hay que considerar,
* segn lo indicado en el fichero.
* @return Nmero mximo de gasolineras.
*/
public int getNumGasolineras() {
return this.G;
}
/**
* Devuelve una cola con las distancias entre dos gasolineras adyacentes,
* segn lo indicado en el fichero. La cabeza de la cola ser la distancia entre la
* primera gasolinera y la segunda, el segundo elemento de la cola la distancia
* entre la segunda gasolinera y la tercera, y as sucesivamente.
* @return Cola con las distancias entre dos gasolineras adyacentes.
*/
public ArrayDeque<Integer> getDistancias() {
return this.DG;
}
}

Pagina 23

PED 1: MENSAJERA URGENTE PREDA 2013


CLASE ENTREGA.JAVA
package preda.ped1;
import java.util.ArrayDeque;
/**
* Esta clase contiene el algoritmo que se ha de seguir para resolver el problema.
* Se utiliza una Cola como estructura de datos para almacenar las distancias entre
* gasolineras (ArrayDeque<>), porque imagino que recuperar un elemento de un array (la
* alternativa) no tiene un coste constante, si no que depender del nmero de
* elementos en la matriz, y podra incidir negativamente en el tiempo de ejecucin del
* algoritmo.
* En cambio, recuperar la cabeza de una cola, que es lo que haremos en este caso,
* tiene un coste temporal constante O(1) sea cual sea el tamao de esa cola.
*
* Autor:
* Asignatura: Programacin y Estructuras de Datos Avanzadas (PED 1)
*/
public class Entrega {
/** Cola en la que se irn insertando los ndices de las gasolineras visitadas */
private ArrayDeque<Integer> solucion = new ArrayDeque<Integer>();
/** Texto que se ir rellenando, si es necesario, con las diferentes decisiones que
* ha tomado el algoritmo durante la ejecucin */
private StringBuilder traza = new StringBuilder();
/** Salto de lnea independiente del SO en el que se ejecute el programa */
private final String eol = System.getProperty("line.separator");
/** Se usa para saber el tiempo que tarda la ejecucin del algoritmo,
* y es includo en la traza */
private double ms;
/**
* Constructor de la clase.
* Directamente, contiene el algoritmo en s mismo, no es necesario utilizar otro
* mtodo ya que siempre que creemos un objeto <Entrega> ser con este fn.
* @param DG Cola con las distancias entre gasolineras adyacentes (de G(N) a G(N+1))
* @param N Distancia mxima en Km que se puede recorrer sin repostar
* @param G Nmero total de gasolineras en el recorrido
*/
public Entrega(ArrayDeque<Integer> DG, int N, int G) {
this.ms = System.nanoTime();
int km = 0;
// La gasolinera 0 (el punto de partida), se mostrar en la traza de una forma
// diferente al resto. La aadimos aqu para no "ensuciar" demasiado el bucle
// principal del algoritmo.
if(Express.DEBUG)
makeTraza(0, km, N, DG.peek(), false, "Salida desde gasolinera G0");
// Contendr dinmicamente la distancia entre la gasolinera en la que estamos y
// la siguiente.
int dist;
for (int i = 0; i < G-1; i++) {
dist = DG.poll();
if ((km + dist) > N) {
if(Express.DEBUG && i != 0)
makeTraza(i, km, N, dist, true, "");
solucion.addLast(i);
km = 0;
} else if (Express.DEBUG && i != 0) {
makeTraza(i, km, N, dist, false, "");
}
km += dist;
}

Pagina 24

PED 1: MENSAJERA URGENTE PREDA 2013


//
//
//
if

La ltima gasolinera no se contempla en el algoritmo (no tiene sentido


puesto que no hay ms distancia que recorrer). Si tenemos que mostrar la
traza hay que aadir este paso aparte.
(Express.DEBUG)
makeTraza(G-1, km, N, 0, false, "Llegada a destino, " +
"gasolinera G"+(G-1));

// Calculamos el tiempo total que ha tardado en ejecutarse el algoritmo


this.ms = (System.nanoTime() - this.ms)/1000000;
System.out.println(ms + " ms con "+G+" gasolineras.");
}
/**
* Devuelve la solucin calculada por el algoritmo en forma de una cadena de texto
* que contiene los ndices de las gasolineras en las que se ha parado.
* Si se debe mostrar la traza, tambin la devuelve en la misma cadena de texto.
* @return La solucin del problema, incluyendo la traza si es necesario
*/
public String getOutput() {
String out = "";
if(solucion.isEmpty()) {
out = "Sin paradas, viaje directo :)";
}
else {
while(!solucion.isEmpty()) {
out += solucion.removeFirst()+" ";
}
}
if(Express.DEBUG) {
out += eol+eol+"***** TRAZA DE EJECUCIN: *****"+eol+traza;
out += "***** TIEMPO: ("+ms+" ms) *****";
}
return out;
}
/**
* Mtodo auxiliar que sirve para no "ensuciar" mucho el cdigo del algoritmo si
* necesitamos generar la traza de su ejecucin. Genera un texto con la parte de la
* traza correspondiente a la gasolinera que se est tratando cuando es invocado.
* @param i ndice de la gasolinera que se est tratando
* @param km Km recorridos hasta el momento
* @param N Km mximos que se pueden recorrer sin repostar
* @param distancia Distancia hasta la prxima gasolinera
* @param repostar Booleano que indica si se debe repostar en la gasolinera que se
*
est tratando o no.
* @param optText Texto alternativo a mostrar en el encabezado de esta parte de la
*
traza.
*/
private void makeTraza(int i, int km, int N, int distancia,
boolean repostar, String optText) {
if(optText != "")
traza.append(optText+eol);
else
traza.append("Paso por gasolinera G"+i+eol);
traza.append("
Km recorridos: "+km+" de "+N+eol);
traza.append("
Siguiente gasolinera en "+distancia+" km"+eol);
traza.append("
Repostar?: "+(repostar?"S":"No")+eol);
}
}

Pagina 25

Vous aimerez peut-être aussi