66 views
# TDA | Divide & Conquer # Dividir y Conquistar El método de Dividir y Conquistar (D&C) es una técnica fundamental en el diseño de algoritmos, que consiste en los siguientes pasos generales: 1. **Dividir** el problema en subproblemas más pequeños del mismo tipo. 2. **Conquistar** resolviendo estos subproblemas de manera recursiva. Si son suficientemente pequeños, se resuelven directamente. 3. **Combinar** las soluciones de los subproblemas para formar la solución al problema original. ## Forma general de D&C La forma general de un algoritmo D&C para un problema $X$ puede describirse como sigue: - Si $X$ es lo suficientemente pequeño, se resuelve directamente. - De lo contrario: - Dividir $X$ en subproblemas $X_1, X_2, \ldots, X_k$. - Para cada $i \leq k$, hacer $Y_i = F(X_i)$. - Combinar las soluciones $Y_i$ en una solución $Y$ para $X$. - Devolver $Y$. ## Análisis de la complejidad Para analizar la complejidad de un algoritmo D&C, utilizamos la recurrencia: $$T(n) = aT\left(\frac{n}{c}\right) + b'n^d$$ donde: - $a$ es el número de subproblemas en cada división. - $n/c$ es el tamaño máximo de cada subproblema, con $n/c > n_0$. - $b'$ es una constante que representa el costo de dividir y combinar los resultados de los subproblemas. - $d$ es una constante que indica el costo de la división y la combinación. La solución a esta recurrencia varía según los valores de $a$, $c$ y $d$, pero en general se puede expresar usando la notación $O$ para describir la complejidad del algoritmo en términos de $n$. ## Casos específicos - **Cuando $a = 1$ y $d = 0$:** El costo de combinar es constante y la complejidad es $O(\log_c n)$. - **Cuando $d = 1$:** - Si $a < c$, la complejidad es $O(n)$. - Si $a = c$, la complejidad es $O(n \log_c n)$. - Si $a > c$, utilizando la fórmula de la suma de una serie geométrica, la complejidad es $O(n^{\log_c a})$. Estos análisis nos permiten comprender cómo la eficiencia de un algoritmo D&C está influenciada por cómo se dividen los problemas, el número de subproblemas generados, y el costo de combinar sus soluciones. ### Ejemplo Práctico Un ejemplo clásico de D&C es el algoritmo de ordenamiento Merge Sort. Este algoritmo divide el arreglo a ordenar en dos mitades, ordena cada mitad recursivamente y luego combina las dos mitades ordenadas. El proceso de división es simple y el de combinación (merge) tiene un costo lineal respecto al tamaño del problema, $n$. ### Complejidad de Merge Sort Aplicando el análisis anterior, podemos ver que Merge Sort tiene una recurrencia de la forma: $$T(n) = 2T\left(\frac{n}{2}\right) + \Theta(n)$$ donde el término $\Theta(n)$ representa el costo de combinar las dos mitades. Siguiendo el análisis de D&C: - Aquí, $a=2$, $c=2$ (ya que dividimos el problema en 2 partes iguales), y $d=1$ (porque el costo de combinar es lineal). - Aplicando nuestro análisis, vemos que cae en el caso en que $a = c$, lo cual da una complejidad de $O(n \log n)$. ### Conclusiones Los algoritmos de Dividir y Conquistar son una poderosa herramienta en el diseño de algoritmos, permitiendo soluciones eficientes a problemas que de otro modo serían intratables. La clave de su eficacia yace en cómo se divide el problema original y cómo se manejan los subproblemas para minimizar el trabajo redundante y maximizar la reutilización de las soluciones a subproblemas. A través de la correcta aplicación de este enfoque, se pueden alcanzar soluciones óptimas que son tanto elegantes como eficientes. # Algoritmo de Karatsuba El algoritmo de Karatsuba es una técnica eficiente para la multiplicación de números grandes, que se basa en el principio de Dividir y Conquistar. Fue descubierto por Anatolii Alexeevitch Karatsuba en 1960 y representa una mejora significativa sobre la multiplicación tradicional de columnas que se enseña en la escuela. ## Descripción Dado dos números grandes $x$ e $y$, que se pueden escribir como $x = 10^{n/2}a + b$ e $y = 10^{n/2}c + d$, donde $a$ y $c$ son la primera mitad de los dígitos de $x$ e $y$ respectivamente, y $b$ y $d$ son la segunda mitad. El algoritmo de Karatsuba permite calcular el producto $x \cdot y$ mediante la realización de solo tres multiplicaciones de números más pequeños, en lugar de cuatro. El producto se calcula como: $$x \cdot y = 10^n ac + 10^{n/2}(ad + bc) + bd$$ Pero en lugar de calcular $ad$ y $bc$ por separado, Karatsuba observó que se pueden obtener a partir de: $$ad + bc = (a + b)(c + d) - ac - bd$$ Así, las tres multiplicaciones que necesitamos son: 1. $ac$ 2. $bd$ 3. $(a + b) \cdot (c + d)$ ## Complejidad El algoritmo reduce la complejidad de la multiplicación de dos números de $n$ dígitos de $O(n^2)$, que es lo que se obtiene con el método tradicional, a aproximadamente $O(n^{1.585})$, utilizando el análisis de complejidad de Dividir y Conquistar. ## Conclusión El algoritmo de Karatsuba es un claro ejemplo de cómo la técnica de Dividir y Conquistar puede ser utilizada para desarrollar algoritmos más eficientes para problemas computacionales clásicos. Aunque para números pequeños el método tradicional puede ser más rápido debido a su simplicidad, para números grandes Karatsuba ofrece una mejora significativa en la eficiencia. # Teorema Maestro y Análisis Asintótico El Teorema Maestro proporciona una solución directa para las recurrencias que surgen al analizar algoritmos de Dividir y Conquistar, ofreciendo una manera de determinar la complejidad temporal de estos algoritmos de forma asintótica. ## Teorema Maestro El Teorema Maestro se aplica a recurrencias de la forma: $$T(n) = aT\left(\frac{n}{b}\right) + f(n)$$ donde, - $n$ es el tamaño del problema, - $a \geq 1$ es el número de subproblemas en la recurrencia, - $b > 1$ es el factor por el que se divide el tamaño del problema, - $f(n)$ es una función que representa el costo de dividir el problema y combinar los resultados de los subproblemas. El teorema establece tres casos para determinar la complejidad temporal de $T(n)$: 1. **Si $f(n) = O(n^{\log_b a - \epsilon})$ para algún $\epsilon > 0$, entonces $T(n) = \Theta(n^{\log_b a})$.** 2. **Si $f(n) = \Theta(n^{\log_b a})$, entonces $T(n) = \Theta(n^{\log_b a} \log n)$.** 3. **Si $f(n) = \Omega(n^{\log_b a + \epsilon})$ para algún $\epsilon > 0$, y si $a f\left(\frac{n}{b}\right) \leq k f(n)$ para alguna constante $k < 1$ y suficientemente grande $n$, entonces $T(n) = \Theta(f(n))$.** # Análisis Asintótico y Otras Formas de Calcularlo ## Análisis General El análisis asintótico se centra en entender el comportamiento de los algoritmos cuando el tamaño del problema crece indefinidamente. A diferencia del Teorema Maestro, que proporciona una solución directa para recurrencias específicas, existen otros enfoques para calcular la complejidad de los algoritmos de Dividir y Conquistar. ## Análisis por Caso Una técnica importante es el análisis por caso, donde se evalúa la complejidad bajo diferentes supuestos sobre los parámetros del algoritmo, como el número de subproblemas (`a`), el tamaño relativo de cada subproblema (`c`), y el costo de dividir el problema y combinar sus soluciones (`d`). Este enfoque permite entender cómo estas variables influyen en la complejidad final del algoritmo. ### Ejemplos de Análisis por Caso 1. **Cuando `a = 1` y `d = 0`**, es decir, hay un solo subproblema y combinar tiene un costo constante, la complejidad es `O(logc n)`. 2. **Cuando `d = 1`**, lo que implica que la división y la unión tienen un costo lineal, y dependiendo de la relación entre `a` y `c`, la complejidad puede variar significativamente: - **Si `a < c`** ("pocos subproblemas"), la serie converge y la complejidad es `O(n)`. - **Si `a = c`**, la complejidad es `O(n logc n)`. - **Si `a > c`** ("muchos subproblemas"), se utiliza una fórmula específica para calcular la complejidad, que puede resultar en complejidades mayores. ## Conclusión Además del Teorema Maestro, el análisis asintótico de algoritmos de Dividir y Conquistar puede realizarse mediante el análisis detallado de casos específicos, considerando la cantidad de subproblemas generados, el costo de las operaciones de división y combinación, y cómo estos factores contribuyen a la complejidad total. Este enfoque proporciona una comprensión más matizada de la eficiencia del algoritmo bajo diferentes condiciones.