1. Definiciones previas

Documentos relacionados
Minimum Spanning Tree (Árbol de Expansión Mínima) Agustín J. González ELO320: Estructura de datos y Algoritmos

Análisis y Diseño de Algoritmos

Análisis de Algoritmos

Análisis de Algoritmos

Multiplicación de matrices simétricas

Notación Asintótica. Temas. Introducción Notación O Notación Omega Notación Theta. Análisis de Algoritmos

Diseño y Análisis de Algoritmos

Temario. Tipos de recursión. Eficiencia y recursión

Estructuras de Datos y Algoritmos

Análisis y Diseño de Algoritmos

Algoritmos y Estructuras de Datos Curso 06/07. Ejercicios

Algoritmos de Búsqueda

Demostrando cotas inferiores: Arboles de decisión

1. Recuerdo del algoritmo de KRUSKAL

Divide-y-vencerás, backtracking y programación dinámica

Algoritmos voraces (greedy)

Análisis de algoritmos.

Análisis y Diseño de Algoritmos

Carlos Delgado Kloos Ingeniería Telemática Univ. Carlos III de Madrid. Java: Complejidad / 1

1/16. Coste de Algoritmos. 8 de abril de 2018

Resolviendo Recurrencias

Tema 4y 5. Algoritmos voraces. Algoritmos sobre grafos

Algoritmos y Estructuras de Datos Tema 2: Diseño de Algoritmos

Complejidad computacional. Algoritmos y Estructuras de Datos I. Complejidad computacional. Notación O grande

Análisis y Diseño de Algoritmos (AyDA) Isabel Besembel Carrera

Algoritmos Elementales de Grafos. Agustín J. González ELO-320: Estructura de Datos Y Algoritmos 1er.Sem. 2002

Algoritmos y Estructuras de Datos III

Recurrencias y cómo resolverlas

Estructuras de Datos y Algoritmos

Algoritmos Iterativos de Búsqueda y Ordenación y sus tiempos

Algoritmos y Complejidad

Complejidad computacional y asintótica

Análisis de algoritmos

IN34A - Optimización

Estructuras de Datos y Algoritmos. Grafos

Estructuras de Datos y Algoritmos. Curso 2009/2010. Tema 3: Divide y Vencerás

ESCUELA SUPERIOR POLITÉCNICA DEL LITORAL FACULTAD DE INGENIERÍA EN ELECTRICIDAD Y COMPUTACIÓN SYLLABUS DEL CURSO Análisis de Algoritmos

Técnicas y Herramientas. Algoritmos y Complejidad. Técnicas de Demostración. Técnicas y Herramientas. Herramientas Matemáticas Básicas

TIPOS ABSTRACTOS DE DATOS EN HASKELL

NOTACIÓN O GRANDE. El análisis de algoritmos estima el consumo de recursos de un algoritmo.

Tema 01: Fundamentos del Análisis Asintótico de Algoritmos

Análisis de Algoritmos

Algoritmos y problemas

Arreglos. Algoritmos y Estructuras de Datos I. Arreglos en C++ Arreglos y listas

Muchas de las ecuaciones de recurrencia que vamos a usar en este curso tienen la siguiente forma: ( c n =0 T (n) = a T (b n b.

Introducción. Algoritmos y Complejidad. Algoritmos y Algoritmia. Introducción. Problemas e instancias. Pablo R. Fillottrani

Algoritmos y Complejidad. Generalidades. Generalidades. Algoritmos greedy. Problema de la mochila. Caminos más cortos. Pablo R.

Solución - práctico 10

Algoritmos y Complejidad

Complejidad computacional. Algoritmos y Estructuras de Datos I. Complejidad computacional. Notación O grande

Análisis y Diseño de Algoritmos

Introducción al Análisis del Coste de Algoritmos

Estructuras de Datos. Clase 3 Análisis de algoritmos recursivos

Algoritmos y Estructuras de Datos III

Estructura de Datos. Complejidad de Algoritmos. Algoritmo. Algoritmo. Mauricio Solar Lorna Figueroa

Algoritmos para determinar Caminos Mínimos en Grafos

Algoritmos Secuenciales y Recursivos

Esquema de Dividir y Vencer

Estructuras de datos Listas y árboles

Complejidad de algoritmos recursivos

Algoritmos Recursivos de Búsqueda y Ordenación y sus tiempos

Análisis de algoritmos

Introducción Supongamos un subconjunto de n elementos X = {e 1,,e n de un conjunto referencial Y, X Y. Dentro de Y se define una relación de orden tot

Grafos. Leopoldo Taravilse. Facultad de Ciencias Exactas y Naturales Universidad de Buenos Aires. Training Camp 2012

Algorítmica: Análisis de Algoritmos

Análisis de Algoritmos

OBJETIVOS ÍNDICE BIBLIOGRAFÍA

Algoritmos y Estructuras de Datos: Introducción a la Recursión de Programas. Guillermo Román Díez

Introducción al análisis de algoritmos

Introducción y Comportamiento Asintótico

Algoritmos de Ordenamiento

Divide y vencerás. Dr. Eduardo A. RODRÍGUEZ TELLO. 7 de marzo de CINVESTAV-Tamaulipas

Programa de teoría. Algoritmos y Estructuras de Datos II. 2. Divide y vencerás. 1. Análisis de algoritmos

Algoritmos de búsqueda en grafos I

Teoría de Grafos. Herramientas de programación para procesamiento de señales

Tema 2. Divide y vencerás.

Estructuras de Datos. Estructuras de Datos para Conjuntos Disjuntos

Camino mínimo en grafos

ELO320 Estructuras de Datos y Algoritmos. Complejidad. Tomás Arredondo Vidal

A5 Introducción a la optimización en redes

Algorítmica y Lenguajes de Programación. Eficiencia y notación asintótica (i)

Tiempo de Ejecución. Midiendo el Tiempo de Ejecución

Análisis y Diseño de Algoritmos. Complejidad Computacional

Francisco J. Hernández López

Ordenamiento (Sorting)

Complejidad Computacional

SÍLABO DE ESTRUCTURA DE DATOS

Clausuras. , se define la clausura algebráica de A como

Algoritmo de Kruskal

Tema 9. Recursividad

Concentración de Medida en Algoritmos Aleatorizados.

Parte de Algoritmos de la asignatura de Programación Master de Bioinformática. Divide y vencerás

Grafos. Algoritmos y Estructuras de Datos III

La eficiencia de los programas

Matemáticas Discretas

Notación Asintótica 2

Algoritmos y Complejidad

Tema: Métodos de Ordenamiento. Parte 3.

Algoritmos de Ordenación

Transcripción:

Universidad de Chile Facultad de Ciencias Físicas y Matemáticas Departamento de Ingeniería Matemática MA47A: Optimización Combinatorial Profesor: Roberto Cominetti Auxiliares: Raul Aliaga Diaz, Cristóbal Guzmán Clase Auxiliar 27/03/07 1. Definiciones previas Para estimar el orden de magnitud del tiempo de ejecución de un programa, usaremos notación asint totica. Para ellos, introducimos las siguientes definiciones: 1. O(g) = {f : N R tq: c > 0 y n 0 N tq f(n) c g(n) n n 0 }. Esta definición nos permitirá en rigor, hablar del orden de magnitud de una función f : N R. Algunas propiedades: c O(f) = O(f) para todo c R, c > 0. O(f) + O(g) = O(f + g) = O(max{f + g}). O(f)O(g) = O(fg) = fo(g). O(f) m = O(f m ), para todo m N, m > 0. O(O(f)) = O(f). 2. Ω(g) = {f : N R tq: c > 0 y n 0 N tq f(n) c g(n) n n 0 }. 3. o(g) = {f O(g) tq lím n f(n) g(n) = 0}. 4. ω(g) = {f Ω(g) tq lím n f(n) g(n) = }. 5. Θ(g) = O(g) Ω(g). Diremos, por comodidad, que f(n) = O(g(n)) cuando nos referimos a f O(g). Para más detalles, ver notas sobre notación asint totica del Profesor Marcos Kiwi. 2. Algoritmos de ordenamiento 2.1. Bubblesort Consideremos el siguiente algoritmo: 1

BubbleSort(A) 1 Ordena un arreglo A 1: N length(a) 2: for i = 1... N do 3: for j = 1... N i do 4: if A[j + 1] < A[j] then 5: tmp = A[j] 6: A[j] = A[j + 1] 7: A[j + 1] = A[j] 8: end if 9: end for 10: end for Cuántas operaciones toma en ejecutar BubbleSort? Podemos notar fácilmente, que es O(n 2 ). Pero, Qué pasa si tenemos una recursión? 2.2. MergeSort Consideremos el algoritmo de ordenamiento MergeSort: MergeSort(A,p,f) 2 Ordena un arreglo A 1: if p < f then 2: m p+f 2 3: MergeSort(A,p,m) 4: MergeSort(A,m,f) 5: Merge(A,p,m,f) 6: end if En este caso, no es claro cuánto tiempo toma. Sea T (n) el tiempo que toma en correr el algoritmo, según el número de pasos realizados. Podemos así, escribir la ecuación: T (n) = 2T ( n ) + D(n) + C(n) (1) 2 Donde D(n) es el tiempo que nos toma dividir el problema en dos, y C(n) el tiempo que nos toma hacer la operación Merge, que junta las soluciones obtenidas en las mitades previas. Esta recursión es válida, siempre que el tamaño de la instancia n, sea lo suficientemente grande (n > 1, pues ordenar un elemento es trivial, se deja tal cual!). Como la operación que consideramos a contar es comparar, podemos asumir que D(n) es despreciable frente a las comparaciones (en nuestro caso, es solo una operación aritmética) y observemos que C(n) = n. Luego, si desarrollamos la recursión, obtendremos: 2

T (n) = 2T ( n 2 ) + n = 2 (2T ( n 4 ) + n 2 ) + n = 2 (2 (2T ( n 8 ) + n 4 ) + n 2 ) + n =... = 2 2... 2T (1) + n + n 2 +... + n 2 k Donde hemos iterado k veces, con k el número de veces antes de que n 2 k menor o igual a uno. Así nos queda: sea T (n) = 2 k T (1) + k n (2) Si consideramos entradas que son de tamaño siempre una potencia de dos, como n = 2 l, es directo observar que T (n) = lt (1) + nl = log nt (1) + n log n. Como T (1) es constante, tenemos en consecuencia que T (n) = O(n log n). Si consideramos entradas de cualquier tamaño en general, podemos demostrar (usando cotas apropiadas obtenidas a partir de considerar n 2 n b o n 2 n 2 ) que T (n) = O(n log n). Se puede demostrar que cualquier algoritmo de ordenamiento de este estilo (que particiona la entrada para reordenar recursivamente) no puede hacerlo mejor que O(n log n) 1, esta estrategia en general es una manera de abordar la técnica de Dividir y reinar 2. Para recurrencias como la recién vista, puede obtenerse una cota en general vía El Teorema Maestro 3. 3. Algoritmo de Kruskal Consideremos el algoritmo de Kruskal para encontrar un árbol generador de peso mínimo: Cuántas operaciones toma el algoritmo?. Notemos que hay algunas operaciones exógenas a la descripción usual del algoritmo, que dice si los extremos de los arcos están en componentes conexas distintas, entonces se agrega. Sin embargo, en el momento de implementar el algoritmo, podemos considerarlo de la siguiente manera. Inicialmente, para cada nodo se le crea un conjunto, el cual es el árbol al que pertence (MakeSet(u)), luego, cuando se hace la pregunta Cuál es la componente conexa a la que pertenece u? se realiza el llamado a FindSet(u), y una vez que se unen los arcos se actualiza el conjunto al que pertenecen con Union(u,v). Dependiendo de como representemos el grafo mediante estructuras de datos apropiadas (o no tan apropiadas), las operaciones que tome el algoritmo cambiarán. j=0 1 Ver Introduction to algorithms, Cormen, sección IV.9.1 2 Ver Cormen, sección I.3.1 3 Ver Cormen, secciónes I.4.3, I.4.4 3

Kruskal(G,w) 3 Encuentra un árbol generador del grafo G de peso mínimo 1: A 2: for all v V [G] do 3: MakeSet(v) 4: end for 5: Ordenar los arcos e E[G] en función de los pesos según w 6: for all arco (u, v) E, en el orden anterior do 7: if FindSet(u) FindSet(v) then 8: A A {(u, v)} 9: Union(u,v) 10: end if 11: end for 12: return A La estructura de datos que escojamos para representar el grafo, debe (al menos) implementar las operaciones siguientes: MakeSet(u). FindSet(u). Union(u,v). Consideremos dos maneras de representar un grafo. Mediante una matriz de adyacencia y una lista enlazada. Las operaciones podemos contabilizarlas como se sigue (con V [G] = n y E[G] = m): Matriz adyacencia Lista enlazada MakeSet(u) O(1) O(1) FindSet(u) O(n) O(1) Union(u,v) O(n) O(log n) La implementación con listas enlazadas, considera tener para un árbol, un nodo raíz, y punteros desde cada nodo a los nodos que le siguen. También, cada nodo tiene un puntero a la raíz del árbol. El inicializar las estructuras, podemos considerar que toma tiempo constante, asumiendo en la matriz de adyacencia, que inicializar los valores del arreglo de la matriz toma un tiempo despreciable con respecto a las otras operaciones. La operación FindSet(u), en el caso de la matriz de adyacencia, debe en el peor caso preguntar si llega a cada uno de los otros nodos, para asi definir on the fly a que conjunto pertenece el nodo en cuestión. En cambio, en la lista enlazada, podemos guardar una etiqueta en el nodo raíz (podemos hablar de raíz, pues cada componente conexa será siempre un árbol) que nos diga cuál es el conjunto al que pertenece el nodo. Así, nos queda por saber como se diferencia la operación Union en ambos casos. En el caso de la matriz de adyacencia, no sería necesario, pues en cada ocasión calculariamos el conjunto de los alcanzables a partir de un nodo, solo habría que actualizar la matriz 4

de adyacencia correspondiente en un valor. Sin embargo, en la lista enlazada, debemos actualizar los valores del nodo padre o raíz, para que asi el FindSet funcione como debe. Para ello, notemos que podemos hacer las asignaciones (o cambios de padre ) a lo más O(log n) veces. Pues si en cada caso consideramos el árbol más pequeño, podemos actualizar menos nodos si sólo cambiamos las referencias de ese árbol más pequeño. Así, cada vez que un nodo es forzado a cambiar su padre o raíz, es porque el otro árbol de la unión tiene tamaño al menos igual al del árbol en cuestión, por lo tanto, un nodo no puede ser cambiado más de O(log n) veces, pues k n, si a un nodo se le han realizado log k actualizaciones de padre, entonces el conjunto de nodos que lleva el árbol tiene al menos k nodos. En el primer paso del algoritmo, se ordenan los arcos, que como vimos en la parte anterior puede hacerse en O(m log m). Luego, sumando las operaciones hechas en la parte de revisar los arcos, notamos que se realizan a lo más n 1 log n operaciones, y puesto que el grafo es conexo, entonces podemos concluir que el algoritmo toma tiempo O(m log n) o bien O(m log m). 4. Archivos adjuntos Adjunto a este documento, están los siguientes archivos: En la carpeta Estructuras, se encuentran algunos ejemplos de estructuras de datos. En la carpeta Matlab, se encuentran implementaciones para los algoritmos de ordenamiento, junto con funciones de testeo de las mismas: testbubblesort y testmergesort, en que se grafica la función g del O(g) en que toma correr el algoritmo, y los tiempos obtenidos empíricamente 4. Dentro de ella, también hay dos carpetas que contienen una implementación en Matlab del algoritmo de Kruskal ( MST Kruskal ) y otra que permite dibujar grafos, proveyéndolos a una función en el formato adecuado ( GraphLayout ). En la carpeta Javas, se encuentra dos implementaciones de Kruskal, una muy pro (pero que para correr necesita una versión de java 1.5 o superior), y otra mucho más sencilla, junto con un archivo de prueba. Se recomienda revisar los archivos y las implementaciones, junto con las notas sobre notación asintótica del profesor Marcos Kiwi. 4 Se recomienda usar valores no mayores que 12 para invocar a las funciones, puesto que el tamaño de los arreglos crecen exponencialmente 5