En informática , la complejidad computacional , o simplemente complejidad, de un algoritmo es la cantidad de recursos necesarios para ejecutarlo. [ 1 ] Se presta especial atención al tiempo de cálculo (generalmente medido por el número de operaciones elementales necesarias) y a los requisitos de memoria . La complejidad de un problema es la complejidad de los mejores algoritmos que permiten resolverlo.
El estudio de la complejidad de algoritmos explícitamente dados se denomina análisis de algoritmos , mientras que el estudio de la complejidad de problemas se denomina teoría de la complejidad computacional . Ambas áreas están estrechamente relacionadas, ya que la complejidad de un algoritmo siempre representa una cota superior de la complejidad del problema que resuelve. Además, para diseñar algoritmos eficientes, suele ser fundamental comparar la complejidad de un algoritmo específico con la complejidad del problema a resolver. Asimismo, en la mayoría de los casos, lo único que se sabe sobre la complejidad de un problema es que no supera la complejidad de los algoritmos más eficientes conocidos. Por lo tanto, existe una gran superposición entre el análisis de algoritmos y la teoría de la complejidad.
Como la cantidad de recursos necesarios para ejecutar un algoritmo generalmente varía con el tamaño de la entrada, la complejidad se expresa típicamente como una función n ↦ f ( n ) , donde n es el tamaño de la entrada y f ( n ) es la complejidad del peor caso (el máximo de la cantidad de recursos que se necesitan para todas las entradas de tamaño n ) o la complejidad del caso promedio (el promedio de la cantidad de recursos para todas las entradas de tamaño n ). La complejidad temporal generalmente se expresa como el número de operaciones elementales requeridas en una entrada de tamaño n , donde se supone que las operaciones elementales toman una cantidad constante de tiempo en una computadora dada y cambian solo por un factor constante cuando se ejecutan en una computadora diferente. La complejidad espacial generalmente se expresa como la cantidad de memoria requerida por un algoritmo en una entrada de tamaño n .
Recursos
Tiempo
El recurso que se suele tener en cuenta es el tiempo. Cuando se utiliza el término "complejidad" sin ninguna especificación, generalmente se refiere a la complejidad temporal.
Las unidades de tiempo habituales (segundos, minutos, etc.) no se utilizan en la teoría de la complejidad porque dependen demasiado de la elección de un ordenador específico y de la evolución de la tecnología. Por ejemplo, un ordenador actual puede ejecutar un algoritmo significativamente más rápido que uno de la década de 1960; sin embargo, esto no es una característica intrínseca del algoritmo, sino una consecuencia de los avances tecnológicos en el hardware informático . La teoría de la complejidad busca cuantificar los requisitos de tiempo intrínsecos de los algoritmos, es decir, las restricciones de tiempo básicas que un algoritmo impondría a cualquier ordenador. Esto se logra contando el número de operaciones elementales que se ejecutan durante el cálculo. Se supone que estas operaciones toman un tiempo constante (es decir, no se ven afectadas por el tamaño de la entrada) en una máquina determinada, y a menudo se denominan pasos .
Complejidad de bits
Formalmente, la complejidad de bits se refiere al número de operaciones con bits necesarias para ejecutar un algoritmo. En la mayoría de los modelos de computación , es igual a la complejidad temporal, salvo por un factor constante. En las computadoras , el número de operaciones con palabras de máquina necesarias también es proporcional a la complejidad de bits. Por lo tanto, la complejidad temporal y la complejidad de bits son equivalentes en modelos de computación realistas.
Espacio
Otro recurso importante es el tamaño de la memoria de la computadora necesaria para ejecutar los algoritmos.
Circuito
Comunicación
Para la clase de algoritmos distribuidos que suelen ser ejecutados por múltiples partes que interactúan entre sí, el recurso de mayor interés es la complejidad de la comunicación. Se trata de la cantidad necesaria de comunicación entre las partes ejecutoras.
Otros
El número de operaciones aritméticas es otro recurso de uso común. En este caso, se habla de complejidad aritmética . Si se conoce un límite superior para el tamaño de la representación binaria de los números que aparecen durante un cálculo, la complejidad temporal suele ser el producto de la complejidad aritmética por un factor constante.
Para muchos algoritmos, el tamaño de los enteros que se utilizan durante un cálculo no está acotado, y no es realista considerar que las operaciones aritméticas toman un tiempo constante. Por lo tanto, la complejidad temporal, generalmente llamada complejidad de bits en este contexto, puede ser mucho mayor que la complejidad aritmética. Por ejemplo, la complejidad aritmética del cálculo del determinante de una matriz de enteros n × n espara los algoritmos habituales ( eliminación gaussiana ). La complejidad de bits de los mismos algoritmos es exponencial en n , porque el tamaño de los coeficientes puede crecer exponencialmente durante el cálculo. Por otro lado, si estos algoritmos se acoplan con aritmética multimodular , la complejidad de bits puede reducirse a Õ ( n 4 ) .
En las tareas de ordenación y búsqueda , el recurso que generalmente se considera es el número de comparaciones de entradas. Esto suele ser una buena medida de la complejidad temporal si los datos están organizados adecuadamente.
Complejidad en función del tamaño de entrada.
Es imposible contar el número de pasos de un algoritmo para todas las entradas posibles. Dado que la complejidad generalmente aumenta con el tamaño de la entrada, se suele expresar como una función del tamaño n (en bits ) de la entrada; por lo tanto, la complejidad es una función de n . Sin embargo, la complejidad de un algoritmo puede variar drásticamente para diferentes entradas del mismo tamaño. Por consiguiente, se suelen utilizar diversas funciones de complejidad.
La complejidad en el peor de los casos es el máximo de la complejidad para todas las entradas de tamaño n , y la complejidad en el caso promedio es el promedio de la complejidad para todas las entradas de tamaño n (esto tiene sentido, ya que el número de posibles entradas de un tamaño dado es finito). Generalmente, cuando se usa el término "complejidad" sin especificarlo, se considera la complejidad temporal en el peor de los casos.
Complejidad asintótica
Generalmente, resulta difícil calcular con precisión la complejidad en el peor de los casos y en el caso promedio. Además, estos valores exactos tienen poca utilidad práctica, ya que cualquier cambio de ordenador o de modelo de cálculo modificaría la complejidad. Asimismo, el uso de recursos no es crítico para valores pequeños de n , lo que implica que, para valores pequeños de n , la facilidad de implementación suele ser más importante que una baja complejidad.
Por estas razones, generalmente uno se centra en el comportamiento de la complejidad para valores grandes de n , es decir, en su comportamiento asintótico cuando n tiende al infinito. Por lo tanto, la complejidad generalmente se expresa utilizando la notación O grande .
Por ejemplo, el algoritmo habitual para la multiplicación de enteros tiene una complejidad deEsto significa que hay una constantede tal manera que la multiplicación de dos enteros de como máximo n dígitos se pueda realizar en un tiempo menor queEste límite es preciso en el sentido de que la complejidad del peor caso y la complejidad del caso promedio sonlo que significa que también hay una constantede tal manera que estas complejidades son mayores queLa base no aparece en estas complejidades, ya que al cambiar la base solo cambian las constantes.y
Modelos de computación
La evaluación de la complejidad se basa en la elección de un modelo de computación , que consiste en definir las operaciones básicas que se realizan en una unidad de tiempo. Cuando el modelo de computación no se especifica explícitamente, generalmente se asume implícitamente que es una máquina de Turing de cintas múltiples , ya que varios modelos de computación más realistas, como las máquinas de acceso aleatorio, son asintóticamente equivalentes para la mayoría de los problemas. Solo para problemas muy específicos y difíciles, como la multiplicación de enteros en tiempoque la definición explícita del modelo de computación es necesaria para las demostraciones.
Modelos deterministas
Un modelo determinista de computación es aquel en el que los estados sucesivos de la máquina y las operaciones a realizar están completamente determinados por el estado precedente. Históricamente, los primeros modelos deterministas fueron las funciones recursivas , el cálculo lambda y las máquinas de Turing . El modelo de máquinas de acceso aleatorio (también llamadas máquinas RAM) también se utiliza ampliamente, como una contraparte más cercana a las computadoras reales .
Cuando no se especifica el modelo de computación, generalmente se asume que se trata de una máquina de Turing de cintas múltiples . Para la mayoría de los algoritmos, la complejidad temporal es la misma en las máquinas de Turing de cintas múltiples que en las máquinas de RAM, aunque puede ser necesario tener cuidado con la forma en que se almacenan los datos en la memoria para lograr esta equivalencia.
Computación no determinista
En un modelo de computación no determinista , como las máquinas de Turing no deterministas , se pueden tomar decisiones en ciertos pasos del cálculo. En la teoría de la complejidad, se consideran todas las opciones posibles simultáneamente, y la complejidad temporal no determinista es el tiempo necesario cuando siempre se toman las mejores decisiones. En otras palabras, se considera que el cálculo se realiza simultáneamente en tantos procesadores (idénticos) como sean necesarios, y el tiempo de computación no determinista es el tiempo que emplea el primer procesador que finaliza el cálculo. Este paralelismo es parcialmente compatible con la computación cuántica mediante estados entrelazados superpuestos al ejecutar algoritmos cuánticos específicos , como por ejemplo el algoritmo de Shor para encontrar los factores primos de un número entero.
Aunque dicho modelo computacional aún no sea realista, tiene importancia teórica, principalmente relacionada con el problema P = NP , que cuestiona la identidad de las clases de complejidad formadas al tomar "tiempo polinomial" y "tiempo polinomial no determinista" como límites superiores. Simular un algoritmo NP en una computadora determinista generalmente requiere "tiempo exponencial". Un problema pertenece a la clase de complejidad NP si puede resolverse en tiempo polinomial en una máquina no determinista. Un problema es NP-completo si, en términos generales, pertenece a NP y no es más fácil que cualquier otro problema NP. Muchos problemas combinatorios , como el problema de la mochila , el problema del viajante y el problema de satisfacibilidad booleana , son NP-completos. Para todos estos problemas, el mejor algoritmo conocido tiene complejidad exponencial. Si cualquiera de estos problemas pudiera resolverse en tiempo polinomial en una máquina determinista, entonces todos los problemas NP también podrían resolverse en tiempo polinomial, y se tendría P = NP. A partir de 2017Generalmente se conjetura que P ≠ NP, con la implicación práctica de que los peores casos de problemas NP son intrínsecamente difíciles de resolver, es decir, requieren más tiempo que cualquier lapso de tiempo razonable (¡décadas!) para longitudes de entrada interesantes.
Computación paralela y distribuida
La computación paralela y distribuida consiste en dividir el cálculo entre varios procesadores que trabajan simultáneamente. La diferencia entre ambos modelos radica principalmente en la forma de transmitir la información entre ellos. Normalmente, en la computación paralela la transmisión de datos entre procesadores es muy rápida, mientras que en la computación distribuida, la transmisión se realiza a través de una red y, por lo tanto, es mucho más lenta.
El tiempo necesario para un cálculo en N procesadores es al menos el cociente entre N del tiempo que requiere un solo procesador. De hecho, este límite teóricamente óptimo no se puede alcanzar en general, ya que algunas subtareas no se pueden paralelizar y algunos procesadores pueden tener que esperar el resultado de otro procesador.
El principal problema de complejidad consiste, por tanto, en diseñar algoritmos de tal forma que el producto del tiempo de cálculo por el número de procesadores sea lo más cercano posible al tiempo necesario para el mismo cálculo en un solo procesador.
Computación cuántica
Una computadora cuántica es aquella cuyo modelo de computación se basa en la mecánica cuántica . La tesis de Church-Turing se aplica a las computadoras cuánticas; es decir, todo problema que pueda resolver una computadora cuántica también puede ser resuelto por una máquina de Turing. Sin embargo, algunos problemas podrían resolverse teóricamente con una complejidad temporal mucho menor utilizando una computadora cuántica que una clásica. Por el momento, esto es puramente teórico, ya que nadie sabe cómo construir una computadora cuántica eficiente.
La teoría de la complejidad cuántica se ha desarrollado para estudiar las clases de complejidad de los problemas resueltos mediante ordenadores cuánticos. Se utiliza en la criptografía postcuántica , que consiste en diseñar protocolos criptográficos resistentes a los ataques de los ordenadores cuánticos.
Complejidad del problema
De manera informal, la complejidad de un problema se define como la complejidad mínima de todos los algoritmos, incluidos los desconocidos, que lo resuelven, aunque dicha complejidad mínima podría no existir. Por lo tanto, la complejidad de un problema no es mayor que la complejidad de ningún algoritmo que lo resuelva.
De ello se deduce que cualquier complejidad de un algoritmo que se exprese con la notación O grande , es también una cota superior para la complejidad del problema correspondiente.
Por otro lado, generalmente es difícil obtener límites inferiores no triviales para la complejidad del problema, y existen pocos métodos para obtener dichos límites inferiores.
Para resolver la mayoría de los problemas, es necesario leer todos los datos de entrada, lo que, normalmente, requiere un tiempo proporcional al tamaño de los datos. Por lo tanto, dichos problemas tienen una complejidad que es al menos lineal , es decir, utilizando la notación omega grande , una complejidad
La solución de algunos problemas, típicamente en álgebra computacional y geometría algebraica computacional , puede ser muy grande. En tal caso, la complejidad está limitada inferiormente por el tamaño máximo de la salida, ya que la salida debe escribirse. Por ejemplo, un sistema de n ecuaciones polinómicas de grado d en n indeterminadas puede tener hastasoluciones complejas , si el número de soluciones es finito (este es el teorema de Bézout ). Como estas soluciones deben escribirse, la complejidad de este problema esPara este problema, un algoritmo de complejidades conocido, lo que puede considerarse como asintóticamente óptimo.
Un límite inferior no lineal dees conocido por el número de comparaciones necesarias para un algoritmo de ordenación . Por lo tanto, los mejores algoritmos de ordenación son asintóticamente óptimos, ya que su complejidad esEste límite inferior resulta del hecho de que hay n ! maneras de ordenar n objetos. Como cada comparación divide en dos partes este conjunto de n ! órdenes, el número de comparaciones N que se necesitan para distinguir todos los órdenes debe validarselo cual implicapor la fórmula de Stirling .
Un método estándar para demostrar cotas inferiores para la complejidad de los problemas consiste en reducir un problema a otro. Más precisamente, supongamos que se puede codificar un problema A de tamaño n en un subproblema de tamaño f ( n ) de un problema B , y que la complejidad de A esSin pérdida de generalidad, se puede suponer que la función f aumenta con n y tiene una función inversa h . Entonces, la complejidad del problema B esEste es el método que se utiliza para demostrar que, si P ≠ NP (una conjetura sin resolver), la complejidad de cada problema NP-completo espara cada entero positivo k .
Uso en el diseño de algoritmos
Evaluar la complejidad de un algoritmo es una parte importante del diseño de algoritmos , ya que proporciona información útil sobre el rendimiento que se puede esperar.
Es un error común pensar que la evaluación de la complejidad de los algoritmos se volverá menos importante como resultado de la ley de Moore , que postula el crecimiento exponencial de la potencia de las computadoras modernas . Esto es incorrecto porque este aumento de potencia permite trabajar con grandes cantidades de datos de entrada ( big data ). Por ejemplo, cuando se quiere ordenar alfabéticamente una lista de unos cientos de entradas, como la bibliografía de un libro, cualquier algoritmo debería funcionar bien en menos de un segundo. Por otro lado, para una lista de un millón de entradas (los números de teléfono de una gran ciudad, por ejemplo), los algoritmos elementales que requierenLas comparaciones tendrían que hacer un billón de comparaciones, lo que necesitaría alrededor de tres horas a una velocidad de 10 millones de comparaciones por segundo. Por otro lado, el ordenamiento rápido y el ordenamiento por fusión solo requierencomparaciones (como complejidad en el caso promedio para el primero, como complejidad en el peor caso para el segundo). Para n = 1.000.000 , esto da aproximadamente 30.000.000 de comparaciones, lo que solo tomaría 3 segundos a 10 millones de comparaciones por segundo.
De este modo, la evaluación de la complejidad permite descartar muchos algoritmos ineficientes antes de su implementación. Esto también puede utilizarse para optimizar algoritmos complejos sin necesidad de probar todas las variantes. Al determinar los pasos más costosos de un algoritmo complejo, el estudio de la complejidad permite centrar los esfuerzos para mejorar la eficiencia de la implementación en dichos pasos.
Véase también
Referencias
- ↑ Vadhan, Salil (2011), "Complejidad computacional" (PDF) , en van Tilborg, Henk CA; Jajodia, Sushil (eds.), Enciclopedia de criptografía y seguridad , Springer, pp. 235–240 , doi : 10.1007/978-1-4419-5906-5_442 , ISBN 9781441959065
- Arora, Sanjeev ; Barak, Boaz (2009), Complejidad computacional: un enfoque moderno , Cambridge , ISBN 978-0-521-42426-4, Zbl 1193.68112
- Calude, Cristian (1988), Teorías de la complejidad computacional , Elsevier , pág. 487, ISBN 9780444703569
- Du, Ding-Zhu; Ko, Ker-I (2000), Teoría de la complejidad computacional , John Wiley & Sons , ISBN 978-0-471-34506-0ISSN 0167-5060
- Garey, Michael R.; Johnson , David S. (1979), Computers and Intractability: A Guide to the Theory of NP-Completeness , Serie de libros en ciencias matemáticas (1.ª ed.), Nueva York: WH Freeman and Company , ISBN 9780716710455, MR 0519066 , OCLC 247570676
- Goldreich, Oded (2008), Complejidad computacional: una perspectiva conceptual , Cambridge University Press
- van Leeuwen, Jan , ed. (1990), Manual de informática teórica (vol. A): algoritmos y complejidad , MIT Press , ISBN 978-0-444-88071-0
- Papadimitriou, Christos (1994), Complejidad computacional (1.ª ed.), Addison Wesley, ISBN 0-201-53082-1
- Sipser, Michael (2006), Introducción a la teoría de la computación (2.ª ed.), EE. UU.: Thomson Course Technology , ISBN 0-534-95097-3
- Análisis de algoritmos
- Teoría de la complejidad computacional
- Recursos computacionales