Link al enunciado de la práctica en formato pdf

Transcripción

Link al enunciado de la práctica en formato pdf
PRÁCTICA DE ALGORÍTMICA
ENUNCIADO B
El problema.
Supongamos que lo único que se conoce respecto de un algoritmo son sus tiempos de
ejecución en función del número de datos que procesa. Dicha información podría ser algo
parecido a lo siguiente:
n
1
2
3
4
5
6
7
8
9
10
11
12
T(n) 0,9 1,6 2,3 3,0 3,7 4,4 5,1 5,8 6,5 7,2 7,9 8,6
El objeto de esta práctica es realizar un programa capaz de deducir la cota superior de
complejidad de dicho algoritmo únicamente a partir de esta información.
Conviene recordar que la cota de complejidad no representa el tiempo de ejecución sino
la manera en que éste evoluciona y, en este sentido, el principal reto de la práctica
consistirá en investigar qué algoritmo se debe utilizar.
Con el fin de acotar el problema, vamos a clasificar los posibles grados de complejidad
en diez tipos:
Complejidad Código
O(n)
1
O ( n2 )
2
3
O(n )
3
4
O(n )
4
O ( n5 )
5
6
O(n )
6
O(Ln)
7
n
O(2 )
8
O(1)
9
Otras
0
Como se puede ver, trataremos complejidades polinómicas (6 tipos), logarítmicas,
exponenciales y constantes. Además, se ha añadido un último tipo con el que haremos
referencia a cualquier otra posibilidad.
Prácticamente se desconoce toda información relativa a los programas que se deben
analizar. No se sabe su cometido, ni su lenguaje de implementación, ni la máquina en que
han sido ejecutados. Tan solo sabremos sus tiempos de ejecución y la estructura
matemática que los caracteriza. Así, vamos a suponer que los tiempos de ejecución de
los programas se comportan conforme a alguna de las funciones que se describen a
continuación.
Programas con tiempo constante:
T (n) = c
/ c ∈ R+
Programas con tiempo polinómico:
T (n) = ak nk + ak -1 nk -1 + . . . + a2 n2 + a1 n + a0
Programas con tiempo logarítmico:
/ ai ∈ R, k ∈ N, 0 ≤ K ≤ 6
T (n) = L (n + a) + b
/ a, b ∈ R, n + a > 0
Programas con tiempo exponencial:
T (n) = 2n + a + b
/ a, b ∈ R, T (n) < 30
El programa solución.
El procedimiento principal de la solución a la práctica deberá llamarse “pb” y deberá estar
en un fichero de nombre “pb.adb”. Esto es muy importante y si no se cumple es posible
que la práctica no se pueda evaluar.
Datos de entrada.
La entrada al programa será un único fichero ASCII que se llamará “f1.txt” y estará en su
mismo directorio. Consistirá en una secuencia de números reales separados entre si por
uno o varios caracteres en blanco, pudiendo incluir además cambios de línea. Puede
haber una cantidad indeterminada de números, pero siempre será múltiplo de 20, ya que
cada conjunto de 20 números representará los tiempos de ejecución de un determinado
programa para los valores de n = 1, 2, ...,19, 20.
Por ejemplo, un fichero con el contenido que se indica a continuación, representaría los
tiempos de ejecución de dos programas (40 números).
1.000 1.45 1.8 2 2.2 2.45 2.8 3.1 3.56 3.77 3.99 4.33 4.5 5.0
5.6 5.82 6.45 6.8 7.23 8 5.45 5.45 5.45 5.45 5.45 5.45 5.45
5.45 5.45 5.45 5.45 5.45 5.45 5.45 5.45 5.45 5.45 5.45 5.45 5.45
Los 20 primeros números se refieren al primero de los programas e indican los siguientes
tiempos:
T(1) = 1.000
T(2) = 1.45
T(3) = 1.8 .......... T(19) = 7.23
T(20) = 8
Los 20 restantes hacen referencia a un segundo programa cuyo tiempo de ejecución
permanece constante para los 20 valores de n.
Al igual que ocurre en el ejemplo mostrado, cuando el número sea exacto podrá aparecer
con o sin decimales indistintamente.
Datos de salida.
La salida del programa constará de un único fichero ASCII que tendrá por nombre “f2.txt”
y estará en su mismo directorio. Constará exclusivamente de una secuencia de números
enteros (del 0 al 9) separados por uno o más espacios en blanco. Cada uno de los
números hará referencia a uno de los programas del fichero de entrada guardando
estrictamente el orden en que éstos se proporcionaron. Dichos números harán referencia
al orden de complejidad del programa examinado de acuerdo al código que aparece en la
tabla indicada más arriba.
Por ejemplo, un fichero de salida podría tener el siguiente contenido:
3
2
1
7
8
0
0
Ello indicaría que se han examinado 7 programas. Los tres primeros tienen complejidad
polinómica de orden cúbico, cuadrático y lineal respectivamente. La complejidad del
cuarto programa es logarítmica y la del quinto exponencial. Los dos ceros del final indican
que los dos últimos programas no encajan en ninguna de las complejidades que estamos
contemplando.
Si en algún momento no se sabe clasificar uno de los programas de la entrada, se le
asignará el número 0 como salida. Es decir, el 0 se utilizará tanto para indicar “otra
complejidad” como “no lo se”.
Bajo ningún concepto se debe omitir ningún número en la salida o cambiar su orden
respecto a los datos de entrada. El fichero de salida siempre debe tener tantos números
enteros como el resultado de dividir por 20 los números proporcionados como entrada.
Uso de funciones matemáticas.
Dependiendo del enfoque que se de a la solución, puede que en algún caso venga bien
utilizar alguna función matemática. Si esto fuera así, incluiríamos la siguiente declaración
al principio del programa:
with ada.numerics.generic_elementary_functions;
use ada.numerics;
Después, en la zona de declaraciones del procedimiento donde se vayan a usar las
funciones, sería necesario poner lo siguiente:
package funciones is new generic_elementary_functions (float);
use funciones;
A partir de ese momento ya se podrá hacer referencia a las funciones matemáticas del
paquete y cuyo contenido puede consultarse en varios sitios de internet, como por
ejemplo en el siguiente enlace:
http://www.adaic.org/resources/add_content/standards/95lrm/ARM_HTML/RM-A-5-1.html
Entrada y salida con números enteros.
Para poder trabajar con enteros de forma cómoda, es útil escribir en la zona de
declaraciones lo siguiente:
package enteros is new integer_IO (integer);
use enteros;
De esta forma podremos utilizar Put y Get directamente con ellos.
El problema de la precisión.
Al trabajar con números reales podemos encontrar problemas derivados de la precisión al
realizar determinadas operaciones. Por ejemplo, puede ocurrir que dos variables, a y b,
contengan los resultados de diferentes operaciones. Aunque en teoría hayamos llegado a
la conclusión de que, por las operaciones que se han realizado, a y b deberían ser
iguales, podemos llevarnos la sorpresa de que al compararlas éstas resulten diferentes.
Con el fin de solucionar este problema vamos a evitar el uso del tipo float, ya que utiliza
representación en coma flotante, y vamos a apostar por los reales de coma fija. Para ello,
introduciremos lo siguiente en la zona de declaración:
type coma_fija is delta 0.0000001 digits 18;
package coma_fija_IO is new decimal_IO (coma_fija);
Con esto definimos un tipo de números que tendrán un total de 18 dígitos de los cuales 7
se utilizarán para representar la parte decimal. Con esto tendremos un rango de trabajo y
una precisión suficientes.
Pero con esto no acaban nuestros problemas. Lo único que hemos conseguido es que los
márgenes de error en nuestras variables a y b, utilizadas como ejemplo, sean “parecidos”,
pero el error seguirá existiendo.
La solución pasa por contemplar ese margen de error cuando hagamos las
comparaciones. En vez de preguntar si a y b son iguales, preguntaremos si a y b son
“casi” iguales. Por ejemplo podemos hacer lo siguiente:
if abs( a – b ) < abs( a * 0.2 ) then
-- Consideramos que a y b son iguales.
else
-- Los consideramos diferentes.
end if;
En este caso consideramos que ambos valores son iguales si difieren en menos de un
20% del valor de uno de ellos. Obsérvese la necesidad de usar la función que calcula el
valor absoluto cuando no estamos seguros de los valores que puedan tomar nuestras
variables. Naturalmente el margen de error permitido no tiene por qué ser el 20%, sino
que en cada caso habrá que adoptar el más adecuado.
Evaluación de la práctica.
La corrección se hará de forma automática por lo que es muy importante seguir al pie de
la letra todas las indicaciones que se dan.
Las prácticas que no compilen correctamente o aborten durante la ejecución serán
consideradas suspensas. Lo mismo ocurrirá con aquellas que no sigan las
especificaciones indicadas respecto a los nombres de los ficheros de entrada y salida,
formato de éstos y demás normas generales. También es importante cumplir las
instrucciones generales que se han dado en documentación aparte. Digamos que la
primera prueba consiste en ser capaz de cumplir todos estos requisitos sin ninguna
excepción.
La práctica será probada con los datos reales de más 100 de programas. Cada programa
clasificado adecuadamente irá incrementando la nota de la práctica. Cada programa
clasificado de forma errónea descontará una cantidad similar. Los programas cuya
clasificación sea 0 (“otra complejidad / no lo se”) ni suman ni restan nota.
Ficheros de prueba.
Junto con el enunciado de la práctica se proporcionan unos ficheros de pruebas para
poder comprobar el correcto funcionamiento de la solución. Hay que recordar que, aunque
los ficheros proporcionados para las pruebas tienen nombres distintos, el fichero de
entrada a nuestro programa siempre debe tener el mismo nombre (f1.txt).
En la siguiente tabla se muestran los resultados que deberían obtenerse para cada uno
de los ficheros de prueba.
P1
P2
1 1 1 1
P3
2 2 2 2
P4
3 3 3 3
P5
4 4 4 4
P6
5 5 5 5
Ln
6 6 6 6
Exp
7 7 7 7
Cons
8 8 8 8
Otro
9 9 9 9
Varios1
0 0 0 0
9 8 7 0 1 2 3 4 5 6
Varios2
1 1 7 7 5 5 3 3 9 9 2 2 8 8 4 4 0 0 6 6
Varios3
6 0 5 9 1 1 4 7 3 8 3 6 9 5 2 2 4 8 3 1 0 7 6 8 2 0 5 7 4 9

Documentos relacionados