Divide y Venceras

10,653 views

Published on

divide y venceras, teoria algoritmica

Published in: Education, Technology
  • Be the first to like this

Divide y Venceras

  1. 1. Divide y vencerás <ul><li>1. Método general. </li></ul><ul><li>2. Análisis de tiempos de ejecución. </li></ul><ul><li>2.1. Análisis general. </li></ul><ul><li>2.2. Caso recursivo. </li></ul><ul><li>3. Ejemplos. </li></ul><ul><li>3.1. Búsqueda del máximo y el mínimo. </li></ul><ul><li>3.2. Ordenación por mezcla y ordenación rápida. </li></ul><ul><li>3.3. El problema de selección. </li></ul><ul><li>3.4. Multiplicación rápida de enteros largos. </li></ul><ul><li>3.5. Multiplicación rápida de matrices. </li></ul>
  2. 2. Método general <ul><li>La técnica divide y vencerás consiste en descomponer el problema en un conjunto de subproblemas más pequeños. Después se resuelven estos subproblemas y se combinan las soluciones para obtener la solución para el problema original. </li></ul><ul><li>Esquema general: </li></ul><ul><li>divide_venceras (p: problema) </li></ul><ul><li>dividir (p, p 1 , p 2 , ..., p k ) </li></ul><ul><li>para i = 1, 2, ..., k </li></ul><ul><li> s i = resolver (p i ) </li></ul><ul><li>solucion = combinar (s 1 , s 2 , ..., s k ) </li></ul><ul><li>Puede ser recursivo siendo “resolver” una nueva llamada a “divide_venceras” </li></ul><ul><li>Si el problema es “pequeño”, entonces se puede resolver de forma directa. </li></ul><ul><li>Ejemplo: Torres de Hanoi </li></ul>
  3. 3. Método general <ul><li>Para que pueda aplicarse la técnica divide y vencerás necesitamos: </li></ul><ul><ul><li>El problema original debe poder dividirse fácilmente en un conjunto de subproblemas, del mismo tipo que el problema original pero con una resolución más sencilla (menos costosa). </li></ul></ul><ul><ul><li>La solución de un subproblema debe obtenerse independientemente de los otros. </li></ul></ul><ul><ul><li>Normalmente los subproblemas deben ser de tamaños parecidos. Como mínimo necesitamos que haya dos subproblemas. Si sólo tenemos un subproblema hablamos de técnicas de reducción (o simplificación ). </li></ul></ul><ul><ul><li>Necesitamos un método (más o menos directo) de resolver los problemas de tamaño pequeño. </li></ul></ul><ul><ul><li>Es necesario tener un método de combinar los resultados de los subproblemas. </li></ul></ul>
  4. 4. Método general, esquema recursivo <ul><li>Normalmente para resolver los subproblemas se utilizan llamadas recursivas al mismo algoritmo (aunque no necesariamente). </li></ul><ul><li>Esquema recursivo (con división en 2 subproblemas): </li></ul><ul><li>divide_venceras (p, q: indice) </li></ul><ul><li>var m: indice </li></ul><ul><li>si pequeño (p, q) </li></ul><ul><li> solucion = solucion_directa (p, q) </li></ul><ul><li>en otro caso </li></ul><ul><li> m = dividir (p, q); </li></ul><ul><li> solucion = combinar (divide_venceras (p, m), </li></ul><ul><li> divide_venceras (m+1, q)); </li></ul>
  5. 5. Análisis de tiempos de ejecución <ul><li>Para el esquema recursivo, con división en dos subproblemas: </li></ul><ul><li>g(n) Si n  n 0 es suficientemente pequeño </li></ul><ul><li>t(n) = </li></ul><ul><li>2*t(n/2) + f(n) En otro caso </li></ul><ul><li>t(n) : tiempo de ejecución del algoritmo. </li></ul><ul><li>g(n) : tiempo de comprobar si es pequeño y calcular la solución para el caso base </li></ul><ul><li>f(n) : tiempo de comprobar si es pequeño y de dividir el problema y combinar los resultados. </li></ul>
  6. 6. Análisis de tiempos de ejecución <ul><li>Desarrollando tenemos: </li></ul><ul><li>Suponiendo que n es potencia de 2, n = 2 k , y n 0 = n/2 m . </li></ul><ul><li>Si n 0 =1, entonces m=k: </li></ul>
  7. 7. Análisis de tiempos de ejecución <ul><li>Ejemplo 1. La resolución directa se puede hacer en un tiempo constante y la combinación de resultados también. </li></ul><ul><ul><li>g(n) = c; f(n) = d </li></ul></ul><ul><li>Ejemplo 2. La solución directa se calcula en O(n 2 ) y la combinación en O(n). </li></ul><ul><ul><li>g(n) = c·n 2 ; f(n) = d·n </li></ul></ul>
  8. 8. Análisis de tiempos de ejecución <ul><li>Si el problema se divide en a llamadas recursivas de tamaño n/b , y la combinación requiere f(n) = d·n  O(n), entonces: </li></ul><ul><ul><li>t(n) = a·t(n/b) + d·n </li></ul></ul><ul><ul><li>Suponiendo n = b k  k = log b n </li></ul></ul><ul><ul><li>t(b k ) = a·t(b k-1 ) + d·b k </li></ul></ul><ul><ul><li>Podemos deducir que: </li></ul></ul><ul><ul><li>O(n log b a ) Si a > b </li></ul></ul><ul><ul><li>t(n)  O(n·log n) Si a = b </li></ul></ul><ul><ul><li>O(n) Si a < b </li></ul></ul><ul><li>Ejemplo 3. Dividimos en 2 trozos de tamaño n/2 (ordenación por mezcla): </li></ul><ul><ul><li>a = b = 2 </li></ul></ul><ul><ul><li>t(n)  O(n·log n) </li></ul></ul><ul><li>Ejemplo 4. Realizamos 4 llamadas recursivas con trozos de tamaño n/2. </li></ul><ul><ul><li>a = 4; b = 2 </li></ul></ul><ul><ul><li>t(n)  O(n log 2 4 ) = O(n 2 ) </li></ul></ul>
  9. 9. Búsqueda del máximo y del mínimo <ul><li>Método directo: </li></ul><ul><li>MaxMin (A: array [1..N] of tipo; var Max, Min: tipo) </li></ul><ul><li>Max = A[1] </li></ul><ul><li>Min = A[1] </li></ul><ul><li>para i=2, 3, ..., N </li></ul><ul><li> si A[i]>Max </li></ul><ul><li> Max = A[i] </li></ul><ul><li> en otro caso si A[i]<Min </li></ul><ul><li> Min = A[i] </li></ul><ul><li>Contamos el número de comparaciones y asignaciones. </li></ul><ul><li> Comparaciones Asignaciones </li></ul>
  10. 10. Búsqueda del máximo y del mínimo <ul><li>Aplicando divide y vencerás : </li></ul><ul><li>MaxMinDV (i, j: integer; var Max, Min: tipo) </li></ul><ul><li>si i<j-1 </li></ul><ul><li>mit = (i+j) div 2 </li></ul><ul><li>MaxMinDV (i, mit, Max1, Min1) </li></ul><ul><li>MaxMinDV (mit+1, j, Max2, Min2) </li></ul><ul><li>/*Combinar*/ </li></ul><ul><li>si Max1>Max2 </li></ul><ul><li> Max= Max1 </li></ul><ul><li>en otro caso </li></ul><ul><li> Max = Max2 </li></ul><ul><li>si Min1<Min2 </li></ul><ul><li> Min = Min1 </li></ul><ul><li>en otro caso </li></ul><ul><li> Min = Min2 </li></ul><ul><li>/*Caso base*/ </li></ul><ul><li>en otro caso si i=j-1 </li></ul><ul><li>si A[i]>A[j] </li></ul><ul><li> Max = A[i] ; Min = A[j] </li></ul><ul><li>en otro caso </li></ul><ul><li> Max = A[j] ; Min= A[i] </li></ul><ul><li>en otro caso </li></ul><ul><li> Max = A[i] ; Min = Max </li></ul>
  11. 11. Búsqueda del máximo y del mínimo <ul><li>Comparaciones (entre valores del tipo a ordenar) usando divide y vencerás. Debemos resolver la ecuación de recurrencia: </li></ul><ul><li>t(n) = 2·t(n/2) + 2 </li></ul><ul><li>Suponiendo n = 2 k , tenemos: </li></ul><ul><li>t(k) = 2·t(k-1) + 2  (x-2)·(x-1) = 0  t(n) = C 1 n + C 2 </li></ul><ul><li>Con condiciones iniciales t(2) = 1; t(4) = 4 </li></ul><ul><li>t(n) = 3/2·n – 2 </li></ul><ul><li>Con condiciones iniciales t(1) = 0; t(2) = 1 </li></ul><ul><li>t(n) = n – 1 </li></ul><ul><li>¿Cuál es el valor de o? </li></ul>
  12. 12. Búsqueda del máximo y del mínimo <ul><li>Asignaciones . La ecuación de recurrencia es la misma, sólo cambian las condiciones iniciales. </li></ul><ul><li>t(2) = 2; t(4) = 6 </li></ul><ul><li>Número de asignaciones: </li></ul><ul><li>t(n) = 2n – 2 </li></ul><ul><li>El número de comparaciones es menor en el caso promedio. </li></ul><ul><li>El número de asignaciones es peor en todos los casos. </li></ul>
  13. 13. Ordenación por mezcla <ul><li>MergeSort (i, j: integer) </li></ul><ul><li>/* Es pequeño si el tamaño es menor que un caso base */ si pequeño(i, j) </li></ul><ul><li> OrdenaciónDirecta(i, j) </li></ul><ul><li>en otro caso </li></ul><ul><li>/* El array original es dividido en dos trozos de tamaño igual (o lo más parecido posible), es decir  n/2  y  n/2  */ s = (i + j) div 2 </li></ul><ul><li>/* Resolver recursivamente los subproblemas */ MergeSort(i, s) MergeSort(s+1, j) </li></ul><ul><li>/* Mezcla dos listas ordenadas. En O(n) */ Combina (i, s, j) </li></ul><ul><li>t(n) = t(  n/2  ) + t(  n/2  ) + f(n); Con f(n)  O(n) Suponiendo n potencia de 2, tenemos: t(n) = 2·t(n/2) + a·n + b t(n)  O(n·log n) </li></ul>
  14. 14. Ordenación rápida <ul><li>QuickSort (i, j: integer) </li></ul><ul><li>/* Es pequeño si el tamaño es menor que un caso base */ si pequeño(i, j) </li></ul><ul><li> OrdenaciónDirecta(i, j) </li></ul><ul><li>en otro caso </li></ul><ul><li>/* El array (i..j) es dividido usando un procedimiento Pivote, que devuelve un entero l entre (i, j), tal que A[i a ]  A[l]  A[j a ], para i a = i..l-1, j a =l+1..j */ Pivote(i,j,l) </li></ul><ul><li>/* Resolver recursivamente los subproblemas, sin incluir el pivote */ QuickSort(i, l-1) QuickSort(l+1, j) </li></ul><ul><li>/* No es necesario combinar */ </li></ul><ul><li>Aunque no hay coste de combinar los resultados, la llamada a Pivote tiene un coste O(n). </li></ul><ul><li>Las particiones no tienen porqué ser de tamaño n/2. </li></ul>
  15. 15. Ordenación rápida <ul><li>Pivote (i, j: integer; var l: integer) </li></ul><ul><li>p = A[i] </li></ul><ul><li>k = i </li></ul><ul><li>l = j+1 </li></ul><ul><li>repetir </li></ul><ul><li>k:= k+1 </li></ul><ul><li>hasta (A[k] > p) o (k  j) </li></ul><ul><li>repetir </li></ul><ul><li>l = l-1 </li></ul><ul><li>hasta (A[l]  p) </li></ul><ul><li>mientras k < l </li></ul><ul><li> intercambiar (k, l) </li></ul><ul><li> repetir </li></ul><ul><li>k = k+1 </li></ul><ul><li>hasta (A[k] > p) </li></ul><ul><li> repetir </li></ul><ul><li>l = l-1 </li></ul><ul><li>hasta (A[l]  p) </li></ul><ul><li>Intercambiar (i, l) </li></ul>
  16. 16. Ordenación rápida: costes. <ul><li>Mejor caso. </li></ul><ul><li>Todas las particiones son de tamaño similar, n/2. </li></ul><ul><li>t(n) = 2·t(n/2) + b·n + c   (n·log n) </li></ul><ul><li>Peor caso. </li></ul><ul><li>Se da cuando la matriz está ordenada o inversamente ordeanada. En este caso una partición tiene tamaño 0 y la otra n-1. </li></ul><ul><li>t(n) = t(n-1) + b·n + c  O(n 2 ) </li></ul><ul><li>Caso promedio. </li></ul><ul><li>Se puede comprobar que t p (n)   (n·log n). </li></ul>
  17. 17. El problema de selección <ul><li>Sea T[1..n] un array (no ordenado) de enteros, y sea s un entero entre 1 y n. El problema de selección consiste en encontrar el elemento que se encontraría en la posición s si el array estuviera ordenado. </li></ul><ul><li>Si s =  n/2  , entonces tenemos el problema de encontrar la mediana de T, es decir el valor que es mayor que la mitad de los elementos de T y menor que la otra mitad. </li></ul><ul><li>Forma sencilla de resolver el problema de selección: </li></ul><ul><li>Ordenar T y devolver el valor T[s]. Esto requeriría  (n·log n) </li></ul>
  18. 18. El problema de selección <ul><li>Utilizando el procedimiento Pivote podemos resolverlo en O(n). </li></ul><ul><li>Selección (T: array [1..n]; s: integer) </li></ul><ul><li>i = 1 </li></ul><ul><li>j = n </li></ul><ul><li>repetir </li></ul><ul><li> Pivote (i, j, l) </li></ul><ul><li> si s < l </li></ul><ul><li>j = l-1 </li></ul><ul><li> en otro caso si s > l </li></ul><ul><li>i = l+1 </li></ul><ul><li>hasta l=s </li></ul><ul><li>devolver T[l] </li></ul><ul><li>El procedimiento es no recursivo. Además es una reducción: el problema es descompuesto en un solo subproblema de tamaño menor. </li></ul><ul><li>En el mejor caso, el subproblema es de tamaño n/2: </li></ul><ul><li>t(n) = t(n/2) + a·n; t(n)  O(n) </li></ul><ul><li>En el peor caso el subproblema es de tamaño n-1: </li></ul><ul><li>t(n) = t(n-1) + a·n; t(n)  O(n 2 ) </li></ul>
  19. 19. <ul><li>Supongamos que representamos números enteros (de tamaño arbitrariamente grande) mediante listas de cifras. </li></ul><ul><li>tipo </li></ul><ul><li> EnteroLargo = puntero a nodo; </li></ul><ul><li> nodo = registro </li></ul><ul><li> valor : 0..9 </li></ul><ul><li> sig : EnteroLargo </li></ul><ul><li>Con el algoritmo clásico de multiplicación, multiplicamos todos los dígitos de un entero por los del otro y sumamos (con los desplazamientos adecuados). </li></ul><ul><li>El algoritmo tendrá un orden de O(n·m), suponiendo n y m las longitudes de los enteros. Si las suponemos iguales, entonces O(n 2 ). </li></ul> Multiplicación rápida de enteros largos
  20. 20. Multiplicación rápida de enteros largos <ul><li>Podemos aplicar la técnica divide y vencerás : </li></ul><ul><ul><li>Divide: los enteros de tamaño n son divididos en tamaño n/2. </li></ul></ul><ul><ul><li>Solucionar los subproblemas de tamaño n/2. </li></ul></ul><ul><ul><li>Combinar: sumar los resultados de los anteriores (con los desplazamientos adecuados). </li></ul></ul><ul><li>Cálculo de la multiplicación con divide y vencerás: </li></ul><ul><li>u·v = 10 2S ·w·y + 10 S ·(w·z+x·y) + x·z </li></ul><ul><li>El problema de tamaño n es descompuesto en 4 problemas de tamaño n/2. La combinación (sumas y desplazamientos) se puede realizar en un tiempo lineal O(n). </li></ul><ul><li>t(n) = 4·t(n/2) + d·n  O(n log 2 4 ) = O(n 2 ), no mejora el método clásico. </li></ul>  n/2   n/2  =S u = w·10 S + x v = y·10 S + z w x u y z v
  21. 21. Multiplicación rápida de enteros largos <ul><li>Multiplicación rápida de enteros largos (Karatsuba y Ofman): </li></ul><ul><li>u·v = 10 2S ·w·y + 10 S ·[(w-x)·(z-y) + w·y + x·z] + x·z </li></ul><ul><li>En este caso se requieren 3 multiplicaciones de tamaño n/2: </li></ul><ul><li>t(n) = 3·t(n/2) + d’·n  O(n log 2 3 )  O(n 1.59 ) </li></ul><ul><li>El método es asintóticamente mejor que el método clásico. Sin embargo, las constantes son mucho mayores. La combinación es muy costosa. </li></ul>
  22. 22. Multiplicación rápida de matrices <ul><li>Multiplicar dos matrices cuadradas A, B de tamaños nxn. </li></ul><ul><li>C = AxB; C(i, j) =  A(i, k)·B(k, j); Para todo i, j= 1..n </li></ul><ul><li> k=1..n </li></ul><ul><li>El método clásico de multiplicación de matrices requiere O(n 3 ). </li></ul><ul><li>Aplicando divide y vencerás , cada matriz es dividida en cuatro submatrices de tamaño (n/2)x(n/2): A ij , B ij y C ij . </li></ul> x = <ul><li>Es necesario resolver 8 problemas de tamaño n/2. La combinación de los resultados requiere un tiempo de O(n 2 ). </li></ul><ul><li>t(n) = 8·t(n/2) + a·n 2 </li></ul><ul><li>Resolviéndolo obtenemos que t(n) es O(n 3 ). </li></ul>C 11 = A 11 B 11 + A 12 B 21 C 12 = A 11 B 12 + A 12 B 22 C 21 = A 21 B 11 + A 22 B 21 C 22 = A 21 B 12 + A 22 B 22 A 11 A 12 A 22 A 21 B 11 B 12 B 22 B 21 C 11 C 12 C 22 C 21
  23. 23. Multiplicación rápida de matrices <ul><li>Multiplicación rápida de matrices (Strassen): </li></ul><ul><li>P = (A 11 +A 22 )(B 11 +B 22 ) </li></ul><ul><li>Q = (A 12 +A 22 ) B 11 C 11 = P + S - T + U </li></ul><ul><li>R = A 11 (B 12 -B 22 ) C 12 = R + T </li></ul><ul><li>S = A 22 (B 21 -B 11 ) C 21 = Q + S </li></ul><ul><li>T = (A 11 +A 12 )B 22 C 22 = P + R - Q + U </li></ul><ul><li>U = (A 21 -A 11 )(B 11 +B 12 ) </li></ul><ul><li>V = (A 12 -A 22 )(B 21 +B 22 ) </li></ul><ul><li>El tiempo de ejecución será: </li></ul><ul><li>t(n) = 7·t(n/2) + a·n 2 </li></ul><ul><li>Resolviéndolo, tenemos que t(n)  O(n log 2 7 )  O(n 2.81 ). </li></ul><ul><li>Las constantes que multiplican al polinomio son mucho mayores (tenemos muchas sumas), por lo que sólo es mejor cuando la entrada es muy grande. </li></ul>

×