Normalmente en los algoritmos no nos importa la comparación, la suma o la resta de números, suponemos que se ejecutan en el tiempo . Por ejemplo, suponemos esto cuando decimos que la clasificación basada en la comparación es , pero cuando los números son demasiado grandes para caber en los registros, normalmente los representamos como matrices, por lo que las operaciones básicas requieren cálculos adicionales por elemento.O ( n log n )
¿Hay alguna prueba que muestre que la comparación de dos números (u otras funciones aritméticas primitivas) se puede hacer en ? Si no, ¿por qué decimos que la ordenación basada en la comparación es ?O ( n log n )
Me encontré con este problema cuando contesté una pregunta SO y me di cuenta de que mi algoritmo no es porque tarde o temprano debería hacer frente a las grandes int, también no era algoritmo de tiempo seudo polinómica, que era .P
Respuestas:
Para las personas como yo que estudian algoritmos para ganarse la vida, el modelo estándar de computación del siglo XXI es la RAM entera . El modelo está destinado a reflejar el comportamiento de las computadoras reales con mayor precisión que el modelo de máquina de Turing. Las computadoras del mundo real procesan enteros de múltiples bits en tiempo constante utilizando hardware paralelo; no enteros arbitrarios , pero (debido a que el tamaño de las palabras crece constantemente con el tiempo) tampoco son enteros de tamaño fijo .
El modelo depende de un único parámetro , llamado tamaño de palabra . Cada dirección de memoria contiene un único número entero de bit, o palabra . En este modelo, el tamaño de entrada es el número de palabras en la entrada, y el tiempo de ejecución de un algoritmo es el número de operaciones en palabras . Las operaciones aritméticas estándar (suma, resta, multiplicación, división de enteros, resto, comparación) y operaciones booleanas (bit a bit y, o, xor, shift, rotate) en palabras requieren tiempo por definición .w n O ( 1 )w w n O(1)
Formalmente, el tamaño de palabra NO es una constantew para fines de análisis de algoritmos en este modelo. Para que el modelo sea coherente con la intuición, necesitamos , ya que de lo contrario ni siquiera podemos almacenar el entero en una sola palabra. Sin embargo, para la mayoría de los algoritmos no numéricos, el tiempo de ejecución es realmente independiente de , porque esos algoritmos no se preocupan por la representación binaria subyacente de su entrada. Mergesort y heapsort se ejecutan en tiempo ; la mediana de 3 clasificaciones rápidas se ejecuta en tiempo en el peor de los casos. Una excepción notable es la ordenación de radix binaria, que se ejecuta en tiempo .w≥log2n n w O(nlogn) O(n2) O(nw)
Establecer nos da el modelo tradicional de RAM de costo logarítmico. Pero algunos algoritmos de RAM entera están diseñados para tamaños de palabras más grandes, como el algoritmo de clasificación de enteros de tiempo lineal de Andersson et al. , que requiere .w=Θ(logn) w=Ω(log2+εn)
Para muchos algoritmos que surgen en la práctica, el tamaño de palabra simplemente no es un problema, y podemos (y lo hacemos) recurrir al modelo de RAM de costo uniforme mucho más simple. La única dificultad seria proviene de la multiplicación anidada, que puede ser usado para construir muy grandes números enteros muy rápidamente. Si pudiéramos realizar operaciones aritméticas en enteros arbitrarios en tiempo constante, podríamos resolver cualquier problema en PSPACE en tiempo polinómico .w
Actualización: también debo mencionar que hay excepciones al "modelo estándar", como el algoritmo de multiplicación de enteros de Fürer , que utiliza máquinas de Turing multitapa (o equivalente, el "bit RAM"), y la mayoría de los algoritmos geométricos, que se analizan teóricamente modelo limpio pero idealizado de "RAM real" .
Sí, esta es una lata de gusanos.
fuente
Solo depende del contexto. Cuando se trata de la complejidad del nivel de bits de los algoritmos, no decimos que la suma de dos números de bits es , decimos que es . Del mismo modo para la multiplicación, etc.O ( 1 ) O ( n )n O(1) O(n)
fuente
Para responder la pregunta como se indica: los algoritmos simplemente lo hacen, con bastante frecuencia, utilizando el modelo RAM. Para ordenar, en muchos casos, las personas incluso analizan el modelo de comparación más simple , que discuto un poco más en la respuesta vinculada.
Para responder a la pregunta implícita sobre por qué lo hacen: diría que este modelo tiene un poder predictivo bastante bueno para ciertos tipos de algoritmos combinatorios, en los que los números son todos "pequeños" y, en máquinas reales, caben en registros.
Para responder al seguimiento implícito sobre algoritmos numéricos: No, el modelo RAM simple y antiguo no es el estándar aquí. Incluso la eliminación gaussiana puede requerir algunos cuidados. Por lo general, para los cálculos de rango, ingresará el Lema de Schwartz (por ejemplo, la Sección 5 aquí ). Otro ejemplo canónico es el análisis del algoritmo elipsoide, que requiere un poco de cuidado para analizar.
Y finalmente: la gente había pensado en la clasificación de cadenas antes , incluso recientemente.
Actualización: El problema con esta pregunta es que "nosotros" y "asumir" no se especifican con tanta precisión. Yo diría que las personas que trabajan en el modelo RAM no están haciendo algoritmos numéricos o teoría de la complejidad (donde determinar la complejidad de la división fue un resultado celebrado ).
fuente
No pude encontrar ningún estudio sobre esto, pero Kozen dice en la introducción de "El diseño y análisis de algoritmos" que el modelo "refleja la observación experimental con mayor precisión [que el modelo de costo logarítmico] para datos de moderado tamaño (ya que la multiplicación realmente toma una unidad de tiempo) ". También da una referencia a este documento como un ejemplo de cómo se puede abusar del modelo .O(1) O(1)
Esta no es una evaluación legítima (sobre todo porque es Python), pero aquí hay algunos números10{1,2,...,66}
python -mtimeit "$a * $b"
para ejecutar$a
en y . (Paré en 66 porque ahí es cuando la sintaxis de Python deja de aceptar literales enteros y tendría que cambiar ligeramente mi código de evaluación, así que no lo hice.: P)$b = 2*$a
Cada número es la media de 10,000,000 bucles, donde toma el mejor tiempo de 3 carreras en cada bucle. Haría barras de error o algo así, pero eso sería más esfuerzo. : p En cualquier caso, me parece bastante constante, incluso hasta , algo sorprendente, ya que es 43, lo que refuerza mi sospecha de que tal vez esta evaluación es particularmente falsa y debería hacerlo en C.1050 log10(sys.maxint)
fuente
Tienes razón, en general no podemos asumir que son .O(1)
Hablando estrictamente, si queremos ordenar una matriz con N números usando comparaciones, y el número más grande es M, entonces, en el peor de los casos, cada comparación puede involucrar comparaciones en el nivel de bits. Y si nuestro algoritmo hace comparaciones , entonces su complejidad total será .O(logM) O(NlogN) O(NlogNlogM)
Sin embargo, notará la diferencia solo para valores muy grandes de , que no pueden almacenarse en un solo registro, como puede ver en el experimento de Dougal.M
fuente
Diría que normalmente asumimos operaciones aritméticas O (1) porque generalmente estamos haciendo cosas en el contexto de enteros de 32 bits o enteros de 64 bits o números de punto flotante IEEE 754. O (1) es probablemente una aproximación bastante buena para ese tipo de aritmética.
Pero en general, eso no es cierto. En general, necesita un algoritmo para realizar sumas, restas, multiplicaciones y divisiones. La lógica y la computabilidad de Boolos, Burgess and Jefferies se me viene a la mente como una forma de comprender las pruebas de eso, en términos de un par de sistemas formales diferentes, funciones recursivas y máquinas de ábaco, al menos, en mi cuarta edición.
Puede ver los términos de cálculo lambda para sustracción y división con Números de iglesia para obtener una explicación fácil de ver por qué esas dos operaciones no son O (1). Es un poco más difícil de ver para la suma, multiplicación y exponenciación, pero está ahí si consideras la forma de los números de la iglesia.
fuente