Capítulo 1 - Introducción al análisis de algoritmos Problema Computacional Establece una relación entre la especificación general de una entrada que describe todas las posibles instancias del problema y la especificación de la salida esperada. Es importante notar que el problema sigue la noción de caja negra Los problemas computacionales son producto de la abstracción En otras palabras, podemos percibir y pensar en muchos detalles acerca de una situación particular de la realidad; cuando abstraemos esta situación ignoramos algunos detalles y prestamos atención a otros, en función del objetivo que queremos lograr. Ejemplo: Juego veintiuno que quitamos características innecesarias. Algoritmo Especificación de la solución a un problema computacional. El algoritmo especifica cómo se transforman las entradas en salidas. Desde una perspectiva teórica, el algoritmo debe ser correcto o eficaz Desde una perspectiva práctica debe ser eficiente (No consumir muchos recursos) Eficiencia Los algoritmos consumen recursos de las máquinas que los ejecutan, principalmente ciclos de procesador y memoria, pero también almacenamiento persistente, interfaces de red, memorias caché y GPUs, entre otros. Los algoritmos eficientes minimizan sus requerimientos de recursos. Los principales aspectos de eficiencia a considerar en un algoritmo son tiempo y espacio. Existe un patrón el cual es que cuando un algoritmo consume más memoria, entonces este consume menos ciclos de reloj y viceversa. Capítulo 1 - Modelo Ram Para acercarnos a las preguntas sobre tiempo de ejecución y consumo de memoria, es necesaria una perspectiva teórica y por tanto hablamos de análisis de complejidad temporal y de complejidad espacial. La máquina de acceso aleatorio, RAM por sus siglas en inglés, es una máquina teórica basada en una arquitectura de registros, de procesamiento lineal y funcionalmente equivalente a una máquina de Turing. Complejidad temporal(T(n)) Con respecto a la utilización de tiempo el modelo RAM hace una diferenciación entre operaciones simples y complejas. Cada operación simple –por ejemplo, las operaciones aritméticas, las operaciones lógicas o el condicional if– toma exactamente una unidad de tiempo en ejecutarse, a esta unidad le llamaremos paso. Las operaciones complejas corresponden a ciclos y procedimientos y podemos entenderlas como composiciones de operaciones simples y complejas. Cada acceso a memoria, ya sea de lectura o de escritura, toma exactamente un paso en ejecutarse. Complejidad espacial S(n): En este modelo cada parámetro y variable local de tipo primitivo consume una unidad de memoria. Un arreglo de tamaño n consume n unidades de memoria. Sin embargo, es importante notar que con las llamadas recursivas de pila es necesario contabilizar la memoria utilizada por cada llamada. Recordemos que la recursividad de pila deja cálculos pendientes y, por lo tanto, la memoria no puede ser liberada hasta que estos sean resueltos. Finalmente, es importante tomar en cuenta si los parámetros que recibe la función a analizar están siendo pasados por valor o por referencia. Cuando los parámetros se pasan por referencia, se está compartiendo 7 memoria entre la función que invoca y la función invocada, y por tanto su consumo sólo debe ser contabilizado una vez. Interpretación de las funciones T(n) y S(n): Puesto que estamos prestando especial atención al crecimiento de las funciones y no tanto a sus detalles específicos, podemos simplificar las expresiones que definen a las funciones T(n) y S(n) para tomar en cuenta únicamente su término de mayor crecimiento. Por ejemplo, para un T(n) = 3n + 10 tenemos los términos 3n y 10, tomamos el de mayor crecimiento 3n, y descartamos la constante 3 quedando entonces con n. Por tanto, decimos que T(n) = O(n). Capítulo 2 – Análisis asintótico A través del análisis asintótico vamos a poder determinar cotas o límites superiores e inferiores para nuestras funciones T (n) y S(p). Si la función g(n) es una cota superior de T (n), entonces el tiempo de ejecución T (n) nunca va a ser mayor que c · g(n), donde c es una constante específica. Por otro lado, si g(n) es una cota inferior de T (n), entonces el tiempo de ejecución de T (n) nunca va a ser menor que c · g(n), donde c es una constante específica. Cota Superior Decimos que c · g(n) es una cota superior de f (n) si y sólo si existen n 0 y c tal que n 0 ≥ 0 ∧ c > 0 ∧ c · g(n) ≥ f (n) , ∀ n ≥ n 0 . La cota superior se denota f (n) = O(g(n))