Aquí aprendemos una ingeniosa aplicación de un algoritmo de divide y vencerás combinado con álgebra lineal para multiplicar números grandes, rápidamente. Y una introducción a la notación Big-O!
De hecho, este resultado es tan contraintuitivo que el gran matemático Kolmogorov conjeturó que no existía un algoritmo tan rápido.
Parte 0: La multiplicación larga es una multiplicación lenta. (Y una introducción a la notación Big-O)
Todos recordamos haber aprendido a multiplicar en la escuela. De hecho, tengo que confesar algo. Recordad que esta es una zona sin juicios 😉
En el colegio, teníamos «juegos húmedos» cuando llovía demasiado para jugar en el patio. Mientras los otros niños (normales/amantes de la diversión/adjetivo positivo de elección) jugaban y hacían el tonto, yo a veces cogía una hoja de papel y multiplicaba números grandes juntos, sólo por el gusto de hacerlo. El puro placer del cálculo. Incluso teníamos una competición de multiplicación, el «Super 144», en el que teníamos que multiplicar una cuadrícula de 12 por 12 (del 1 al 12) lo más rápido posible. Lo practicaba religiosamente, hasta el punto de que tenía mis propias hojas de práctica preparadas de antemano, que fotocopiaba y utilizaba por turnos. Al final conseguí bajar el tiempo a 2 minutos y 1 segundo, momento en el que se alcanzó mi límite físico de garabatos.
A pesar de esta obsesión por la multiplicación, nunca se me ocurrió que pudiéramos hacerlo mejor. Pasé muchas horas multiplicando, pero nunca intenté mejorar el algoritmo.
Consideremos la multiplicación de 103 por 222.
Calculamos 2 veces 3 veces 1 +2 veces 0 veces 10 + 2 veces 1 veces 100 + 20 veces 3 veces 1 + 20 veces 0 veces 10 + 20 veces 1 veces 100 = 22800
En general, para multiplicar a números de n cifras, se necesitan O(n²) operaciones. La notación ‘big-O’ significa que, si tuviéramos que graficar el número de operaciones en función de n, el término n² es realmente lo que importa. La forma es esencialmente cuadrática.
Hoy vamos a cumplir todos nuestros sueños de la infancia y mejorar este algoritmo. Pero antes. Big-O nos espera.
Big-O
Mira las dos gráficas de abajo
Pregúntale a un matemático, «¿son x² y x²+x^(1/3) lo mismo?», y probablemente vomitará en su taza de café y empezará a llorar, y murmurará algo sobre topología. Al menos, esa fue mi reacción (al principio), y apenas sé nada de topología.
Ok. No son lo mismo. Pero a nuestro ordenador no le importa la topología. Los ordenadores difieren en rendimiento de tantas maneras que ni siquiera puedo describir porque no sé nada de ordenadores.
Pero no tiene sentido afinar una apisonadora. Si dos funciones, para valores grandes de su entrada, crecen en el mismo orden de magnitud, entonces a efectos prácticos eso contiene la mayor parte de la información relevante.
En la gráfica anterior, vemos que las dos funciones se parecen bastante para entradas grandes. Pero igualmente, x² y 2x² son similares – esta última es sólo dos veces más grande que la primera. A x = 100, una es de 10.000 y la otra de 20.000. Eso significa que si podemos calcular una, probablemente podamos calcular la otra. Mientras que una función que crece exponencialmente, digamos 10^x, a x = 100 ya es mucho mayor que el número de átomos del universo. (10¹⁰⁰ >>> 10⁸⁰ que es una estimación, el número de Eddington, sobre el número de átomos en el universo según mi búsqueda en Google*).
*en realidad, usé DuckDuckGo.
Escribir números enteros como polinomios es muy natural y muy poco natural. Escribir 1342 = 1000 + 300 + 40 + 2 es muy natural. Escribir 1342 = x³+3x²+4x + 2, donde x = 10, es ligeramente extraño.
En general, supongamos que queremos multiplicar dos números grandes, escritos en base b, con n dígitos cada uno. Escribimos cada número de n cifras como un polinomio. Si dividimos el número de n dígitos en r trozos, este polinomio tiene n/r términos.
Por ejemplo, dejemos que b = 10, r = 3, y nuestro número es 142.122.912
Podemos convertir esto en
Esto funciona muy bien cuando r = 3 y nuestro número, escrito en base 10, tenía 9 dígitos. Si r no divide perfectamente a n, entonces podemos añadir algunos ceros al principio. De nuevo, esto se ilustra mejor con un ejemplo.
27134 = 27134 + 0*10⁵ = 027134, que representamos como