Estructura de Datos: Tarea 1 Complejidad de Algoritmos Profesor: Mauricio Solar. Ayudante de Tareas: José Luis Canepa. Última compilación: 25 de agosto de 2009 Índice 1. Introducción 1.1. Explicación . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.2. Objetivos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2 2 2 2. Descripción de la Tarea 2.1. Algoritmos a analizar . . . . . . . . . . . . . 2.1.1. Búsqueda Secuencial . . . . . . . . . . 2.1.2. Búsqueda Binaria . . . . . . . . . . . 2.1.3. Búsqueda Secuencial Paralela . . . . . 2.2. Implementación de cada algoritmo y pruebas 2.3. Graficación . . . . . . . . . . . . . . . . . . . 2.4. Preguntas al respecto . . . . . . . . . . . . . . 2 2 2 3 3 3 4 4 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3. Evaluación 4 4. Entrega 4.1. Entrega del código . . . . . . . . . . . . . . . . . . . . . . . . . . 4.2. Entrega del informe . . . . . . . . . . . . . . . . . . . . . . . . . 4.3. Contacto . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 5 5 5 5. Apéndice 5.1. Compilar en Linux con gcc . . . . . . . . . 5.2. Usar la librerı́a entregada . . . . . . . . . . 5.3. ¿Por qué un archivo binario en potencias de 5.4. En caso de problemas con el archivo binario 5 5 6 7 8 1 . . . . . . diez? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1. 1.1. Introducción Explicación Como se ha visto en clases, distintos algoritmos proveen diferentes grados de uso de memoria (complejidad Espacial) y el tiempo de ejecución (expresado en la cantidad de veces que se deben recuperar/leer los datos) en base a la entrada (de tamaño “n”). La tarea consiste en 3 fases, una en la que se probarán tres algoritmos conocidos, luego una etapa de graficación, y finalmente responder un cuestionario al respecto de la experiencia. Para realizar la tarea utilizará el lenguaje C en Linux (disponible en los laboratorios de informática o en bajar una copia de Ubuntu) y compilado con “gcc” (ver sección 5.1). Conste que si intenta desarrollar la tarea en windows, es probable que al momento de probar la tarea en linux se produzcan errores dado que gcc de linux es mas estricto. 1.2. Objetivos 1. Entender el cálculo de complejidad de algoritmos. 2. Conocer tres formas de realizar búsquedas, que serán útiles para ARI. 3. Tener una referencia de como cargar archivos binarios y uso de malloc. 4. Aprender a compilar en Linux con gcc y trabajar con librerı́as. 2. 2.1. Descripción de la Tarea Algoritmos a analizar Los algoritmos a continuación representan métodos de búsqueda entre dos arreglos. Se lee un arreglo (ordenado o desordenado) y cada entrada es buscada en el segundo arreglo (ordenado o desordenado). El arreglo del que se leerán los números a buscar se llama arreglo M (tiene m enteros en su interior), y en el que se buscarán, se llama N (y tiene n en su interior). Conste que se considera “encontrado” en el instante en que encuentran el dato, no es necesario que encuentren las repeticiones. 2.1.1. Búsqueda Secuencial Arreglo M ordenado, N desordenado. Consiste en recorrer, por cada elemento del arreglo M, el contenido del arreglo N hasta encontrar el término o llegar hasta el final. 2 2.1.2. Búsqueda Binaria Arrelgo M desordenado, N ordenado. Se recorre el arreglo M, y por cada elemento de este se hace una búsqueda binaria en el arreglo N. La búsqueda binaria solo se puede realizar en arreglos ordenados, y se parte por la mitad del arreglo, luego se determina si el valor buscado está en la sección previa o posterior a la división, y se repite. Investiguen más sobre este algoritmo, es bastante conocido. 2.1.3. Búsqueda Secuencial Paralela Arreglo M ordenado, N ordenado. Este algoritmo no tiene un nombre “oficial” dado que es bastante básico. Consiste en recorrer el arreglo M y el N al mismo tiempo, por cada elemento en M se busca hasta encontrar el término en N, pero si se encuentra uno mayor, significa que no está, y debe procederse al siguiente de M y repetir esta idea. 2.2. Implementación de cada algoritmo y pruebas Cada algoritmo mencionado anteriormente debe estar programado en C, con un nombre fácil de identificar dentro del código. Además, deben tener las cuatro entradas especificadas a continuación: void search_sequential(int *arrm, int *arrn, int length, bool verbose); void search_binary(int *arrm, int *arrn, int length, bool verbose); void search_parallel(int *arrm, int *arrn, int length, bool verbose); (Conste que pueden ser modificadas y agregadas mas argumentos y retornos, pero debe tener esos 4 argumentos siempre con sus mismos nombres). Se deberá ejecutar cada algoritmo 3 veces con distintos tamaños de entrada(101 , 102 , 103 ), ambas del mismo largo length. Para obtener el tiempo de ejecución usarán la librerı́a estándar time.h. El argumento verbose representa un booleano (true o false) descrito en la librerı́a stdbool.h y significa que el programa debe imprimir a pantalla el resultado de la búsqueda (el ı́ndice de donde está ubicado el entero) cuando sea true, y ocultarlo en false. Esto se debe a que el imprimir a stdout consume recursos (afectando el tiempo) y para facilitar el proceso de corrección. Para facilitarles la tarea de cargar este archivo (utilizando almacenamiento dinámico), serán provistos de dos archivos (.h, .c ) donde vendrá un código pre-hecho que cargará el archivo a memoria y retornará un puntero. No están obligados a usarlo. Mas información en la sección 5.2 (página 5.2) (Pueden usar este código como referencia para entender como funciona la función malloc() de C para futuros trabajos) Finalmente, el output del programa será un archivo donde el tiempo promedio de cada ejecución vendrá separado por una coma. 3 2.3. Graficación Usando el archivo separado por comas, debe ser llevado a Excel para generar un gráfico donde se comparen los tres algoritmos. El eje horizontal contendrá el tamaño de la entrada (“n”) y el vertical el tiempo que demoró. Nótese que este gráfico deberá venir incluido en un informe que será evaluado bajo el modelo de informes escritos.1 2.4. Preguntas al respecto Responda las siguientes preguntas: 1. ¿Cuáles son las complejidades de cada uno de los algoritmos para las entradas de tamaño m y n? 2. Suponga que tiene dos arreglos de largo m y n desordenados. El arreglo M tiene 10 elementos y el N tiene 1000. Utilizando un algoritmo como Quicksort, convendrı́a ordenar uno o los dos archivos antes de realizar la búsqueda? ¿Qué ocurre en el caso opuesto (m=1000, n=10)? Suponga que Quicksort es siempre O(xlogx) (donde X es el arreglo a ordenar de largo x). 3. Evaluación Los puntos por cada categorı́a son: 1. Implementación (55 puntos) a) b) c) d) Presentación (5 puntos) Búsqueda Secuencial (10 puntos) Búsqueda Binaria (20 puntos) Búsqueda Sec. Paralela (20 puntos) 2. Graficación (15 puntos) 3. Preguntas (30 puntos) a) Complejidades (15 puntos) b) Caso ejemplo (15 puntos) Errores en la compilación (-Wall) descuentan 5 puntos cada uno hasta un total de 15 puntos. Errores en tiempo de ejecución (e.g. Segmentation Fault) serán causante de obtener un 0 en la primera parte. La presentación del código debe ser clara (buenos nombres de variable, código comentado, bien indentado). En cuanto a la implementación del algoritmo, será evaluado con buena nota si efectivamente encuentra y es el algoritmo descrito. En caso de errores al buscar se descontarán puntos. El gráfico deberá estar bien formateado (rótulo de tı́tulo, ejes) y ser representativo, conforme a las reglas del formato de informe de investigación1 . 4 4. 4.1. Entrega Entrega del código Deben mandar el código comprimido en un archivo “.tar.gz” con el nombre de su grupo (Ejemplo: “grupo06.tar.gz”) (Para comprimir, botón derecho en ubuntu y tienen .tar.gz ahı́). Un archivo explicando como se usa su tarea y supuestos utilizados: “readme.txt”. Nombres y roles de los integrantes deberán venir en dicho “readme.txt”. Código fuente pedido de la sección 2.2. 4.2. Entrega del informe Gráficos, respuesta a las preguntas deben ser entregados en formato de informe de investigación1 , haciendo uso de gráficos (sección 2.3), haciendo referencia al código C que han escrito (2.2), y demostrando empı́ricamente (con ecuaciones) los resultados obtenidos para las preguntas de complejidad de algoritmos. El formato del archivo puede ser .odt (openoffice) o .doc (word 2003). Los nombres y roles de los integrantes del grupo (y número del grupo) en el mismo documento de word/openoffice, además indicando qué fue lo que hizo cada uno de los miembros durante la tarea, tal como se les pide en el formato 1 . 4.3. Contacto La tarea debe ser mandada a “[email protected]” bajo el tı́tulo de “Inf. Exp. Lab. X Grupo Y”, con el comprimido y el informe en dos adjuntos separados. La fecha de entrega es de dos semanas tras la publicación oficial de la tarea (Miércoles 8 de Septiembre), hasta las 23:59. Cualquier duda o error que encuentren sobre las reglas, escriban al mismo mail, o preguntar en persona. Finalmente, el código extra, el archivo de prueba y el formato lo pueden bajar de “http://www.alumnos.usm.cl/∼jose.canepa/” en la sección de “Archivos”. 5. 5.1. Apéndice Compilar en Linux con gcc Para compilar en ubuntu, de partida necesitan la librerı́a build-essential (los laboratorios de la U ya lo tienen instalado): sudo apt-get install build-essential 1 Formato: Descrito por el profesor, “http://www.alumnos.usm.cl/∼jose.canepa/”. 5 será subido a la página Ahora que la tienen, para compilar su tarea (sola) lo mas simple es: gcc miarchivo.c -o salida Pero, si están usando la librerı́a entregada (binarr.h), tendrán que hacer un poco mas de trabajo, el siguiente comando compila y “linkea” ambas (Además de -Wall para mostrar todos las alertas (Wall: “Warn all”)): gcc -Wall binarr.c miarchivo.c -o salida Y ahora “salida” es su ejecutable, para ejecutarlo simplemente usen: ./salida Y si les dice que no tienen permiso de ejecutarlo, hagan: chmod u+x salida Esto les dará permiso de ejecución (+x) a ustedes (u = owner). Esto basta hacerlo una sola vez. 5.2. Usar la librerı́a entregada La librerı́a entregada provee una interfaz para cargar un archivo binario que ya guarda los números a probar. La razón de que se entregue esta librerı́a es que no interesa como carguen los datos, sino el desarrollo de algoritmos. El archivo binario que se les entregó tiene lo siguiente en su interior: Estructura: [int:largo] [[int][int][int][int][int][int][int]...:array de enteros] Contenido: 10 <10 numeros aleatorios> 100 <100 numeros aleatorios> 1000 <1.000 numeros aleatorios> 10^1 10^2 10^3 De tal manera que lo que hace la librerı́a es leer el primer bloque de 4 bytes y puede determinar cuantos enteros hay a continuación, lo que permite saltar usando fseek() para poder encontrar el bloque exacto que se pide dependiendo de la potencia pedida. Para utilizarla, la librerı́a viene con simples funciones, la primera que han de ejecutar, teniendo todos los archivos en la misma carpeta, y una vez incluida la cabecera binarr.h, es, por ejemplo: int* arr = binarr_load("desordenado.bin", 2); Esto retornará un arreglo desordenado con 100 (102 ) enteros entre 0 y 100 guardado en arr. Si desean ver el contenido, pasen el puntero a la función: 6 binarr_print(arr, 2); Y ası́ podrán ver lo que contiene (el 2 viene siendo la misma potencia anterior). Cuando desarrollen sus funciones, tendrán que pasar como argumento el largo, obviamente, no os sirve la potencia, hay que convertirla, para eso también está la función math power(): int length = math_power(10, 2); Ahora length será 100 (102 ), con lo que podrán trabajar con sus funciones. Finalmente, cuando terminen de usar dicho arreglo, deben deshacerse de él, dado que en C hay que especificar cuando eliminar memoria, también hay una función: binarr_free(arr); Si necesitan entrar en mas detalle, lean los comentarios que vienen en el código fuente de binarr.c. 5.3. ¿Por qué un archivo binario en potencias de diez? Resulta mas fácil guardarlos ası́ y recuperarlos. La principal razón, es para que al probar múltiples veces una misma función con diferentes potencias, puedan hacer: int i, *arr; // Prueba de for(i=1; { arrm arrn secuencial i<5; i++) = binarr_load("ordenado1.bin", i); = binarr_load("desordenado.bin", i); // Medir tiempo search_sequential(arrm, arrn, math_power(10, i), false); // Terminar de medir tiempo /* Mas codigo */ binarr_free(arrm); binarr_free(arrn); } // Prueba de binario // ... De tal manera que pueden colgar el proceso de carga de arreglos a un solo entero que representa el tamaño de entrada. 7 5.4. En caso de problemas con el archivo binario El archivo binario tiene números que van desde 0 hasta 1.000. Si observan que los dı́gitos están completamente fuera de esta escala (1.000+) es probable que estén teniendo problemas para leer el archivo binario, dada la arquitectura de su procesador. Algunos procesadores guardan al revés los datos en memoria. Si se da el caso que ustedes tengan datos erróneos solo por esto, escriban un correo al respecto y subiré archivos generados al revés. 8