Source: http://www.slideshare.net/Jaro23/manual-de-anlisis-y-diseo-de-algoritmoswwwfreelibroscom
Timestamp: 2016-06-26 14:57:28+00:00

Document:
Manual analisis de algoritmo pre
by reny_so
by ch1c41z4
MMAANNUUAALL DDEE AANNÁÁLLIISSIISS YY DDIISSEEÑÑOODDEE AALLGGOORRIITTMMOOSSVVeerrssiióónn 11..00DDiirreecccciióónn ddee ÁÁrreeaa IInnffoorrmmááttiiccaaww wwww ..iinnffoorrmmaattiiccaa..iinnaa ccaapp..ccll
Página 1Colaboraron en el presente manual:Víctor Valenzuela Ruzv_valenzuela@inacap.clDocenteIngeniería en Gestión InformáticaINACAP Copiapó
Página 2Copyright© 2003 de Instituto Nacional de Capacitación. Todos los derechos reservados. Copiapó, Chile.Este documento puede ser distribuido libre y gratuitamente bajo cualquier soporte siempre ycuando se respete su integridad.Queda prohibida su venta y reproducción sin permiso expreso del autor.
Página 3PPrreeffaacciioo"Lo que debemos aprender a hacer lo aprendemos haciéndolo".Aristóteles, Ethica Nicomachea II (325 A.C.)El presente documento ha sido elaborado originalmente como apoyo a laasignatura de “Análisis y Diseño de Algoritmos” del séptimo semestre de lacarrera de Ingeniería en Gestión Informática, del Instituto Nacional deCapacitación (INACAP). Este documento engloba la mayor parte de la materiade este curso troncal e incluye ejemplos resueltos y algunos ejercicios que serándesarrollados en clases.El manual ha sido concebido para ser leído en forma secuencial, pero tambiénpara ser de fácil consulta para verificar algún tema específico.No se pretende que estos apuntes sustituyan a la bibliografía de la asignatura nia las clases teóricas, sino que sirvan más bien como complemento a las notasque el alumno debe tomar en clases. Asimismo, no debe considerarse undocumento definitivo y exento de errores, si bien ha sido elaborado condetenimiento y revisado exhaustivamente.El autor pretende que sea mejorado, actualizado y ampliado con ciertafrecuencia, lo que probablemente desembocará en sucesivas versiones, y paraello nadie mejor que los propios lectores para plantear dudas, buscar errores ysugerir mejoras.El Autor.Copiapó, Enero 2003
Página 4ÍÍnnddiiccee GGeenneerraallPáginaPresentación 71. Introducción1.1. Motivación y Objetivos 81.2. Algunas Notas sobre la Historia de los Algoritmos 101.3. Fundamentos Matemáticos 112. Algoritmos y Problemas2.1. Definición de Algoritmo 182.2. Formulación y Resolución de Problemas 192.3. Razones para Estudiar los Algoritmos 222.4. Formas de Representación de Algoritmos 232.5. La Máquina de Turing 243. Eficiencia de Algoritmos3.1. Introducción 253.2. Concepto de Eficiencia 253.3. Medidas de Eficiencia 263.4. Análisis A Priori y Prueba A Posteriori 273.5. Concepto de Instancia 273.6. Tamaño de los Datos 283.7. Cálculo de Costos de Algoritmos3.7.1. Cálculo de eficiencia en análisis iterativo 293.7.2. Cálculo de eficiencia en análisis recursivo 293.8. Principio de Invarianza 313.9. Análisis Peor Caso, Mejor Caso y Caso Promedio 314. Análisis de Algoritmos4.1. Introducción 344.2. Tiempos de Ejecución 344.3. Concepto de Complejidad 364.4. Órdenes de Complejidad 374.5. Notación Asintótica4.5.1. La O Mayúscula 394.5.2. La o Minúscula 394.5.3. Diferencias entre O y o 424.5.4. Las Notaciones Ω y Θ 424.5.5. Propiedades y Cotas más Usuales 424.6. Ecuaciones de Recurrencias4.6.1. Introducción 454.6.2. Resolución de Recurrecias 45
Página 54.6.3. Método del Teorema Maestro 454.6.4. Método de la Ecuación Característica 464.6.5. Cambio de Variable 484.7. Ejemplos y Ejercicios 495. Estrategias de Diseño de Algoritmos5.1. Introducción 515.2. Recursión 515.3. Dividir para Conquistar 555.4. Programación Dinámica 575.5. Algoritmos Ávidos 585.6. Método de Retroceso (backtracking) 605.7. Método Branch and Bound 616. Algoritmos de Ordenamiento6.1. Concepto de Ordenamiento 636.2. Ordenamiento por Inserción 636.3. Ordenamiento por Selección 646.4. Ordenamiento de la Burbuja (Bublesort) 656.5. Ordenamiento Rápido (Quicksort) 656.6. Ordenamiento por Montículo (Heapsort) 686.7. Otros Métodos de Ordenamiento6.7.1. Ordenamiento por Incrementos Decrecientes 746.7.2. Ordenamiento por Mezclas Sucesivas 757. Algoritmos de Búsqueda7.1. Introducción 787.2. Búsqueda Lineal 787.3. Búsqueda Binaria 807.4. Árboles de Búsqueda 817.5. Búsqueda por Transformación de Claves (Hashing) 817.6. Búsqueda en Textos7.6.1. Algoritmo de Fuerza Bruta 887.6.2. Algoritmo de Knuth-Morris-Pratt 887.6.3. Algoritmo de Boyer-Moore 928. Teoría de Grafos8.1. Definiciones Básicas 978.2. Representaciones de Grafos8.2.1. Matriz y Lista de Adyacencia 1018.2.2. Matriz y Lista de Incidencia 1038.3. Recorridos de Grafos8.3.1. Recorridos en Amplitud 1048.3.2. Recorridos en Profundidad 1068.4. Grafos con Pesos 1088.5. Árboles 108
Página 68.6. Árbol Cobertor Mínimo8.6.1. Algoritmo de Kruskal 1098.6.2. Algoritmo de Prim 1118.7. Distancias Mínimas en un Grafo Dirigido8.7.1. Algoritmo de Dijkstra 1138.7.2. Algoritmo de Ford 1148.7.3. Algoritmo de Floyd-Warshall 1159. Complejidad Computacional9.1. Introducción 1189.2. Algoritmos y Complejidad 1189.3. Problemas NP Completos 1189.4. Problemas Intratables 1219.5. Problemas de Decisión 1239.6. Algoritmos No Determinísticos 124Bibliografía 126
Página 7PresentaciónEl curso de Análisis y Diseño de Algoritmos (ADA) tiene como propósitofundamental proporcionar al estudiante las estructuras y técnicas de manejo dedatos más usuales y los criterios que le permitan decidir, ante un problemadeterminado, cuál es la estructura y los algoritmos óptimos para manipular losdatos.El curso está diseñado para proporcionar al alumno la madurez y losconocimientos necesarios para enfrentar, tanto una gran variedad de losproblemas que se le presentarán en su vida profesional futura, como aquellos quese le presentarán en los cursos más avanzados.El temario gira en torno a dos temas principales: estructuras de datos y análisis dealgoritmos. Haciendo énfasis en la abstracción, se presentan las estructuras dedatos más usuales (tanto en el sentido de útiles como en el de comunes), susdefiniciones, sus especificaciones como tipos de datos abstractos (TDAs), suimplantación, análisis de su complejidad en tiempo y espacio y finalmente algunasde sus aplicaciones. Se presentan también algunos algoritmos de ordenación, debúsqueda, de recorridos en gráficas y para resolver problemas mediante recursióny retroceso mínimo analizando también su complejidad, lo que constituye unaprimera experiencia del alumno con el análisis de algoritmos y le proporcionaráherramientas y madurez que le serán útiles el resto de su carrera.Hay que enfatizar que el curso no es un curso de programación avanzada, suobjetivo es preparar al estudiante brindándole una visión amplia de lasherramientas y métodos más usuales para la solución de problemas y el análisis dela eficiencia de dichas soluciones. Al terminar el curso el alumno poseerá unnutrido "arsenal" de conocimientos de los que puede echar mano cuando lorequiera en su futura vida académica y profesional. Sin embargo, dadas estascaracterísticas del curso, marca generalmente la frontera entre un programadorprincipiante y uno maduro, capaz de analizar, entender y programar sistemas desoftware más o menos complejos.Para realizar las implantaciones de las distintas estructuras de datos y de losalgoritmos que se presentan en el curso se hará uso del lenguaje de programaciónMODULA-2, C++ y Java.
Página 8Capítulo 1Introducción1.1 Motivación y ObjetivosLa representación de información es fundamental para las Ciencias de laComputación.La Ciencia de la Computación (Computer Science), es mucho más que elestudio de cómo usar o programar las computadoras. Se ocupa dealgoritmos, métodos de calcular resultados y máquinas autómatas.Antes de las computadoras, existía la computación, que se refiere al uso demétodos sistemáticos para encontrar soluciones a problemas algebraicos osimbólicos.Los babilonios, egipcios y griegos, desarrollaron una gran variedad demétodos para calcular cosas, por ejemplo el área de un círculo o cómocalcular el máximo común divisor de dos números enteros (teorema deEuclides).En el siglo XIX, Charles Babbage describió una máquina que podía liberar alos hombres del tedio de los cálculos y al mismo tiempo realizar cálculosconfiables.La motivación principal de la computación por muchos años fue la dedesarrollar cómputo numérico más preciso. La Ciencia de la Computacióncreció del interés en sistemas formales para razonar y la mecanización de lalógica, así cómo también del procesamiento de datos de negocios. Sinembargo, el verdadero impacto de la computación vino de la habilidad de lascomputadoras de representar, almacenar y transformar la información.La computación ha creado muchas nuevas áreas como las de correoelectrónico, publicación electrónica y multimedia.La solución de problemas del mundo real, ha requerido estudiar más decerca cómo se realiza la computación. Este estudio ha ampliado la gama deproblemas que pueden ser resueltos.Por otro lado, la construcción de algoritmos es una habilidad elegante de ungran significado práctico. Computadoras más poderosas no disminuyen elsignificado de algoritmos veloces. En la mayoría de las aplicaciones no es elhardware el cuello de botella sino más bien el software inefectivo.
Página 9Este curso trata de tres preguntas centrales que aparecen cuando uno quiereque un computador haga algo: ¿Es posible hacerlo? ¿Cómo se hace? y ¿Cuánrápido puede hacerse? El curso da conocimientos y métodos para responderestas preguntas, al mismo tiempo intenta aumentar la capacidad deencontrar algoritmos efectivos. Los problemas para resolver, son unentrenamiento en la solución algorítmica de problemas, y para estimular elaprendizaje de la materia.Objetivos Generales:1. Introducir al alumno en el análisis de complejidad de los algoritmos,así como en el diseño e implementación de éstos con las técnicas ymétodos más usados.2. Desarrollar habilidades en el uso de las técnicas de análisis y diseñode algoritmos computacionales.3. Analizar la eficiencia de diversos algoritmos para resolver unavariedad de problemas, principalmente no numéricos.4. Enseñar al alumno a diseñar y analizar nuevos algoritmos.5. Reconocer y clasificar los problemas de complejidad polinómica y nopolinómica.Metas del curso:Al finalizar este curso, el estudiante debe estar capacitado para:• analizar, diseñar e implementar algoritmos iterativos y recursivoscorrectos.• medir la eficiencia de algoritmos iterativos y recursivos, y de tipos oclases de datos orientados por objetos.• utilizar la técnica de diseño de algoritmos más adecuada para unaaplicación en particular.• definir la aplicabilidad de las diferentes técnicas de búsqueda yordenamiento, del procesamiento de cadenas de caracteres, de losmétodos geométricos, del uso de los algoritmos de grafos, de losalgoritmos paralelos y de los algoritmos de complejidad nopolinómica.• diferenciar entre los algoritmos de complejidad polinómica y nopolinómica.
Página 101.2 Algunas Notas sobre la Historia de los AlgoritmosEl término proviene del matemático árabe AlKhwarizmi, que escribió untratado sobre los números. Este texto se perdió, pero su versión latina,Algoritmi de Numero Indorum, sí se conoce.El trabajo de AlKhwarizmipermitió preservar y difundir el conocimiento delos griegos (con la notable excepción del trabajo de Diofanto) e indios,pilares de nuestra civilización. Rescató de los griegos la rigurosidad y de losindios la simplicidad (en vez de una larga demostración, usar un diagramajunto a la palabra Mira). Sus libros son intuitivos y prácticos y su principalcontribución fue simplificar las matemáticas a un nivel entendible por noexpertos. En particular muestran las ventajas de usar el sistema decimalindio, un atrevimiento para su época, dado lo tradicional de la cultura árabe.La exposición clara de cómo calcular de una manera sistemática a través dealgoritmos diseñados para ser usados con algún tipo de dispositivo mecánicosimilar a un ábaco, más que con lápiz y papel, muestra la intuición y el poderde abstracción de AlKhwarizmi. Hasta se preocupaba de reducir el númerode operaciones necesarias en cada cálculo. Por esta razón, aunque no hayasido él el inventor del primer algoritmo, merece que este concepto estéasociado a su nombre.Los babilonios que habitaron en la antigua Mesopotania, empleaban unaspequeñas bolas hechas de semillas o pequeñas piedras, a manera de"cuentas" y que eran agrupadas en carriles de caña. Más aún, en 1.800 A.C.un matemático babilónico inventó los algoritmos que le permitieron resolverproblemas de cálculo numérico.En 1850 A.C., un algoritmo de multiplicación similar al de expansión binariaes usado por los egipcios.La teoría de las ciencias de la computación trata cualquier objetocomputacional para el cual se puede crear un buen modelo. La investigaciónen modelos formales de computación se inició en los 30s y 40s por Turing,Post, Kleene, Church y otros. En los 50s y 60s los lenguajes deprogramación, compiladores y sistemas operativos estaban en desarrollo,por lo tanto, se convirtieron tanto en el sujeto como la base para la mayoríadel trabajo teórico.El poder de las computadoras en este período estaba limitado porprocesadores lentos y por pequeñas cantidades de memoria. Así, sedesarrollaron teorías (modelos, algoritmos y análisis) para hacer un usoeficiente de ellas. Esto dio origen al desarrollo del área que ahora se conocecomo "Algoritmos y Estructuras de Datos". Al mismo tiempo se hicieronestudios para comprender la complejidad inherente en la solución dealgunos problemas. Esto dió origen a lo que se conoce como la jerarquía deproblemas computacionales y al área de "Complejidad Computacional".
Página 111.3 Fundamentos MatemáticosMonotonicidadUna función f es monótona si es creciente o decreciente. Es creciente sirige la implicación siguiente:∀ n0, n1 : (n1 = n2) è f(n1) = f(n2 )Es decreciente si rige la implicación siguiente:∀ n0, n1 : (n1 = n2) è f(n1) = f(n2 )Una función f(n) es monotónicamente creciente si m ≤ n implica quef(m) ≤ f(n). Similarmente, es monotónicamente decreciente si m ≤ nimplica que f(m) ≥ f(n). Una función f(n) es estrictamente crecientesim < n implica que f(m) < f(n) y estrictamente decreciente si m < nimplica que f(m) > f(n).ConjuntosUn conjunto es una colección de miembros o elementos distinguibles. Losmiembros se toman típicamente de alguna población más grande conocidacomo tipo base. Cada miembro del conjunto es un elemento primitivodel tipo base o es un conjunto. No hay concepto de duplicación en unconjunto.Un orden lineal tiene las siguientes propiedades:• Para cualesquier elemento a y b en el conjunto S, exactamente uno dea < b, a = b, o a > b es verdadero.• Para todos los elementos a, b y c en el conjunto S, si a < b, y b < c,entonces a < c. Esto es conocido como la propiedad detransitividad.PermutaciónUna permutación de una secuencia es simplemente los elementos de lasecuencia arreglados en cualquier orden. Si la secuencia contiene nelementos, existe n! permutaciones.
Página 12Funciones Piso y TechoPiso (floor) y techo (ceiling) de un número real x. Son respectivamenteel mayor entero menor o igual que x, y el menor entero mayor o igual a x.Para cualquier número real x, denotamos al mayor entero, menor que o iguala x como floor(x), y al menor entero, mayor que o igual a x comoceiling(x); (floor=piso, ceiling=techo). Para toda x real,x - 1 < floor(x) ≤ x ≤ ceiling(x) < x + 1Para cualquier entero n,ceiling(x/2) + floor(x/2) = n,y para cualquier entero n y enteros a ≠ 0 y b ≠ 0,ceiling(ceiling(n/a)/b) = ceiling(n/ab) yfloor(floor(n/a)/b) = floor(n/ab).Las funciones floory ceiling son monótonas crecientes.Operador móduloEsta función regresa el residuo de una división entera. Generalmente seescribe n mod m, y el resultado es el entero r, tal que n= qm+r para q unentero y/0 menor o igual que r y r < m.Por ejemplo: 5 mod 3 = 2 y 25 mod 3 = 1PolinomiosDado un entero positivo d, un polinomio en n de grado d es una funciónp(n) de la forma:p(n) = ∑ d*ai nii=0donde las constantes a0, a1,..., ad son los coeficientes del polinimio y ad ≠0. Un polinomio es asintóticamente positivo si y sólo si ad > 0. Para unpolinomio asintóticamente positivo p(n) de grado d, tenemos que p(n) =O(nd). Para cualquier constante real a ≥ 0, la función na es monótonacreciente, y para cualquier constante real a ≤ 0, la función na es monótonadecreciente. Decimos que una función f(n) es polinomialmente acotadasi f(n) = nO(1), que es equivalente en decir que f(n) = O(nk) para algunaconstante k.
Página 13ExponencialesPara toda a ≠ 0, m y n, tenemos las siguientes identidades:aº = 1a¹ = aa(-1) =1/a(a m) n = a m n(a m) n = (a n)ma m a n = a m +nPara todo n y a ≥ ,1lim nà ∞ nb/an = 0de lo que concluimos que nb=o(an).Entonces, cualquier función exponencial positiva crece más rápido quecualquier polinomio.i=0exp(x) = ∑ x i /(i!)∞LogaritmosUn logaritmo de base b para el valor y, es la potencia al cual debe elevarseb para obtener y. Logb y = x ó bx =y.Usos para los logaritmos:• ¿Cuál es el número mínimo de bits necesarios para codificar unacolección de objetos? La respuesta es techo(log2 n).• Por ejemplo, si se necesitan 1000 códigos que almacenar, serequerirán al menos techo(log2 1000) = 10 bits para tener 1000códigos distintos. De hecho con 10 bits hay 1024 códigos distintosdisponibles.• Análisis de algoritmos que trabajan rompiendo un problema ensubproblemas más pequeños. Por ejemplo, la búsqueda binaria de unvalor dado en una lista ordenada por valor. ¿Cuántas veces puedeuna lista de tamaño n ser divida a la mitad hasta que un sóloelemento quede en la lista final? La respuesta es log2 n.
Página 14Los logaritmos tienen las siguientes propiedades:• log nm = log n + log m• log n/m = log n – log m• log nr = r log n• loga n = logb n/ logb a (para a y b enteros)Esta última propiedad dice que el logaritmo de n en distintas bases, estárelacionado por una constante (que es logaritmo de una base en la otra). Asíque análisis de complejidad que se hacen en una base de logaritmos, puedenfácilmente traducirse en otra, simplemente con un factor deproporcionalidad.FactorialesLa notación n! se define para los enteros n ≥ 0 como:1 si n=0n! =n·(n-1)! si n>0Entonces, n!=1·2·3·4 ··· n.Una cota superior débil de la función factorial es n! ≤ nn, pues cada uno delos n términos en el producto factorial es a lo más n. La aproximación deStirling, proporciona una cota superior ajustada, y también una cotainferior:n! = sqrt{2pn} (n/e)n (1 + Q(1/n))donde e es la base del logaritmo natural. Usando la aproximación deStirling, podemos demostrar que:n! = o(nn)n! = (2n)lg(n!) = Q(n lg n)Números de FibonacciLos números de Fibonacci se definen por la siguiente recurrencia:F0 = 0F1 = 1Fi = Fi-1 + Fi-2 para i ≥ 2
Página 15Entonces cada número de Fibonacci es la suma de los números previos,produciendo la sucesión:0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, ...RecursiónUn algoritmo es recursivo si se llama a sí mismo para hacer parte deltrabajo. Para que este enfoque sea exitoso, la llamada debe ser en unproblema menor que al originalmente intentado.En general, un algoritmo recursivo tiene dos partes:• El caso base, que maneja una entrada simple que puede ser resueltasin una llamada recursiva• La parte recursiva, que contiene una o más llamadas recursivas alalgoritmo, donde los parámetros están en un sentido más cercano alcaso base, que la llamada original.Ejemplos de aplicación: cálculo del factorial, torres de Hanoi y función deAckermann.Sumatorias y recurrenciasLas sumatorias se usan mucho para el análisis de la complejidad deprogramas, en especial de ciclos, ya que realizan conteos de operacionescada vez que se entra al ciclo.Cuando se conoce una ecuación que calcula el resultado de una sumatoria,se dice que esta ecuación representa una solución en forma cerrada.Ejemplos de soluciones en forma cerrada:• Σ i = n(n+1)/2• Σ I 2 = (2n 3 +3n 2 + n)/6• Σ 1 log n n = n log n• Σ∞a i = 1/(1-a)El tiempo de ejecución para un algoritmo recursivo está más fácilmenteexpresado por una expresión recursiva.Una relación de recurrencia define una función mediante una expresiónque incluye una o más instancias (más pequeñas) de si misma. Por ejemplo:
Página 16n! = (n-1)! n , 1! = 0! = 1La secuencia de Fibonacci:• Fib(n)=Fib(n-1)+Fib(n-2); Fib(1)=Fib(2)=1• 1,1,2,3,5,8,13,...Técnicas de Demostración MatemáticaPrueba por contradicciónEs la forma más fácil de refutar un teorema o enunciado, es mediante uncontra-ejemplo. Desafortunadamente, no importa el número de ejemplosque soporte un teorema, esto no es suficiente para probar que es correcto.Para probar un teorema por contradicción, primero suponemos que elteorema es falso. Luego encontramos una contradicción lógica que surja deesta suposición. Si la lógica usada para encontrar la contradicción escorrecta, entonces la única forma de resolver la contradicción es suponer quela suposición hecha fue incorrecta, esto es, concluir que el teorema debe serverdad.Prueba por inducción matemáticaLa inducción matemática es como recursión y es aplicable a una variedad deproblemas.La inducción proporciona una forma útil de pensar en diseño de algoritmos,ya que lo estimula a pensar en resolver un problema, construyendo a partirde pequeños subproblemas.Sea T un teorema a probar, y expresemos T en términos de un parámetroentero positivo n. La inducción matemática expresa que T es verdad paracualquier valor de n, si las siguientes condiciones se cumplen:• Caso base. T es verdad para n = 1• Paso inductivo. Si T es verdad para n-1, => T es verdad para n.Ejemplo de demostración del teorema:La suma de los primeros n números enteros es n(n+1)/2.
Página 17EstimaciónConsiste en hacer estimaciones rápidas para resolver un problema. Puedeformalizarse en tres pasos:• Determine los parámetros principales que afectan el problema.• Derive una ecuación que relacione los parámetros al problema.• Seleccione valores para los parámetros, y aplique la ecuación paraobtener una solución estimada.Ejemplo: ¿Cuántos libreros se necesitan para guardar libros que contienenen total un millón de páginas?Se estiman que 500 páginas de un libro requieren cerca de una pulgada en larepisa del librero, con lo cual da 2000 pulgadas de repisa. Lo cual da 167pies de repisa (aprox. 200 pies). Si una repisa es alrededor de 4 pies deancho, entonces se necesitan 50 repisas. Si un librero contiene 5 repisas,entonces se necesitan 10 libreros.
Página 18Capítulo 2Algoritmos y Problemas2.1 Definición de AlgoritmoEl concepto intuitivo de algoritmo, lo tenemos prácticamente todos: Unalgoritmo es una serie finita de pasos para resolver un problema.Hay que hacer énfasis en dos aspectos para que un algoritmo exista:1. El número de pasos debe ser finito. De esta manera el algoritmo debeterminar en un tiempo finito con la solución del problema,2. El algoritmo debe ser capaz de determinar la solución del problema.De este modo, podemos definir algoritmo como un "conjunto de reglasoperacionales inherentes a un cómputo". Se trata de un método sistemático,susceptible de ser realizado mecánicamente, para resolver un problemadado.Sería un error creer que los algoritmos son exclusivos de la informática.También son algoritmos los que aprendemos en la escuela para multiplicar ydividir números de varias cifras. De hecho, el algoritmo más famoso de lahistoria se remonta a la antigüedad: se trata del algoritmo de Euclides paracalcular el máximo común divisor.Siempre que se desee resolver un problema hay que plantearse quéalgoritmo utilizar. La respuesta a esta cuestión puede depender denumerosos factores, a saber, el tamaño del problema, el modo en que estáplanteado y el tipo y la potencia del equipo disponible para su resolución.Características de un algoritmo1. Entrada: definir lo que necesita el algoritmo2. Salida: definir lo que produce.3. No ambiguo: explícito, siempre sabe qué comando ejecutar.4. Finito: El algoritmo termina en un número finito de pasos.5. Correcto: Hace lo que se supone que debe hacer. La solución escorrecta6. Efectividad: Cada instrucción se completa en tiempo finito. Cadainstrucción debe ser lo suficientemente básica como para que enprincipio pueda ser ejecutada por cualquier persona usando papel ylápiz.
Página 197. General: Debe ser lo suficientemente general como para contemplartodos los casos de entrada.Así podemos, decir que un Algoritmo es un conjunto finito deinstrucciones precisas para resolver un problema.Un algoritmo es un método o proceso seguido para resolver un problema.Si el problema es visto como una función, entonces el algoritmo toma unaentrada y la transforma en la salida.Un problema es una función o asociación de entradas con salidas. Unproblema puede tener muchos algoritmos.Por tanto, un algoritmo es un procedimiento para resolver un problemacuyos pasos son concretos y no ambiguos. El algoritmo debe ser correcto, delongitud finita y debe terminar para todas las entradas. Un programa esuna instanciación de un algoritmo en un lenguaje de programación.2.2 Formulación y Resolución de ProblemasLos algoritmos son los procedimientos que se construyen para la resoluciónde cualquier problema. De este modo, cuando se refiere a la construcción deun programa, nos estamos refiriéndo a la construcción de un algoritmo. Elalgoritmo no es un concepto proveniente del campo de la computación, sinoque es un término matemático.Los algoritmos los encontramos, o mejor, los ejecutamos a lo largo denuestras actividades diarias; por ejemplo, cuando hacemos una llamadatelefónica, tenemos en cuenta un conjunto de instrucciones mínimas y elorden en el cual debemos ejecutarlas para conseguir comunicarnos conalguien en particular; o cuando consultamos un diccionario, cuando seprepara un menú, etc.Podemos conceptuar que un algoritmo es un procedimiento que contiene unconjunto finito de pasos que se deben ejecutar en un orden específico ylógico, para solucionar los problemas.Un algoritmo puede ser caracterizado por una función lo cual asocia unasalida: s= f (E) a cada entrada E.Se dice entonces que un algoritmo calcula una función f. Entonces la entradaes una variable independiente básica en relación a la que se producen lassalidas del algoritmo, y también los análisis de tiempo y espacio.
Página 20Cuando se tiene un problema para el cual debemos especificar un algoritmosolución, tendremos en cuenta varios puntos:o Si no se conoce un método para solucionar el problema en cuestión,debemos hacer un análisis del mismo para llegar a una solución,después de evaluar alternativas, exigencias y excepciones.o Si se conoce un "buen" método de solución al problema, se debeespecificar exacta y completamente el método o procedimiento desolución en un lenguaje que se pueda interpretar fácilmente.o Los problemas pueden agruparse en conjunto de problemas que sonsemejantes.o En algún sentido, en general, un método para solucionar todos losproblemas de un conjunto dado, se considera superior a un métodopara solucionar solamente un problema del conjunto.o Hay criterios para determinar que tan "buena" es una solución,distintos de ver si trabaja o no, o hasta qué punto es general laaplicabilidad del método. Estos criterios involucran aspectos talescomo: eficiencia, elegancia, velocidad, etc.Al definir con exactitud un método de solución para un problema, este debeestar en capacidad de encontrarla si existe; en caso de que no exista, elmétodo debe reconocer esta situación y suspender cualquier acción.Formular y resolver problemas constituye una actividad esencial de la vida.Los modelos educativos se han dedicado a plantear problemas y enseñarcomo solucionarlos. Sin embargo, no se enseña nunca técnicas de cómoresolver problemas en general.La formulación y resolución de problemas ha sido preocupación de lospsicólogos cuando hablan de la inteligencia del hombre. Ellos han concebidoesta capacidad como el concurso de ciertas destrezas mentales relacionadascon lo que ya se ha aprendido y que incluyen:o Capacidad de analizar los problemas.o La rapidez y precisión con que acude al pensamiento.o Una organización de ideas para garantizar que se posee lainformación esencial que asegura el aprendizaje.o Una retención clara del incremento de conocimiento.No se trata de almacenar problemas y sus correspondientes soluciones, sinode desarrollar una capacidad para hacer frente con éxito a situacionesnuevas o desconocidas en la vida del hombre. Ante situaciones nuevas, elque no sabe buscar soluciones se sentirá confuso y angustiado y entonces nobusca una estrategia y dará una primera solución para poner punto final a suagonía.
Página 21El que sabe buscar soluciones, selecciona la estrategia que le parece máscercana a la requerida y hace una hábil adaptación que se ajusta a la nuevademanda.Al movernos dentro de la informática no siempre encontramos losproblemas a punto de resolverlos, es necesario empezar por hacer suformulación. Se exige hacerlo en términos concisos y claros partiendo de lasbases o supuestos que se dispone para especificar sus requerimientos.Sólo a partir de una buena formulación será posible diseñar una estrategiade solución. Es necesario aprender a desligar estos dos procesos. No sedeben hacer formulaciones pensando paralelamente en posibles soluciones.Es necesario darle consistencia e independencia para determinar conprecisión a qué se quiere dar solución y luego canalizar una gama dealternativas posibles.Si ya se ha realizado la formulación del problema podemos cuestionarla conel fin de entender bien la naturaleza del problema.En nuestra área, el análisis de un problema tiene dos etapas claramentedefinidas y relacionadas:o Formulación o planteamiento del problema.o Resolución del problema.A su vez, la formulación la podemos descomponer en tres etapas:o Definición del problema.o Supuestos: aserciones y limitaciones suministradas.o Resultados esperados.La fase de planteamiento del problema lo que pretende un algoritmo essintetizar de alguna forma una tarea, cálculo o mecanismo antes de sertranscrito al computador. Los pasos que hay que seguir son los siguientes:o Análisis previo del problema.o Primera visión del método de resolución.o Descomposición en módulos.o Programación estructurada.o Búsqueda de soluciones parciales.o Ensamblaje de soluciones finales.La fase de resolución del problema se puede descomponer en tresetapas:o Análisis de alternativas y selección de la solución.o Especificación detallada del procedimiento solución.
Página 22o Adopción o utilización de una herramienta para suimplementación, si es necesaria.Hay que notar que el computador es un medio y no es el fin en la solución deproblemas.En otras palabras, no es el computador el que soluciona los problemas,somos nosotros quienes lo hacemos y de alguna manera le contamos comoes la cosa para que él con su velocidad y exactitud trabaje con grandesvolúmenes de datos.En el campo de las ciencias de la computación la solución de problemas sedescribe mediante el diseño de procedimientos llamados algoritmos, loscuales posteriormente se implementan como programas. Losprogramas son procedimientos que solucionan problemas y que seexpresan en un lenguaje conocido por el computador.Se pueden dar problemas que por su tamaño es necesario subdividirlos enproblemas más simples para solucionarlos, utilizando la filosofía de "Dividirpara conquistar". Se parte del principio de que es más fácil solucionar variosproblemas simples como partes de un todo que seguir una implantación de"Todo o Nada". Desarrollar la capacidad de formular y resolver problemasnos prepara para enfrentar situaciones desconocidas.2.3 Razones para Estudiar los AlgoritmosCon el logro de computadores cada vez más rápidos se podría caer en latentación de preguntarse si vale la pena preocuparse por aumentar laeficiencia de los algoritmos. ¿No sería más sencillo aguardar a la siguientegeneración de computadores?. Vamos a demostrar que esto no es así.Supongamos que se dispone, para resolver un problema dado, de unalgoritmo que necesita un tiempo exponencial y que, en un ciertocomputador, una implementación del mismo emplea 10-4 x 2n segundos.Este programa podrá resolver un ejemplar de tamaño n=10 en una décimade segundo. Necesitará casi 10 minutos para resolver un ejemplar de tamaño20. Un día entero no bastará para resolver uno de tamaño 30. En un año decálculo ininterrumpido, a duras penas se resolverá uno de tamaño 38.Imaginemos que necesitamos resolver ejemplares más grandes ycompramos para ello un computador cien veces más rápido. El mismoalgoritmo conseguirá resolver ahora un ejemplar de tamaño n en sólo 10-6 2nsegundos. ¡Qué decepción al constatar que, en un año, apenas se consigueresolver un ejemplar de tamaño 45!En general, si en un tiempo dado se podía resolver un ejemplar de tamaño n,con el nuevo computador se resolverá uno de tamaño n+7 en ese mismotiempo.
Página 23Imaginemos, en cambio, que investigamos en algorítmica y encontramos unalgoritmo capaz de resolver el mismo problema en un tiempo cúbico. Laimplementación de este algoritmo en el computador inicial podría necesitar,por ejemplo, 10-2 n3 segundos. Ahora se podría resolver en un día unejemplar de un tamaño superior a 200. Un año permitiría alcanzar casi eltamaño 1500.Por tanto, el nuevo algoritmo no sólo permite una aceleración másespectacular que la compra de un equipo más rápido, sino que hace dichacompra más rentable.Dado estos argumentos, podemos resumir las siguientes razones parajustificar el estudiar los algoritmos:1. Evitar reinventar la rueda.Para algunos problemas de programación ya existen buenos algoritmospara solucionarlos. Para esos algoritmos ya fueron analizadas suspropiedades. Por ejemplo, confianza en su correctitud y eficiencia.2. Para ayudar cuando desarrollen sus propios algoritmos.No siempre existe un algoritmo desarrollado para resolver un problema.No existe regla general de creación de algoritmos. Muchos de losprincipios de proyecto de algoritmos ilustrados por algunos de losalgoritmos que estudiaremos son importantes en todos los problemas deprogramación. El conocimiento de los algoritmos bien definidos proveeuna fuente de ideas que pueden ser aplicadas a nuevos algoritmos.3. Ayudar a entender herramientas que usan algoritmos particulares. Porejemplo, herramientas de compresión de datos:• Pack usa Códigos Huffman.• Compress usa LZW.• Gzip usa Lempel-Ziv.4. Útil conocer técnicas empleadas para resolver problemas dedeterminados tipos.2.4 Formas de Representación de AlgoritmosExisten diversas formas de representación de algoritmos, pero no hay unconsenso con relación a cuál de ellas es mejor.Algunas formas de representación de algoritmos tratan los problemas a unnivel lógico, abstrayéndose de detalles de implementación, muchas vecesrelacionados con un lenguaje de programación específico. Por otro lado,existen formas de representación de algoritmos que poseen una mayor
Página 24riqueza de detalles y muchas veces acaban por oscurecer la idea principal, elalgoritmo, dificultando su entendimiento.Dentro de las formas de representación de algoritmos más conocidas,sobresalen:• La descripción narrativa• El Flujograma convencional• El diagrama Chapin• El pseudocódigo, o también conocido como lenguaje estructurado.2.5 La Máquina de TuringAlan Turing, en 1936 desarrolló su Máquina de Turing (la cual se cubreen los cursos denominados Teoría de la Computación o Teoría deAutomátas), estableciendo que cualquier algoritmo puede ser representadopor ella.Turing mostró también que existen problemas matemáticos bien definidospara los cuales no hay un algoritmo. Hay muchos ejemplos de problemaspara los cuales no existe un algoritmo. Un problema de este tipo es elllamado problema de paro (halting problem):Dado un programa de computadora con sus entradas, ¿parará estealguna vez?Turing probó que no hay un algoritmo que pueda resolver correctamentetodas las instancias de este problema.Alguien podría pensar en encontrar algunos métodos para detectar patronesque permitan examinar el programa para cualquier entrada. Sin embargo,siempre habrá sutilezas que escapen al análisis correspondiente.Alguna persona más suspicaz, podría proponer simplemente correr elprograma y reportar éxito si se alcanza una declaración de fin.Desgraciadamente, este esquema no garantiza por sí mismo un paro y enconsecuencia el problema no puede ser resuelto con los pasos propuestos.Como consecuencia de acuerdo con la definición anterior, ese últimoprocedimiento no es un algoritmo, pues no se llega a una solución.Muchas veces aunque exista un algoritmo que pueda solucionarcorrectamente cualquier instancia de un problema dado, no siempre dichoalgoritmo es satisfactorio porque puede requerir de tiempos exageradamenteexcesivos para llegar a la solución.
Página 25Capítulo 3Eficiencia de Algoritmos3.1 IntroducciónUn objetivo natural en el desarrollo de un programa computacional esmantener tan bajo como sea posible el consumo de los diversos recursos,aprovechándolos de la mejor manera que se encuentre. Se desea un buenuso, eficiente, de los recursos disponibles, sin desperdiciarlos.Para que un programa sea práctico, en términos de requerimientos dealmacenamiento y tiempo de ejecución, debe organizar sus datos en unaforma que apoye el procesamiento eficiente.Siempre que se trata de resolver un problema, puede interesar considerardistintos algoritmos, con el fin de utilizar el más eficiente. Pero, ¿cómodeterminar cuál es "el mejor"?. La estrategia empírica consiste enprogramar los algoritmos y ejecutarlos en un computador sobre algunosejemplares de prueba. La estrategia teórica consiste en determinarmatemáticamente la cantidad de recursos (tiempo, espacio, etc.) quenecesitará el algoritmo en función del tamaño del ejemplar considerado.El tamaño de un ejemplar x corresponde formalmente al número de dígitosbinarios necesarios para representarlo en el computador. Pero a nivelalgorítmico consideraremos el tamaño como el número de elementos lógicoscontenidos en el ejemplar.3.2 Concepto de EficienciaUn algoritmo es eficiente cuando logra llegar a sus objetivos planteadosutilizando la menor cantidad de recursos posibles, es decir, minimizando eluso memoria, de pasos y de esfuerzo humano.Un algoritmo es eficaz cuando alcanza el objetivo primordial, el análisis deresolución del problema se lo realiza prioritariamente.Puede darse el caso de que exista un algoritmo eficaz pero no eficiente, en loposible debemos de manejar estos dos conceptos conjuntamente.La eficiencia de un programa tiene dos ingredientes fundamentales:espacio y tiempo.• La eficiencia en espacio es una medida de la cantidad de memoriarequerida por un programa.
Página 26• La eficiencia en tiempo se mide en términos de la cantidad detiempo de ejecución del programa.Ambas dependen del tipo de computador y compilador, por lo que no seestudiará aquí la eficiencia de los programas, sino la eficiencia de losalgoritmos. Asimismo, este análisis dependerá de si trabajamos conmáquinas de un solo procesador o de varios de ellos. Centraremos nuestraatención en los algoritmos para máquinas de un solo procesador queejecutan una instruccióny luego otra.3.3 Medidas de EficienciaInventar algoritmos es relativamente fácil. En la práctica, sin embargo, nose pretende sólo diseñar algoritmos, si no más bien que buenos algoritmos.Así, el objetivo es inventar algoritmos y probar que ellos mismos sonbuenos.La calidad de un algoritmo puede ser avalada utilizando varios criterios.Uno de los criterios más importantes es el tiempo utilizado en la ejecucióndel algoritmos. Existen varios aspectos a considerar en cada criterio detiempo. Uno de ellos está relacionado con el tiempo de ejecuciónrequerido por los diferentes algoritmos, para encontrar la solución final deun problema o cálculo particular.Normalmente, un problema se puede resolver por métodos distintos, condiferentes grados de eficiencia. Por ejemplo: búsqueda de un número enuna guía telefónica.Cuando se usa un computador es importante limitar el consumo derecursos.Recurso Tiempo:• Aplicaciones informáticas que trabajan “en tiempo real” requierenque los cálculos se realicen en el menor tiempo posible.• Aplicaciones que manejan un gran volumen de información sino se tratan adecuadamente pueden necesitar tiemposimpracticables.Recurso Memoria:• Las máquinas tienen una memoria limitada.
Página 273.4 Análisis A Priori y Prueba A PosterioriEl análisis de la eficiencia de los algoritmos (memoria y tiempo de ejecución)consta de dos fases: Análisis A Priori y Prueba A Posteriori.El Análisis A Priori (o teórico) entrega una función que limita el tiempode cálculo de un algoritmo. Consiste en obtener una expresión que indique elcomportamiento del algoritmo en función de los parámetros que influyan.Esto es interesante porque:• La predicción del costo del algoritmo puede evitar unaimplementación posiblemente laboriosa.• Es aplicable en la etapa de diseño de los algoritmos, constituyendouno de los factores fundamentales a tener en cuenta.En la Prueba A Posteriori (experimental o empírica) se recogenestadísticas de tiempo y espacio consumidas por el algoritmo mientras seejecuta. La estrategia empírica consiste en programar los algoritmos yejecutarlos en un computador sobre algunos ejemplares de prueba, haciendomedidas para:• una máquina concreta,• un lenguaje concreto,• un compilador concreto y• datos concretosLa estrategia teórica tiene como ventajas que no depende del computador nidel lenguaje de programación, ni siquiera de la habilidad del programador.Permite evitar el esfuerzo inútil de programar algoritmos ineficientes y dedespediciar tiempo de máquina para ejecutarlos. También permite conocerla eficiencia de un algoritmo cualquiera que sea el tamaño del ejemplar alque se aplique.3.5 Concepto de InstanciaUn problema computacional consiste en una caracterización de unconjunto de datos de entrada, junto con la especificación de la salidadeseada en base a cada entrada.Un problema computacional tiene una o más instancias, que son valoresparticulares para los datos de entrada, sobre los cuales se puede ejecutar elalgoritmo para resolver el problema.Ejemplo: el problema computacional de multiplicar dos números enterostiene por ejemplo, las siguientes instancias: multiplicar 345 por 4653,multiplicar 2637 por 10000, multiplicar -32341 por 12, etc.
Página 28Un problema computacional abarca a otro problema computacional si lasinstancias del segundo pueden ser resueltas como instancias del primero enforma directa.Ejemplo: Problema del Vendedor Viajero.3.6 Tamaño de los DatosVariable o expresión en función de la cual intentaremos medir lacomplejidad del algoritmo.Es claro que para cada algoritmo la cantidad de recurso (tiempo, memoria)utilizados depende fuertemente de los datos de entrada. En general, lacantidad de recursos crece a medida que crece el tamaño de la entrada.El análisis de esta cantidad de recursos no es viable de ser realizadoinstancia por instancia.Se definen entonces las funciones de cantidad de recursos en base altamaño (o talla) de la entrada. Suele depender del número de datos delproblema. Este tamaño puede ser la cantidad de dígitos para un número, lacantidad de elementos para un arreglo, la cantidad de caracteres de unacadena, en problemas de ordenación es el número de elementos a ordenar,en matrices puede ser el número de filas, columnas o elementos totales, enalgoritmos recursivos es el número de recursiones o llamadas propias quehace la función.En ocasiones es útil definir el tamaño de la entrada en base a dos o másmagnitudes. Por ejemplo, para un grafo es frecuente utilizar la cantidad denodos y de arcos.En cualquier caso, se debe elegir la misma variable para compararalgoritmos distintos aplicados a los mismos datos.C1C2C3C4Una instancia de soluciónsería:<C1, C2 , C4, C3 >con una longitud de 27.1056939
Página 293.7 Cálculo de Costos de AlgoritmosQueremos saber la eficiencia de los algoritmos, no del computador. Por ello,en lugar de medir el tiempo de ejecución en microsegundos o algo por elestilo, nos preocuparemos del númerode veces que se ejecuta una operaciónprimitiva (de tiempo fijo).Para estimar la eficiencia de este algoritmo, podemos preguntarnos, "si elargumento es una frase de N números, ¿cuántas multiplicacionesrealizaremos?" La respuesta es que hacemos una multiplicación por cadanúmero en el argumento, por lo que hacemos N multiplicaciones. Lacantidad de tiempo que se necesitaría para el doble de números sería eldoble.3.7.1 Cálculo de eficiencia en análisis iterativoCuando se analiza la eficiencia, en tiempo de ejecución, de un algoritmo sonposibles distintas aproximaciones: desde el cálculo detallado (que puedecomplicarse en muchos algoritmos si se realiza un análisis para distintoscontenidos de los datos de entrada, casos más favorables, caso peor,hipótesis de distribución probabilística para los contenidos de los datos deentrada, etc.) hasta el análisis asintótico simplificado aplicando reglasprácticas.En estos apuntes se seguirá el criterio de análisis asintótico simplificado, sibien nunca se ha de dejar de aplicar el sentido común. Como en todos losmodelos simplificados se ha mantener la alerta para no establecer hipótesissimplificadoras que no se correspondan con la realidad.En lo que sigue se realizará una exposición basada en el criterio de exponeren un apartado inicial una serie de reglas y planteamientos básicosaplicables al análisis de eficiencia en algoritmos iterativos, en un segundoapartado se presenta una lista de ejemplos que permitan comprenderalgunas de las aproximaciones y técnicas expuestas.3.7.2 Cálculo de eficiencia en análisis recursivoRetomando aquí el socorrido ejemplo del factorial, tratemos de analizar elcoste de dicho algoritmo, en su versión iterativa , se tiene:PROCEDURE Factorial(n : CARDINAL) : CARDINALBEGINVAR Resultado,i : CARDINAL ;Resultado :=1 ;FOR i :=1 TO n DOResultado :=Resultado*i;
Página 30END ;RETURN ResultadoEND Factorial ;Aplicando las técnicas de análisis de coste en algoritmos iterativos de formarápida y mentalmente (es como se han de llegar a analizar algoritmos tansimples como éste), se tiene: hay una inicialización antes de bucle, de costeconstante. El bucle se repite un número de veces n y en su interior se realizauna operación de coste constante. Por tanto el algoritmo es de coste lineal oexpresado con algo más de detalle y rigor, si la función de coste delalgoritmo se expresa por T(n), se tiene que T(n) ? T.Una versión recursiva del mismo algoritmo, es:PROCEDURE Factorial(n: CARDINAL): CARDINAL;BEGINIF n=0 THENRETURN 1ELSERETURN ( n* Factorial(n-1) )ENDEND Factorial;Al aplicar el análisis de coste aprendido para análisis de algoritmos iterativosse tiene: hay una instrucción de alternativa, en una de las alternativassimplemente se devuelve un valor (operación de coste constante). En la otraalternativa se realiza una operación de coste constante (multiplicación) condos operandos. El primer operando se obtiene por una operación de costeconstante (acceso a la variable n), el coste de la operación que permiteobtener el segundo operando es el coste que estamos calculando!, es decir esel coste de la propia función factorial (solo que para parámetro n-1).Por tanto, para conocer el orden de la función de coste de este algoritmo¿debemos conocer previamente el orden de la función de coste de estealgoritmo?, entramos en una recurrencia.Y efectivamente, el asunto está en saber resolver recurrencias. Si T(n) es lafunción de coste de este algoritmo se puede decir que T(n) es igual a unaoperación de coste constante c cuando n vale 0 y a una operación de costeT(n-1) más una operación de coste constante (el acceso a n y lamultiplicación) cuando n es mayor que 0, es decir:c si n = 0T(n) =T(n-1) + c si n > 0Se trata entonces de encontrar soluciones a recurrencias como ésta.Entendiendo por solución una función simple f(n) tal que se pueda asegurarque T(n) es del orden de f(n).
Página 31En este ejemplo puede comprobarse que T(n) es de orden lineal, es decir delorden de la función f(n)=n, ya que cualquier función lineal T(n)= an+bsiendo a y b constantes, es solución de la recurrencia:T(0)= b , es decir una constanteT(n)= a.n+b= an-a+ b+a= a(n-1)+b+a= T(n-1)+a, es decir elcoste de T(n) es igual al de T(n-1) más una constante.3.8 Principio de InvarianzaDado un algoritmo y dos implementaciones I1 y I2 (máquinas distintas ocódigos distintos) que tardan T1(n) y T2(n) respectivamente, el principio deinvarianza afirma que existe una constante real c>0 y un número natural n0tales que para todo n>=n0 se verifica que T1(n)<=c·T2(n).Es decir, el tiempo de ejecución de dos implementaciones distintas de unalgoritmo dado no va a diferir más que en una constante multiplicativa.Asumimos que un algoritmo tarda un tiempo del orden de T(n) si existenuna constante real c>0 y una implementación I del algoritmo que tardamenos que c·T(n), para todo n tamaño de entrada.El comportamiento de un algoritmo puede variar notablemente paradiferentes secuencias de entrada. Suelen estudiarse tres casos para unmismo algoritmo: caso mejor, caso peor, caso medio.3.9 Análisis Peor Caso, Mejor Caso y Caso PromedioPuede analizarse un algoritmo particular o una clase de ellos. Una clase dealgoritmo para un problema son aquellos algoritmos que se puedenclasificar por el tipo de operación fundamental que realizan.Ejemplo:Problema: OrdenamientoClase: Ordenamiento por comparaciónPara algunos algoritmos, diferentes entradas (inputs) para un tamaño dadopueden requerir diferentes cantidades de tiempo.Por ejemplo, consideremos el problema de encontrar la posición particularde un valor K, dentro de un arreglo de n elementos. Suponiendo que sóloocurre una vez. Comentar sobre el mejor, peor y caso promedio.¿Cuál es la ventaja de analizar cada caso? Si examinamos el peor de loscasos, sabemos que al menos el algoritmo se desempeñará de esa forma.
Página 32En cambio, cuando un algoritmo se ejecuta muchas veces en muchos tiposde entrada, estamos interesados en el comportamiento promedio o típico.Desafortunadamente, esto supone que sabemos cómo están distribuidos losdatos.Si conocemos la distribución de los datos, podemos sacar provecho de esto,para un mejor análisis y diseño del algoritmo. Por otra parte, sinoconocemos la distribución, entonces lo mejor es considerar el peor de loscasos.Tipos de análisis:o Peor caso: indica el mayor tiempo obtenido, teniendo enconsideración todas las entradas pos ibles.o Mejor caso: indica el menor tiempo obtenido, teniendo enconsideración todas las entradas posibles.o Media: indica el tiempo medio obtenido, considerando todas lasentradas posibles.Como no se puede analizar el comportamiento sobre todas las entradas posibles,va a existir para cada problema particular un análisis en él:- peor caso- mejor caso- caso promedio (o medio)El caso promedio es la medida más realista de la performance, pero es másdifícil de calcular pues establece que todas las entradas son igualmenteprobables, lo cual puede ser cierto o no. Trabajaremos específicamente conel peor caso.
Página 33EjemploSea A una lista de n elementos A1, A2 , A3, ... , An. Ordenar significa permutarestos elementos de tal forma que los mismos queden de acuerdo con unorden preestablecido.Ascendente A1<=A2 <=A3 ..........<=AnDescendente A1 >=A2 >=........>=AnCaso peor: Que el vector esté ordenado en sentido inverso.Caso mejor: Que el vector esté ordenado.Caso medio: Cuando el vector esté desordenado aleatoriamente.
Página 34Capítulo 4Análisis de Algoritmos4.1 IntroducciónComo hemos visto, existen muchos enfoques para resolver un problema.¿Cómo escogemos entre ellos? Generalmente hay dos metas en el diseño deprogramas de cómputo:• El diseño de un algoritmo que sea fácil de entender, codificar ydepurar (Ingeniería de Software).• El diseño de un algoritmo que haga uso eficiente de los recursos de lacomputadora (Análisis y Diseño de algoritmos).El análisis de algoritmos nos permite medir la dificultad inherente deun problema y evaluar la eficiencia de un algoritmo.4.2 Tiempos de EjecuciónUna medida que suele ser útil conocer es el tiempo de ejecución de unalgoritmo en función de N, lo que denominaremos T(N). Esta función sepuede medir físicamente (ejecutando el programa, reloj en mano), ocalcularse sobre el código contando instrucciones a ejecutar y multiplicandopor el tiempo requerido por cada instrucción.Así, un trozo sencillo de programa como:S1; FOR i:= 1 TO N DO S2 END;requiere:T(N):= t1 + t2*Nsiendo t1 el tiempo que lleve ejecutar la serie "S1" de sentencias, y t2 el quelleve la serie "S2".Prácticamente todos los programas reales incluyen alguna sentenciacondicional, haciendo que las sentencias efectivamente ejecutadasdependan de los datos concretos que se le presenten. Esto hace que más queun valor T(N) debamos hablar de un rango de valores:Tmin(N) <= T(N) <= Tmax(N)
Página 35los extremos son habitualmente conocidos como "caso peor" y "caso mejor".Entre ambos se hallará algún "caso promedio" o más frecuente. Cualquierfórmula T(N) incluye referencias al parámetro N y a una serie de constantes"Ti" que dependen de factores externos al algoritmo como pueden ser lacalidad del código generado por el compilador y la velocidad de ejecución deinstrucciones del computador que lo ejecuta.Dado que es fácil cambiar de compilador y que la potencia de loscomputadores crece a un ritmo vertiginoso (en la actualidad, se duplicaanualmente), intentaremos analizar los algoritmos con algún nivel deindependencia de estos factores; es decir, buscaremos estimacionesgenerales ampliamente válidas.No se puede medir el tiempo en segundos porque no existe un computadorestándar de referencia, en su lugar medimos el número de operacionesbásicas o elementales.Las operaciones básicas son las que realiza el computador en tiempo acotadopor una constante, por ejemplo:• Operaciones aritméticas básicas• Asignaciones de tipos predefinidos• Saltos (llamadas a funciones, procedimientos y retorno)• Comparaciones lógicas• Acceso a estructuras indexadas básicas (vectores y matrices)Es posible realizar el estudio de la complejidad de un algoritmo sólo en basea un conjunto reducido de sentencias, por ejemplo, las que más influyen enel tiempo de ejecución.Para medir el tiempo de ejecución de un algoritmo existen varios métodos.Veamos algunos de ellos:a) BenchmarkingLa técnica de benchmark considera una colección de entradas típicasrepresentativas de una carga de trabajo para un programa.b) ProfilingConsiste en asociar a cada instrucción de un programa un número querepresenta la fracción del tiempo total tomada para ejecutar esainstrucción particular. Una de las técnicas más conocidas (e informal) esla Regla 90-10, que afirma que el 90% del tiempo de ejecución seinvierte en el 10% del código.
Página 36c) AnálisisConsiste en agrupar las entradas de acuerdo a su tamaño, y estimar eltiempo de ejecución del programa en entradas de ese tamaño, T(n). Estaes la técnica que se estudiará en el curso.De este modo, el tiempo de ejecución puede ser definido como unafunción de la entrada. Denotaremos T(n) como el tiempo de ejecución de unalgoritmo para una entrada de tamaño n.4.3 Concepto de ComplejidadLa complejidad (o costo) de un algoritmo es una medida de la cantidad derecursos (tiempo, memoria) que el algoritmo necesita. La complejidad de unalgoritmo se expresa en función del tamaño (o talla) del problema.La función de complejidad tiene como variable independiente el tamaño delproblema y sirve para medir la complejidad (espacial o temporal). Mide eltiempo/espacio relativo en función del tamaño del problema.El comportamiento de la función determina la eficiencia. No es única paraun algoritmo: depende de los datos. Para un mismo tamaño del problema,las distintas presentaciones iniciales de los datos dan lugar a distintasfunciones de complejidad. Es el caso de una ordenación si los datos estántodos inicialmente desordenados, parcialmente ordenados o en ordeninverso.Ejemplo: f(n)= 3n2+2n+1 , en donde n es el tamaño del problema y expresael tiempo en unidades de tiempo.¿Una computadora más rápida o un algoritmo más rápido?Si compramos una computadora diez veces más rápida, ¿en qué tiempopodremos ahora ejecutar un algoritmo?La respuesta depende del tamaño de la entrada de datos, así como de larazón de crecimiento del algoritmo.Si la razón de crecimiento es lineal (es decir, T(n)=cn) entonces porejemplo, 100.000 números serán procesados en la nueva máquina en elmismo tiempo que 10.000 números en la antigua computadora.¿De qué tamaño (valor de n) es el problema que podemos resolver con unacomputadora X veces más rápida (en un intervalo de tiempo fijo)?Por ejemplo, supongamos que una computadora resuelve un problema detamaño n en una hora. Ahora supongamos que tenemos una computadora
Página 3710 veces más rápida, ¿de qué tamaño es el problema que podemosresolver?f(n) n n´ cambio n´/n10n 1000 10000 n´=10n 1020n 500 5000 n´=10n 105n log n 250 1842 √(10)n<n´<10n7.372n2 70 223 n´=√(10)n 3.162n 13 16 n´=n+3 ----En la tabla de arriba, f(n) es la razón de crecimientode un algoritmo.Supongamos que tenemos una computadora que puede ejecutar 10.000operaciones básicas en una hora. La segunda columna muestra el máximovalor de n que puede ejecutarse con 10.000 operaciones básicas en unahora. Es decir f(n)=”total de operaciones básicas en un intervalo de tiempo”.Por ejemplo, f(n)=10.000 operaciones básicas por hora.Si suponemos que tenemos una computadora 10 veces más rápida, entoncespodremos ejecutar 100.000 operaciones básicas en una hora. Por lo cualf(n’)=100.000 operaciones básicas por hora.Ejercicio: Comentar sobre la razón n´/n según el incremento de velocidadde la computadora y la razón de crecimiento del algoritmo en cuestión.Nota: los factores constantes nunca afectan la mejora relativa obtenida poruna computadora más rápida.4.4 Órdenes de ComplejidadSe dice que O(f(n)) define un "orden de complejidad". Escogeremoscomo representante de este orden a la función f(n) más sencilla del mismo.Así tendremos:O(1) orden constanteO(log n) orden logarítmicoO(n) orden linealO(n2 ) orden cuadráticoO(na) orden polinomial (a > 2)O(an) orden exponencial (a > 2)O(n!) orden factorialEs más, se puede identificar una jerarquía de órdenes de complejidad quecoincide con el orden de la tabla anterior; jerarquía en el sentido de quecada orden de complejidad superior tiene a los inferiores como
Página 38subconjuntos. Si un algoritmo A se puede demostrar de un cierto orden O1 ,es cierto que también pertenece a todos los órdenes superiores (la relaciónde orden cota superior es transitiva); pero en la práctica lo útil es encontrarla "menor cota superior", es decir el menor orden de complejidad que locubra.Antes de realizar un programa conviene elegir un buen algoritmo, dondepor bueno entendemos que utilice pocos recursos, siendo usualmente losmás importantes el tiempo que lleve ejecutarse y la cantidad de espacio enmemoria que requiera. Es engañoso pensar que todos los algoritmos son"más o menos iguales" y confiar en nuestra habilidad como programadorespara convertir un mal algoritmo en un producto eficaz. Es asimismoengañoso confiar en la creciente potencia de las máquinas y elabaratamiento de las mismas como remedio de todos los problemas quepuedan aparecer.Un ejemplo de algunas de las funciones más comunes en análisis dealgoritmos son:La mejor técnica para diferenciar la eficiencia de los algoritmos es el estudiode los órdenes de complejidad. El orden de complejidad se expresageneralmente en términos de la cantidad de datos procesados por elprograma, denominada n, que puede ser el tamaño dado o estimado.En el análisis de algoritmos se considera usualmente el caso peor, si bien aveces conviene analizar igualmente el caso mejor y hacer alguna estimaciónsobre un caso promedio. Para independizarse de factores coyunturales, talescomo el lenguaje de programación, la habilidad del codificador, la máquinade soporte, etc. se suele trabajar con un cálculo asintótico que indicacómo se comporta el algoritmo para datos muy grandes y salvo algúncoeficiente multiplicativo. Para problemas pequeños es cierto que casi todoslos algoritmos son "más o menos iguales", primando otros aspectos comoesfuerzo de codificación, legibilidad, etc. Los órdenes de complejidad sóloson importantes para grandes problemas.
Página 394.5 Notación AsintóticaEl interés principal del análisis de algoritmos radica en saber cómo crece eltiempo de ejecución, cuando el tamaño de la entrada crece. Esto es laeficiencia asintóticadel algoritmo. Se denomina “asintótica” porque analizael comportamiento de las funciones en el límite, es decir, su tasa decrecimiento.La notación asintótica se describe por medio de una función cuyo dominioes los números naturales (Ν ) estimado a partir de tiempo de ejecución o deespacio de memoria de algoritmos en base a la longitud de la entrada. Seconsideran las funciones asintóticamente no negativas.La notación asintótica captura el comportamiento de la función paravalores grandes de N.Las notaciones no son dependientes de los tres casos anteriormente vistos,es por eso que una notación que determine el peor caso puede estarpresente en una o en todas las situaciones.4.5.1 La O MayúsculaLa notación O se utiliza para comparar funciones. Resulta particularmenteútil cuando se quiere analizar la complejidad de un algoritmo, en otraspalabras, la cantidad de tiempo que le toma a un computador ejecutar unprograma.Definición: Sean f y g funciones con dominio en R ≤ 0 o N es imagen enR. Si existen constantes C y k tales que:∀ x > k, | f (x) | ≤ C | g (x) |es decir, que para x > k, f es menor o igual a un multiplo de g, decimos que:f (x) = O ( g (x) )La definición formal es:f (x) = O ( g (x) ) ∃ k , N | ∀ x > N, | f (x) | ≤ k| g (x) |¿Qué quiere decir todo esto? Básicamente, que una función es siempremenor que otra función (por ejemplo, el tiempo en ejecutar tu programa esmenor que x2 ) si no tenemos en cuenta los factores constantes (eso es lo quesignifica la k) y si no tenemos en cuenta los valores pequeños (eso es lo quesignifica la N).
Página 40¿Por qué no tenemos en cuenta los valores pequeños de N? Porqué paraentradas pequeñas, el tiempo que tarda el programa no es significativo ycasi siempre el algoritmo será suficientemente rápido para lo que queremos.Así 3N3 + 5N2 – 9 = O (N3) no significa que existe una función O(N3) quees igual a 3N 3 + 5N 2 – 9.Debe leerse como:“3N 3+5N 2 –9 es O-Grande de N3”que significa:“3N 3+5N 2 –9 está asintóticamente dominada por N3”La notación puede resultar un poco confusa. Veamos algunos ejemplos.Ejemplo 1: Muestre que 3N 3 + 5N 2 – 9 = O (N 3).De nuestra experiencia anterior pareciera tener sentido que C = 5.Encontremos k tal que:3N 3 + 5N 2 – 9 ≤ 5N 3 for N > k :1. Agrupamos: 5N2 = 2N 3 + 92. Cuál k asegura que 5N2 = N3 para N > k?3. k = 54. Así que para N > 5, 5N2 = N3 = 2N3 + 9.5. C = 5, k = 5 (no es único!)Ejemplo 2: Muestre que N 4≠ O (3N3 + 5N2 – 9) .Hay que mostrar que no existen C y k tales que para N > k, C* (3N3 + 5N2 –9) ≥ N4 es siempre cierto (usamos límites, recordando el curso de Cálculo).lim x4 / C(3x3 + 5 x2 – 9) = lim x / C(3 + 5/x – 9/x3)x à ∞ x à ∞= lim x / C(3 + 0 – 0) = (1/3C) . lim x = ∞x à ∞ x à ∞
Página 41Así que sin importar cual sea el C que se elija, N4 siempre alcanzará yrebasará C* (3N3 + 5N2 – 9).Podemos considerarnos afortunados porque sabemos calcular límites!Límites serán útiles para probar relaciones (como veremos en el próximoteorema).Lema: Si el límite cuando x à ∞ del cociente |f (x)/g (x)| existe (es finito)entonces f (x) = O ( g (x) ).Ejemplo 3: 3N3 + 5N2 – 9 = O (N3 ). Calculamos:lim x3 / (3x3 + 5 x2 – 9) = lim 1 /(3 + 5/x – 9/x3) = 1 /3x à ∞ x à ∞Listo!4.5.2 La o MinúsculaLa cota superior asintótica dada por la notación O puede o no ser ajustadaasintóticamente. La cota 2n² = O(n²) es ajustada asintóticamente, pero lacota 2n = O(n²) no lo es. Utilizaremos la notación o para denotar una cotasuperior que no es ajustada asintóticamente.Definimos formalmente o(g(n)) ("o pequeña") como el conjunto:o(g(n)) = {f(n): para cualquier constante positiva c > 0, existe una constanten0 > 0 tal que: 0 ≤ f(n) ≤ cg(n) para toda n ≥ n0 }.Por ejemplo, 2n = o(n²), pero 2n² no pertenece a o(n²).Las notaciones de O y o son similares. La diferencia principal es, que en f(n)= O(g(n)), la cota 0 ≤ f(n) ≤ cg(n) se cumple para alguna constante c > 0,pero en f(n) = o(g(n)), la cota 0 ≤ f(n) ≤ cg(n) se cumple para todas lasconstantes c> 0. Intuitivamente en la notación o, la función f(n) se vuelveinsignificante con respecto a g(n) a medida que n se acerca a infinito, esdecir:lim (f(n)/g(n)) = 0.x à ∞
Página 424.5.3 Diferencia entre O y oPara o la desigualdad se mantiene para todas las constantes positivas,mientras que para O la desigualdad se mantiene sólo para algunasconstantes positivas.4.5.4 Las Notaciones Ω y ΘΩ Es el reverso de O.f (x ) = Ω(g (x )) àß g (x ) = O (f (x ))Ω Grande dice que asintóticamente f (x ) domina a g (x ).Θ Grande dice que ambas funciones se dominan mutuamente, en otraspalabras, son asintóticamente equivalentes.f (x ) = Θ(g (x ))àßf (x ) = O (g (x ))∧ f (x ) =Ω(g (x ))f = Θ(g): “f es de ordeng ”4.5.5 Propiedades y Cotas más UsualesPropiedades de las notaciones asintóticasRelaciones de ordenLa notación asintótica nos permite definir relaciones de orden entre elconjunto de las funciones sobre los enteros positivos:• f ∈ O (g (x )) ↔ f ≤ g (se dice que f es asintóticamente menor o igual que g)• f ∈ o (g (x )) ↔ f < g• f ∈ Θ (g (x )) ↔ f = g• f ∈ Ω (g (x )) ↔ f ≥ g• f ∈ ω (g (x )) ↔ f > g
Página 43Relaciones entre cotas1. f(n) ∈ O(f (n))2. f(n) ∈ O(f (n)) ∧ g(n) ∈ O(f (n )) ⇒ f(n) ∈ O(h (n ))3. f(n) ∈ O(g (n)) ⇒ g(n) ∈ Ω(h(n ))4. lim f(n)/g(n) = 0 ⇒ O(f (n)) ⊂ O(g (n))n à ∞5. O(f (n)) = O(g (n)) ⇔ f(n) ∈ O(g (n)) ∧ g(n) ∈ O(f (n ))6. O(f (n)) ⊂ O(g (n)) ⇔ f(n) ∈ O(g (n))∧ g(n) ∉ O(f (n ))7. Ω(f(n )) ⊂ Ω(g (n)) ⇔ f(n) ∈ Ω(g (n))∧ g(n) ∉ Ω(f (n ))Relaciones de inclusiónSon muy útiles a la hora de comparar funciones de coste en el casoasintótico.∀ x, y, a, ε ∈ R >01. loga n ∈ O(logbn)2. O(loga x n) ⊂ O(logax+δn)3. O(loga x n) ⊂ O(n)4. O(nx) ⊂ O(nx+δ)5. O(nx logay n) ⊂ O(nx+δ)6. O(nx) ⊂ O(2n )
Página 44Propiedades de clausuraEl conjunto de funciones del orden de f(n) es cerrado respecto de la suma yla multiplicación de funciones:1. f1(n) ∈ O(g1(n)) ∧ f2(n) ∈ O(g2(n)) ⇒ f1(n) + f2(n) ∈ O(g1(n) + g2(n))2. f1(n) ∈ O(g1(n)) ∧ f2(n) ∈ O(g2(n)) ⇒ f1(n) * f2(n) ∈ O(g1(n) * g2(n))3. f1(n) ∈ O(g1(n)) ∧ f2(n) ∈ O(g2(n)) ⇒ f1(n)+f2(n) ∈ O(max(g1(n), g2(n))como consecuencia es que los polinomios de grado k en n son exactamentedel orden de nkaknk + … + a1n + a0 ∈ Θ(nk)Cotas Asintóticas Más Usuales1. c ∈ Θ(1) ∀ c ∈ R ≥ 0n2. ∑ i = (n/2)(n+1) ∈ Θ(n2 )i=1n3. ∑ i2 = (n/3)(n+1) (n+1/2) ∈ Θ(n3)i=1n4. ∑ ik ∈ Θ(nk+1) ∀ k ∈ Ni=1n5. ∑ log i ∈ Θ(n log n)i=1n6. ∑ (n- i)k ∈ Θ(nk+1) ) ∀ k ∈ Ni=1
Página 454.6 Ecuaciones de Recurrencias4.6.1 IntroducciónComo ya hemos indicado, el análisis del tiempo de ejecución de unprograma recursivo vendrá en función del tiempo requerido por la(s)llamada(s) recursiva(s) que aparezcan en él. De la misma manera que laverificación de programas recursivos requiere de razonar por inducción,para tratar apropiadamente estas llamadas, el cálculo de su eficienciarequiere un concepto análogo: el de ecuación de recurrencia.Demostraciones por inducción y ecuaciones de recurrencia son por tanto losconceptos básicos para tratar programas recursivos.Supongamos que se está analizando el coste de un algoritmo recursivo,intentando calcular una función que indique el uso de recursos, por ejemploel tiempo, en términos del tamaño de los datos; denominémosla T(n) paradatos de tamaño n. Nos encontramos con que este coste se define a su vez enfunción del coste de las llamadas recursivas, es decir, de T(m) para otrostamaños m (usualmente menores que n). Esta manera de expresar el coste Ten función de sí misma es lo que denominaremos una ecuación recurrente ysu resolución, es decir, la obtención para T de una fórmula cerrada(independiente de T) puede ser dificultosa.4.6.2 Resolución de RecurrenciasLas ecuaciones de recurrencia son utilizadas para determinar cotasasintóticas en algoritmos que presentan recursividad.Veremos dos técnicas básicas y una auxiliar que se aplican a diferentesclases de recurrencias:• Método del teorema maestro• Método de la ecuación característica• Cambio de variableNo analizaremos su demostración formal, sólo consideraremos suaplicación para las recurrencias generadas a partir del análisis dealgoritmos.4.6.3 Método del Teorema MaestroSe aplica en casos como:5 si n=0T(n) =9T(n/3) + n si n ≠ 0
Página 46Teorema: Sean a = 1, b > 1 constantes, f(n) una función y T(n) unarecurrencia definida sobre los enteros no negativos de la forma T(n) =aT(n/b) + f(n), donde n/b puede interpretarse como n/b o n/b.Entonces valen:1. Si f(n) ∈ O(mlogba - ε) para algún ε > 0 entonces T(n) ∈ Θ(nlogba).2. Si f(n) ∈ Θ(nlogba) entonces T(n) ∈ Θ(nlogba lgn).3. Si f(n) ∈ Ω(nlogba + ε) para algún ε > 0, y satisface af(n/b) ≤ cf(n) paraalguna constante c < 1, entonces T(n) ∈ Θ(f(n)).Ejemplos:1. Si T(n)=9T(n/3)+n entonces a=9, b=3, se aplica el caso 1 con ε=1 yT(n) ∈ Θ(n2 ).2. Si T(n)=T(2n/3)+1 entonces a=1, b=3/2, se aplica el caso 2 y T(n) =Θ(lgn).3. Si T(n)=3T(n/4)+nlgn entonces a=3, b=4, f(n) ∈ Ω(nlog43 + 0,2 ) y3(n/4) lg(n/4) ≤ 3/4n lgn, por lo que se aplica el caso 3 y T(n) ∈ Θ(nlgn)4.6.4 Método de la Ecuación CaracterísticaSe aplica a ciertas recurrencias lineales con coeficientes constantes como:5 si n=0T(n) = 10 si n=15T(n-1) + 8T(n-2)+2n si n > 0En general, para recurrencias de la forma:T(n) = a1T(n-1) + a2T(n-2) + … + akT(n-k) + bnp(n)donde ai, 1 ≤ i ≤ k, b son constantes y p(n) es un polinomio en n de grado s.Ejemplos:1. En T(n)=2t(n-1)+3n, a1 =2, b=3, p(n)=1, s=0.2. En T(n)=t(n-1)+ t(n-2)+n, a1=1, a2 =1, b=1, p(n)=n, s=1.En general, para:T(n) = a1T(n-1) + a2T(n-2) + … + akT(n-k) + bnp(n)
Página 47♦ Paso 1: Encontrar las raíces no nulas de la ecuación característica:(xk - a1xk-1 - a2xk-2 - … - ak)(x-b)s+1 = 0Raíces: ri, 1 ≤ i ≤ l ≤ k, cada una con multiplicidad mi.♦ Paso 2: Las soluciones son de la forma de combinaciones lineales deestas raíces de acuerdo a su multiplicidad.T(n) = -----♦ Paso 3: Se encuentran valores para las constantes cij, tal que:1 ≤ i ≤ l, 0 ≤ j ≤ mi - 1y di, 0 ≤ i ≤ s – 1 según la recurrencia original y las condicionesiniciales (valores de la recurrencia para n=0,1, …).Ejemplo: Resolver la ecuación de recurrencia siguiente:0 si n=0T(n) =2T(n-1) + 1 si n > 0donde b=1 y p(n)=1 de grado 0.La ecuación característica (x-2)(x-1)0+1 = 0, con raíces 2 y 1 demultiplicidad 1.La solución general es entonces de la forma: T(n)=c112n +c211n.A partir de las condiciones iniciales se encuentra el siguiente sistema deecuaciones que sirve para hallar c11 yc21:c11 +c21 = 0 de n = 02c11 +c21 = 1 de n = 1de donde c11 = 1 y c21 =-1.La solución es entonces: T(n) = 2n –1.
Página 484.6.5 Cambio de VariableDada la siguiente ecuación de recurrencia:a si n=1T(n) =2T(n/2) + nlog2 n si n = 2kNo se puede aplicar el teorema maestro ni la ecuación característica.Se define una nueva recurrencia S(i) = T(2i), con el objetivo de llevarla a unaforma en la que se pueda resolver siguiendo algún método anterior.Entonces el caso general queda:S(i) = T(2i) = 2T(2i /2) + 2ii = 2T(2i-1 ) + i2i = 2S(i-1) + i2iCon b=2 y p(i) = i de grado 1.La ecuación característica de esta recurrencia es:(x - 2)(x - 2)i+1 = 0,con raíz 2 de grado 3.La solución es entonces S(i) = c112i + c12 i2i + c13i22i, con lo que volviendo a lavariable original queda:T(n) - 2T(n/2) = nlog2 n = (c12 - c13)n + 2c13n(log2n).Se pueden obtener los valores de las constantes sustituyendo esta soluciónen la recurrencia original:T(n) = c11n + c12(log2n)n + c13(log2n)2n.de donde c12 =c13 y 2c12 = 1.Por tanto T(n) ∈ Θ(nlog2 n | n es potencia de2).Si se puede probar que T(n) es eventualmente no decreciente, por la reglade las funciones de crecimiento suave se puede extender el resultadoa todos los n (dado que nlog2n es de crecimiento suave).En este caso T(n) ∈ Θ(nlog2n).
Página 494.7 Ejemplos y EjerciciosEjemplos de cálculo del tiempo de ejecución de un programaVeamos el análisis de un simple enunciado de asignación a una variableentera:a = b;Como el enunciado de asignación toma tiempo constante, está en Θ(1).Consideremos un simple ciclo “for”:sum=0;for (i=1; i<=n; i++)sum += n;La primera línea es Θ(1). El ciclo “for” es repetido n veces. La tercera líneatoma un tiempo constante también, el costo total por ejecutar las dos líneasque forman el ciclo “for” es Θ(n). El costo por el entero fragmento de códigoes también Θ(n).Analicemos un fragmento de código con varios ciclos “for”, algunos de loscuales están anidados.sum=0;for (j=1; j<=n; j++)for (i=1; i<=j; i++)sum++;for (k=1; k<=n; k++)A[k]= k-1;Este código tiene tres partes: una asignación, y dos ciclos.La asignación toma tiempo constante, llamémosla c1. El segundo ciclo essimilar al ejemplo anterior y toma c2n= Θ(n).Analicemos ahora el primer ciclo, que es un doble ciclo anidado. En estecaso trabajemos de adentro hacia fuera. La expresión sum++ requieretiempo constante, llamemosle c3.Como el ciclo interno es ejecutado j veces, tiene un costo de c3j. El cicloexterior es ejecutado n veces, pero cada vez el costo del ciclo interior esdiferente.El costo total del ciclo es c3 veces la suma de los números 1 a n, es decir Σni=1 j= n(n+1)/2, que es Θ(n2). Por tanto, Θ(c1 +c2n+c3n2) es simplemente Θ(n2 ).
Página 50Comparemos el análisis asintótico de los siguientes fragmentos de código:sum1=0;for(i=1; i<=n; i++)for(j=1; j<=n; j++)sum1++;sum2=0;for(i=1; i<=n; i++)for(j=1; j<=i; j++)sum2++;El primer fragmento de ejecuta el enunciado sum1++, precisamente n2veces.Por otra parte, el segundo fragmento de código tiene un costo aproximadode ½ n2 . Así ambos códigos tienen un costo de Θ(n2).Ejemplo, no todos los ciclos anidados son Θ(n2):sum1=0;for(k=1; k<=n; k*=2)for(j=1; j<=n; j++)sum1++;El costo es Θ(n log n).
Página 51Capítulo 5Estrategias de Diseño deAlgoritmos5.1 IntroducciónA través de los años, los científicos de la computación han identificadodiversas técnicas generales que a menudo producen algoritmos eficientespara la resolución de muchas clases de problemas. Este capítulo presentaalgunas de las técnicas más importantes como son: recursión, dividir paraconquistar, técnicas ávidas, el método de retroceso y programacióndinámica.Se debe, sin embargo, destacar que hay algunos problemas, como los NPcompletos, para los cuales ni éstas ni otras técnicas conocidas produciránsoluciones eficientes. Cuando se encuentra algún problema de este tipo,suele ser útil determinar si las entradas al problema tienen característicasespeciales que se puedan explotar en la búsqueda de una solución, o si puedeusarse alguna solución aproximada sencilla, en vez de la solución exacta,difícil de calcular.5.2 RecursiónLa recursividad es una técnica fundamental en el diseño de algoritmoseficientes, que está basada en la solución de versiones más pequeñas delproblema, para obtener la solución general del mismo. Una instancia delproblema se soluciona según la solución de una o más instancias diferentesy más pequeñas que ella.Es una herramienta poderosa que sirve para resolver cierto tipo deproblemas reduciendo la complejidad y ocultando los detalles del problema.Esta herramienta consiste en que una función o procedimiento se llama a símismo.Una gran cantidad de algoritmos pueden ser descritos con mayor claridad entérminos de recursividad, típicamente el resultado será que sus programasserán más pequeños.La recursividad es una alternativa a la iteración o repetición, y aunque entiempo de computadora y en ocupación en memoria es la solución recursivamenos eficiente que la solución iterativa, existen numerosas situaciones enlas que la recursividad es una solución simple y natural a un problema queen caso contrario ser difícil de resolver. Por esta razón se puede decir que la
Página 52recursividad es una herramienta potente y útil en la resolución de problemasque tengan naturaleza recursiva y, por ende, en la programación.Existen numerosas definiciones de recursividad, algunas de las másimportantes o sencillas son éstas:• Un objeto es recursivo si figura en su propia definición.• Una definición recursiva es aquella en la que el objeto que se defineforma parte de la definición (recuerde la regla gramatical: lo definidonunca debe formar parte de la definición)La característica importante de la recursividad es que siempre existe unmedio de salir de la definición, mediante la cual se termina el procesorecursivo.Ventajas:• Puede resolver problemas complejos.• Solución más natural.Desventajas:• Se puede llegar a un ciclo infinito.• Versión no recursiva más difícil de desarrollar.• Para la gente sin experiencia es difícil de programar.Tipos de Recursividad• Directa o simple: un subprograma se llama a si mismo una o másveces directamente.
Página 53• Indirecta o mutua: un subprograma A llama a otro subprograma By éste a su vez llama al subprograma A.a) Propiedades para un Procedimiento Recursivo• Debe de existir cierto criterio (criterio base) en donde elprocedimiento no se llama a sí mismo. Debe existir al menos unasolución no recursiva.• En cada llamada se debe de acercar al criterio base (reducir rango). Elproblema debe reducirse en tamaño, expresando el nuevo problemaen términos del propio problema, pero más pequeño.Los algoritmos de divide y vencerás pueden ser procedimientosrecursivos.b) Propiedades para una Función Recursiva• Debe de haber ciertos argumentos, llamados valores base para los quela función no se refiera a sí misma.• Cada vez que la función se refiera así misma el argumento de lafunción debe de acercarse más al valor base.Ejemplos:Factorialn! = 1 * 2 * 3 ... * (n-2) * (n-1) * n0! = 11! = 12! = 1 * 23! = 1 * 2 * 3...n! = 1 * 2 * 3 ... * (n-2) * (n-1) * n⇒ n! = n* (n-1)!
Página 54si n = 0 entonces n! = 1si n > 0 entonces n! = n * (n-1)!Function factorial(n:integer):longint:beginif ( n = 0 ) thenfactorial:=1elsefactorial:= n * factorial(n-1);end;Rastreo de ejecución:Si n = 6Nivel1) Factorial(6)= 6 * factorial(5) = 7202) Factorial(5)=5 * factorial(4) = 1203) Factorial(4)=4 * factorial(3) = 244) Factorial(3)=3 * factorial(2) = 65) Factorial(2)=2 * factorial(1) = 26) Factorial(1)=1 * factorial(0) = 17) Factorial(0)=1La profundidad de un procedimiento recursivo es el número de veces que sellama a sí mismo.Serie de Fibonacci0, 1, 1, 2, 3, 5, 8, ...F0 = 0F1 = 1F2 = F1 + F0 = 1F3 = F2 + F1 = 2F4 = F3 + F2 = 3...Fn = Fn-1 + Fn-2si n= 0, 1 Fn = nsi n >1 Fn = Fn-1 + Fn-2
Página 55Procedure fibo(var fib:longint; n:integer);varfibA, fibB : integer;beginif ( n = 0) or ( n = 1) then fib:=nelse beginfibo(fibA,n-1);fibo(fibB,n-2);fib:=fibA + FibB;end;end;function fibo ( n:integer) : integer;beginif ( n = 0) or ( n = 1) thenfibo:=nelsefibo:=fibo(n-1) + fibo(n-2);end;end;No todos los ambientes de programación proveen las facilidades necesariaspara la recursión y adicionalmente, a veces su uso es una fuente deineficiencia inaceptable. Por lo cual se prefiere eliminar la recursión. Paraello, se sustituye la llamada recursiva por un lazo, donde se incluyen algunasasignaciones para recolocar los parámetros dirigidos a la función.Remover la recursión es complicado cuando existe más de una llamadarecursiva, pero una vez hecha ésta, la versión del algoritmo es siempre máseficiente.5.3 Dividir para ConquistarMuchos algoritmos útiles tienen una estructura recursiva, de modo que pararesolver un problema se llaman recursivamente a sí mismos una o más vecespara solucionar subproblemas muy similares.Esta estructura obedece a una estrategia dividir-y-conquistar, en que seejecuta tres pasos en cada nivel de la recursión:• Dividir. Dividen el problema en varios subproblemas similares alproblema original pero de menor tamaño;• Conquistar. Resuelven recursivamente los subproblemas si lostamaños de los subproblemas son suficientemente pequeños, entoncesresuelven los subproblemas de manera directa; y luego,
Página 56• Combinar. Combinan estas soluciones para crear una solución alproblema original.La técnica Dividir para Conquistar (o Divide y Vencerás) consiste endescomponer el caso que hay que resolver en subcasos más pequeños,resolver independientemente los subcasos y por último combinar lassoluciones de los subcasos para obtener la solución del caso original.Caso GeneralConsideremos un problema arbitrario, y sea A un algoritmo sencillo capaz deresolver el problema. A debe ser eficiente para casos pequeños y lodenominamos subalgoritmo básico.El caso general de los algoritmos de Divide y Vencerás (DV) es como sigue:función DV(x){si x es suficientemente pequeño o sencillo entoncesdevolver A(x)descomponer x en casos más pequeños x1, x2, x3 , ... , xlpara i ← 1 hasta l haceryi ← DV(xi)recombinar los yi para obtener una solución y de xdevolver y}El número de subejemplares l suele ser pequeño e independiente del casoparticular que haya que resolverse.Para aplicar la estrategia Divide y Vencerás es necesario que se cumplan trescondiciones:• La decisión de utilizar el subalgoritmo básico en lugar de hacerllamadas recursivas debe tomarse cuidadosamente.• Tiene que ser posible descomponer el caso en subcasos y recomponerlas soluciones parciales de forma eficiente.• Los subcasos deben ser en lo posible aproximadamente del mismotamaño.En la mayoría de los algoritmos de Divide y Vencerás el tamaño de los lsubcasos es aproximadamente m/b, para alguna constante b, en donde m esel tamaño del caso (o subcaso) original (cada subproblema esaproximadamente del tamaño 1/b del problema original).
Página 57El análisis de tiempos de ejecución para estos algoritmos es el siguiente:Sea g(n) el tiempo requerido por DV en casos de tamaño n, sin contar eltiempo necesario para llamadas recursivas. El tiempo total t(n) requeridopor este algoritmo de Divide y Vencerás es parecido a:t(n) = l t(n/ b) + g(n) para l = 1 y b = 2siempre que n sea suficientemente grande. Si existe un entero k = 0 tal que:g(n) ∈ Θ (nk),se puede concluir que:T(nk) si l < bkt(n) ∈ T(nk log n) si l = bkT (nlogbl) si l > bkSe deja propuesta la demostración de esta ecuación de recurrencia usandoanálisis asintótico.5.4 Programación DinámicaFrecuentemente para resolver un problema complejo se tiende a dividir esteen subproblemas más pequeños, resolver estos últimos (recurriendoposiblemente a nuevas subdivisiones) y combinar las soluciones obtenidaspara calcular la solución del problema inicial.Puede ocurrir que la división natural del problema conduzca a un grannúmero de subejemplares idénticos. Si se resuelve cada uno de ellos sintener en cuenta las posibles repeticiones, resulta un algoritmo ineficiente;en cambio si se resuelve cada ejemplar distinto una sola vez y se conserva elresultado, el algoritmo obtenido es mucho mejor.Esta es la idea de la programación dinámica: no calcular dos veces lo mismoy utilizar normalmente una tabla de resultados que se va rellenando amedida que se resuelven los subejemplares.La programación dinámica es un método ascendente. Se resuelven primerolos subejemplares más pequeños y por tanto más simples. Combinando lassoluciones se obtienen las soluciones de ejemplares sucesivamente másgrandes hasta llegar al ejemplar original.
Página 58Ejemplo:Consideremos el cálculo de números combinatorios. El algoritmo sería:función C(n, k)si k=0 o k=n entonces devolver 1si no devolver C(n-1, k-1) + C(n-1, k)Ocurre que muchos valores C(i, j), con i < n y j < k se calculan y recalculanvarias veces.Un fenómeno similar ocurre con el algoritmo de Fibonacci.La programación dinámica se emplea a menudo para resolver problemas deoptimización que satisfacen el principio de optimalidad: en una secuenciaóptima de decisiones toda subsecuencia ha de ser también óptima.Ejemplo:Si el camino más corto de Santiago a Copiapó pasa por La Serena, la partedel camino de Santiago a La Serena ha de ser necesariamente el caminomínimo entre estas dos ciudades.Podemos aplicar esta técnica en:• Camino mínimo en un grafo orientado• Árboles de búsqueda óptimos5.5 Algoritmos ÁvidosLos algoritmos ávidos o voraces (Greedy Algorithms) son algoritmos quetoman decisiones de corto alcance, basadas en información inmediatamentedisponible, sin importar consecuencias futuras.Suelen ser bastante simples y se emplean sobre todo para resolver problemasde optimización, como por ejemplo, encontrar la secuencia óptima paraprocesar un conjunto de tareas por un computador, hallar el camino mínimode un grafo, etc.Habitualmente, los elementos que intervienen son:• un conjunto o lista de candidatos (tareas a procesar, vértices delgrafo, etc.);• un conjunto de decisiones ya tomadas (candidatos ya escogidos);• una función que determina si un conjunto de candidatos es unasolución al problema (aunque no tiene por qué ser la óptima);
Página 59• una función que determina si un conjunto es completable, esdecir, si añadiendo a este conjunto nuevos candidatos es posiblealcanzar una solución al problema, suponiendo que esta exista;• una función de selección que escoge el candidato aún noseleccionado que es más prometedor;• una función objetivo que da el valor/coste de una solución (tiempototal del proceso, la longitud del camino, etc.) y que es la que sepretende maximizar o minimizar;Para resolver el problema de optimización hay que encontrar un conjunto decandidatos que optimiza la función objetivo. Los algoritmos voracesproceden por pasos.Inicialmente el conjunto de candidatos es vacío. A continuación, en cadapaso, se intenta añadir al conjunto el mejor candidato de los aún noescogidos, utilizando la función de selección. Si el conjunto resultante no escompletable, se rechaza el candidato y no se le vuelve a considerar en elfuturo. En caso contrario, se incorpora al conjunto de candidatos escogidos ypermanece siempre en él. Tras cada incorporación se comprueba si elconjunto resultante es una solución del problema. Un algoritmo voraz escorrecto si la solución así encontrada es siempre óptima.El esquema genérico del algoritmo voraz es:funcionvoraz(C:conjunto):conjunto{ C es el conjunto de todos los candidatos }S <= vacío { S es el conjunto en el que se construye la soluciónmientras ¬solución(S) y C <> vacío hacerx ← el elemento de C que maximiza seleccionar(x)C ← C {x}si completable(S U {x}) entonces S ← S U {x}si solución(S)entonces devolver Ssi no devolver no hay soluciónEl nombre voraz proviene de que, en cada paso, el algoritmo escoge el mejor"pedazo" que es capaz de "comer" sin preocuparse del futuro. Nunca deshaceuna decisión ya tomada: una vez incorporado un candidato a la soluciónpermanece ahí hasta el final; y cada vez que un candidato es rechazado, lo espara siempre.
Página 60Ejemplo: Mínimo número de monedasSe desea pagar una cantidad de dinero a un cliente empleando el menornúmero posible de monedas. Los elementos del esquema anterior seconvierten en:• candidato: conjunto finito de monedas de, por ejemplo, 1, 5, 10 y 25unidades, con una moneda de cada tipo por lo menos;• solución: conjunto de monedas cuya suma es la cantidad a pagar;• completable: la suma de las monedas escogidas en un momento dadono supera la cantidad a pagar;• función de selección: la moneda de mayor valor en el conjunto decandidatos aún no considerados;• función objetivo: número de monedas utilizadas en la solución.5.6 Método de Retroceso (backtracking)El Backtracking o vuelta atrás es una técnica de resolución general deproblemas mediante una búsqueda sistemática de soluciones. Elprocedimiento general se basa en la descomposición del proceso debúsqueda en tareas parciales de tanteo de soluciones (trial and error).Las tareas parciales se plantean de forma recursiva al construirgradualmente las soluciones. Para resolver cada tarea, se descompone envarias subtareas y se comprueba si alguna de ellas conduce a la solución delproblema. Las subtareas se prueban una a una, y si una subtarea no conducea la solución se prueba con la siguiente.La descripción natural del proceso se representa por un árbol de búsquedaen el que se muestra como cada tarea se ramifica en subtareas. El árbol debúsqueda suele crecer rápidamente por lo que se buscan herramientaseficientes para descartar algunas ramas de búsqueda produciéndose la podadel árbol.Diversas estrategias heurísticas, frecuentemente dependientes delproblema, establecen las reglas para decidir en que orden se tratan las tareasmediante las que se realiza la ramificación del árbol de búsqueda, y quéfunciones se utilizan para provocar la poda del árbol.El método de backtracking se ajusta a muchos famosos problemas de lainteligencia artificial que admiten extensiones muy sencillas. Algunos deestos problemas son el de las Torres de Hanoi, el del salto del caballo o el dela colocación de las ocho reinas en el tablero de ajedrez, el problema dellaberinto.
Página 61Un caso especialmente importante en optimización combinatoria es elproblema de la selección óptima. Se trata del problema de seleccionar unasolución por sus componentes, de forma que respetando una serie derestricciones, de lugar a la solución que optimiza una función objetivo que seevalúa al construirla. El árbol de búsqueda permite recorrer todas lassoluciones para quedarse con la mejor de entre las que sean factibles. Lasestrategias heurísticas orientan la exploración por las ramas másprometedoras y la poda en aquellas que no permiten alcanzar una soluciónfactible o mejorar la mejor solución factible alcanzada hasta el momento.El problema típico es el conocido como problema de la mochila. Sedispone de una mochila en la que se soporta un peso máximo P en la que sedesean introducir objetos que le den el máximo valor. Se dispone de unacolección de n objetos de los que se conoce su peso y su valor;(pi,vi); i = 1, 2, ..., n.5.7 Método de Branch and BoundBranch and bound (o también conocido como Ramifica y Poda) es unatécnica muy similar a la de Backtracking, pues basa su diseño en el análisisdel árbol de búsqueda de soluciones a un problema. Sin embargo, no utilizala búsqueda en profundidad (depth first), y solamente se aplica enproblemas de Optimización.Los algoritmos generados por está técnica son normalmente de ordenexponencial o peor en su peor caso, pero su aplicación ante instancias muygrandes, ha demostrado ser eficiente (incluso más que bactracking).Se debe decidir de qué manera se conforma el árbol de búsqueda desoluciones. Sobre el árbol, se aplicará una búsqueda en anchura (breadth-first), pero considerando prioridades en los nodos que se visitan (best-first). El criterio de selección de nodos, se basa en un valor óptimo posible(bound), con el que se toman decisiones para hacer las podas en el árbol.Modo de proceder de los algoritmos Branch and Bound:• Nodo vivo: nodo con posibilidad de ser ramificado, es decir, no seha realizado una poda.• No se pueden comenzar las podas hasta que no se hayadescendido a una hoja del árbol• Se aplicará la poda a los nodos con una cota peor al valor de unasolución dada à a los nodos podados a los que se les ha aplicadouna poda se les conoce como nodos muertos• Se ramificará hasta que no queden nodos vivos por expandir• Los nodos vivos han de estar almacenados en algún sitio à listade nodos, de modo que sepamos qué nodo expandir
Página 62Algoritmo General:Funciónramipoda(P:problema): solucionL={P} (lista de nodos vivos)S= ∞± (según se aplique máximo o mínimo)Mientras L ≠ 0n=nodo de mejor cota en Lsi solucion(n)si (valor(n) mejor que s)s=valor(n)para todo m ∈ L y Cota(m) peor que Sborrar nodo m de Lsinoañadir a L los hijos de n con sus cotasborrar n de L (tanto si es solución como si ha ramificado)Fin_mientrasDevolver S
Página 63Capítulo 6Algoritmos de Ordenamiento6.1 Concepto de OrdenamientoDEFINICIÓN.Entrada: Una secuencia de n números <a1, a2 , …, an>, usualmente en laforma de un arreglo de n elementos.Salida: Una permutación <a’1, a’2, …, a’n> de la secuencia de entrada talque a’1 = a’2 = … = a’n.CONSIDERACIONES. Cada número es normalmente una clave que formaparte de un registro; cada registro contiene además datos satélites que semueven junto con la clave; si cada registro incluye muchos datos satélites,entonces movemos punteros a los registros (y no los registros).Un método de ordenación se denomina estable si el orden relativo de loselementos no se altera por el proceso de ordenamiento. (Importante cuantose ordena por varias claves).Hay dos categorías importantes y disjuntas de algoritmos de ordenación:• Ordenación interna: La cantidad de registros es suficientementepequeña y el proceso puede llevarse a cabo en memoria.• Ordenación externa: Hay demasiados registros para permitirordenación interna; deben almacenarse en disco.Por ahora nos ocuparemos sólo de ordenación interna.Los algoritmos de ordenación interna se clasifican de acuerdo con lacantidad de trabajo necesaria para ordenar una secuencia de n elementos:¿Cuántas comparaciones de elementos y cuántos movimientos deelementos de un lugar a otro son necesarios?Empezaremos por los métodos tradicionales (inserción y selección). Sonfáciles de entender, pero ineficientes, especialmente para juegos de datosgrandes. Luego prestaremos más atención a los más eficientes: heapsort yquicksort.

References: Resolución 
 Resolución 
 Resolución 
 resolución 
 Resolución 
 resolución 
 Resolución 
 resolución 
 resolución 
 resolución