230 likes | 534 Views
Divide y vencerás. 1. Método general. 2. Análisis de tiempos de ejecución. 2.1. Análisis general. 2.2. Caso recursivo. 3. Ejemplos. 3.1. Búsqueda del máximo y el mínimo. 3.2. Ordenación por mezcla y ordenación rápida. 3.3. El problema de selección.
E N D
Divide y vencerás 1. Método general. 2. Análisis de tiempos de ejecución. 2.1. Análisis general. 2.2. Caso recursivo. 3. Ejemplos. 3.1. Búsqueda del máximo y el mínimo. 3.2. Ordenación por mezcla y ordenación rápida. 3.3. El problema de selección. 3.4. Multiplicación rápida de enteros largos. 3.5. Multiplicación rápida de matrices. 1
Método general • 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. • Esquema general: divide_venceras (p: problema) dividir (p, p1, p2, ..., pk) para i = 1, 2, ..., k si = resolver (pi) solucion = combinar (s1, s2, ..., sk) Puede ser recursivo siendo “resolver” una nueva llamada a “divide_venceras” Si el problema es “pequeño”, entonces se puede resolver de forma directa. • Ejemplo: Torres de Hanoi 2
Método general • Para que pueda aplicarse la técnica divide y vencerás necesitamos: • 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). • La solución de un subproblema debe obtenerse independientemente de los otros. • 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). • Necesitamos un método (más o menos directo) de resolver los problemas de tamaño pequeño. • Es necesario tener un método de combinar los resultados de los subproblemas. 3
Método general, esquema recursivo • Normalmente para resolver los subproblemas se utilizan llamadas recursivas al mismo algoritmo (aunque no necesariamente). • Esquema recursivo (con división en 2 subproblemas): divide_venceras (p, q: indice) var m: indice si pequeño (p, q) solucion = solucion_directa (p, q) en otro caso m = dividir (p, q); solucion = combinar (divide_venceras (p, m), divide_venceras (m+1, q)); 4
Análisis de tiempos de ejecución • Para el esquema recursivo, con división en dos subproblemas: g(n) Si nn0 es suficientemente pequeño t(n) = 2*t(n/2) + f(n) En otro caso • t(n): tiempo de ejecución del algoritmo. • g(n): tiempo de comprobar si es pequeño y calcular la solución para el caso base • f(n): tiempo de comprobar si es pequeño y de dividir el problema y combinar los resultados. 5
Análisis de tiempos de ejecución • Desarrollando tenemos: Suponiendo que n es potencia de 2, n = 2k, y n0 = n/2m. • Si n0=1, entonces m=k: 6
Análisis de tiempos de ejecución • Ejemplo 1. La resolución directa se puede hacer en un tiempo constante y la combinación de resultados también. g(n) = c; f(n) = d • Ejemplo 2. La solución directa se calcula en O(n2) y la combinación en O(n). g(n) = c·n2; f(n) = d·n 7
Análisis de tiempos de ejecución • 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: t(n) = a·t(n/b) + d·n Suponiendo n = bk k = logb n t(bk) = a·t(bk-1) + d·bk Podemos deducir que: O(nlogba) Si a > b t(n) O(n·log n) Si a = b O(n) Si a < b • Ejemplo 3. Dividimos en 2 trozos de tamaño n/2 (ordenación por mezcla): a = b = 2 t(n) O(n·log n) • Ejemplo 4. Realizamos 4 llamadas recursivas con trozos de tamaño n/2. a = 4; b = 2 t(n) O(nlog24) = O(n2) 8
Búsqueda del máximo y del mínimo • Método directo: MaxMin (A: array [1..N] of tipo; var Max, Min: tipo) Max = A[1] Min = A[1] para i=2, 3, ..., N si A[i]>Max Max = A[i] en otro caso si A[i]<Min Min = A[i] • Contamos el número de comparaciones y asignaciones. Comparaciones Asignaciones 9
Búsqueda del máximo y del mínimo • Aplicando divide y vencerás: MaxMinDV (i, j: integer; var Max, Min: tipo) si i<j-1 mit = (i+j) div 2 MaxMinDV (i, mit, Max1, Min1) MaxMinDV (mit+1, j, Max2, Min2) /*Combinar*/ si Max1>Max2 Max= Max1 en otro caso Max = Max2 si Min1<Min2 Min = Min1 en otro caso Min = Min2 /*Caso base*/ en otro caso si i=j-1 si A[i]>A[j] Max = A[i] ; Min = A[j] en otro caso Max = A[j] ; Min= A[i] en otro caso Max = A[i] ; Min = Max 10
Búsqueda del máximo y del mínimo • Comparaciones (entre valores del tipo a ordenar) usando divide y vencerás. Debemos resolver la ecuación de recurrencia: t(n) = 2·t(n/2) + 2 • Suponiendo n = 2k, tenemos: t(k) = 2·t(k-1) + 2 (x-2)·(x-1) = 0 t(n) = C1n + C2 • Con condiciones iniciales t(2) = 1; t(4) = 4 t(n) = 3/2·n – 2 • Con condiciones iniciales t(1) = 0; t(2) = 1 t(n) = n – 1 • ¿Cuál es el valor de o? 11
Búsqueda del máximo y del mínimo • Asignaciones. La ecuación de recurrencia es la misma, sólo cambian las condiciones iniciales. t(2) = 2; t(4) = 6 Número de asignaciones: t(n) = 2n – 2 • El número de comparaciones es menor en el caso promedio. • El número de asignaciones es peor en todos los casos. 12
Ordenación por mezcla MergeSort (i, j: integer) /* Es pequeño si el tamaño es menor que un caso base */ si pequeño(i, j) OrdenaciónDirecta(i, j) en otro caso /* 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 /* Resolver recursivamente los subproblemas */ MergeSort(i, s) MergeSort(s+1, j) /* Mezcla dos listas ordenadas. En O(n) */ Combina (i, s, j) 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) 13
Ordenación rápida QuickSort (i, j: integer) /* Es pequeño si el tamaño es menor que un caso base */ si pequeño(i, j) OrdenaciónDirecta(i, j) en otro caso /* El array (i..j) es dividido usando un procedimiento Pivote, que devuelve un entero l entre (i, j), tal que A[ia] A[l] A[ja], para ia = i..l-1, ja=l+1..j */ Pivote(i,j,l) /* Resolver recursivamente los subproblemas, sin incluir el pivote */ QuickSort(i, l-1) QuickSort(l+1, j) /* No es necesario combinar */ • Aunque no hay coste de combinar los resultados, la llamada a Pivote tiene un coste O(n). • Las particiones no tienen porqué ser de tamaño n/2. 14
Ordenación rápida Pivote (i, j: integer; var l: integer) p = A[i] k = i l = j+1 repetir k:= k+1 hasta (A[k] > p) o (k j) repetir l = l-1 hasta (A[l] p) mientras k < l intercambiar (k, l) repetir k = k+1 hasta (A[k] > p) repetir l = l-1 hasta (A[l] p) Intercambiar (i, l) 15
Ordenación rápida: costes. • Mejor caso. Todas las particiones son de tamaño similar, n/2. t(n) = 2·t(n/2) + b·n + c (n·log n) • Peor caso. 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. t(n) = t(n-1) + b·n + c O(n2) • Caso promedio. Se puede comprobar que tp(n) (n·log n). 16
El problema de selección • 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. • 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. • Forma sencilla de resolver el problema de selección: Ordenar T y devolver el valor T[s]. Esto requeriría (n·log n) 17
El problema de selección • Utilizando el procedimiento Pivote podemos resolverlo en O(n). Selección (T: array [1..n]; s: integer) i = 1 j = n repetir Pivote (i, j, l) si s < l j = l-1 en otro caso si s > l i = l+1 hasta l=s devolver T[l] • El procedimiento es no recursivo. Además es una reducción: el problema es descompuesto en un solo subproblema de tamaño menor. • En el mejor caso, el subproblema es de tamaño n/2: t(n) = t(n/2) + a·n; t(n) O(n) • En el peor caso el subproblema es de tamaño n-1: t(n) = t(n-1) + a·n; t(n) O(n2) 18
Multiplicación rápida de enteros largos • Supongamos que representamos números enteros (de tamaño arbitrariamente grande) mediante listas de cifras. tipo EnteroLargo = puntero a nodo; nodo = registro valor : 0..9 sig : EnteroLargo • 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). • El algoritmo tendrá un orden de O(n·m), suponiendo n y m las longitudes de los enteros. Si las suponemos iguales, entonces O(n2). 19
u v w y x z Multiplicación rápida de enteros largos u = w·10S + x • Podemos aplicar la técnica divide y vencerás: • Divide: los enteros de tamaño n son divididos en tamaño n/2. • Solucionar los subproblemas de tamaño n/2. • Combinar: sumar los resultados de los anteriores (con los desplazamientos adecuados). • Cálculo de la multiplicación con divide y vencerás: u·v = 102S·w·y + 10S·(w·z+x·y) + x·z • 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). • t(n) = 4·t(n/2) + d·n O(nlog24) = O(n2), no mejora el método clásico. v = y·10S + z n/2 n/2=S 20
Multiplicación rápida de enteros largos • Multiplicación rápida de enteros largos (Karatsuba y Ofman): u·v = 102S·w·y + 10S·[(w-x)·(z-y) + w·y + x·z] + x·z • En este caso se requieren 3 multiplicaciones de tamaño n/2: t(n) = 3·t(n/2) + d’·n O(nlog23) O(n1.59) • 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. 21
A11 B11 C11 B12 A12 C12 A21 C21 B21 B22 A22 C22 Multiplicación rápida de matrices • Multiplicar dos matrices cuadradas A, B de tamaños nxn. C = AxB; C(i, j) = A(i, k)·B(k, j); Para todo i, j= 1..n k=1..n • El método clásico de multiplicación de matrices requiere O(n3). • Aplicando divide y vencerás, cada matriz es dividida en cuatro submatrices de tamaño (n/2)x(n/2): Aij, Bij y Cij. C11 = A11B11 + A12B21 C12 = A11B12 + A12B22 C21 = A21B11 + A22B21 C22 = A21B12 + A22B22 x = • Es necesario resolver 8 problemas de tamaño n/2. La combinación de los resultados requiere un tiempo de O(n2). t(n) = 8·t(n/2) + a·n2 • Resolviéndolo obtenemos que t(n) es O(n3). 22
Multiplicación rápida de matrices • Multiplicación rápida de matrices (Strassen): P = (A11+A22)(B11+B22) Q = (A12+A22) B11 C11 = P + S - T + U R = A11 (B12-B22) C12 = R + T S = A22(B21-B11) C21 = Q + S T = (A11+A12)B22 C22 = P + R - Q + U U = (A21-A11)(B11+B12) V = (A12-A22)(B21+B22) • El tiempo de ejecución será: t(n) = 7·t(n/2) + a·n2 • Resolviéndolo, tenemos que t(n) O(nlog27) O(n2.81). • 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. 23