22 votos

Algoritmo de exponenciación rápida - ¿Cómo llegar a él?

He estado aprendiendo sobre la exponenciación rápida cuando encontré este algoritmo:

int expo(int a, int b) {
  int result = 1;

  while (b) {
    if (b%2==1) {
      result *= a;
    }
    b /= 2;
    a *= a;
  }

  return result;
}

Se supone que esto calcula $a^b$ en tiempo logarítmico, pero no pude entender ni encontrar en ningún lugar cómo llegar a este procedimiento, aparte de notar que (esta fórmula en particular no me ayudó a entender por qué el algoritmo es correcto):

$$ x^n = \begin{cases} 1 & \text{si $n=0$} \\ \frac{1}{x}^{-n} & \text{si $n<0$} \\ x \cdot \left( x^{\frac{n-1}{2}} \right)^2 & \text{si $n$ es impar} \\ \left( x^{\frac{n}{2}} \right)^2 & \text{si $n$ es par} \end{cases} $$

Por lo que entiendo, la transición de esta identidad particular al algoritmo real es bastante obvia, pero sinceramente no lo entiendo y he trabajado manualmente en bastantes ejemplos para este algoritmo.

¿Alguien puede explicar?

3 votos

Ejecuta el bucle manualmente para, por ejemplo, $b = 5$. Pista: $a^5 = a^{4+1} = (a^2)^2 \cdot a$. Observa que b%2 == 1 cuando el $n^{th}$ bit está encendido en la representación binaria de $b$.

0 votos

¿Eso equivale a algo más que el hecho de que $b$ sea impar en ese caso?

0 votos

Sí. Espero que la respuesta publicada lo explique mejor.

26voto

dxiv Puntos 1639

Escribe $b = b_0 + b_1 2 + b_2 2^2 + ... + b_n 2^n$ donde $b_k$ son los dígitos binarios de la representación de $b$ en base $2$. Ten en cuenta que $n$ varía de forma logarítmica con $b$. Entonces:

$$ a^b = a^{b_0} \cdot (a^2)^{b_1} \cdot (a^{2^2})^{b_2} \cdot ... \cdot (a^{2^n})^{b_n} $$

Los términos donde los bits $b_k = 0$ se evalúan como $1$ y pueden omitirse. Los términos con $b_k = 1$ son de la forma $a^{2^k}$ y se pueden calcular mediante el cuadrado repetido de $a$ $k$ veces. Esto es precisamente lo que hace el código publicado.

2 votos

¡Gracias, esa ecuación tuvo total sentido de inmediato!

1 votos

Para personas interesadas en matemáticas: Este argumento muestra que el algoritmo también funciona si a es un elemento de algún anillo arbitrario.

0 votos

No puedo entender lo que estás diciendo.

6voto

B. Goddard Puntos 2488

El algoritmo utiliza la expansión binaria del exponente para reducir la cantidad de multiplicaciones que uno tiene que hacer. Si tomas $a$ y lo elevas al cuadrado y luego lo vuelves a elevar al cuadrado y luego al cuadrado de nuevo, produces los números $a, a^2, a^4, a^8,\ldots,a^{2^k}$ hasta que $2^{k+1}>b$ es Entonces eso toma alrededor de $\log_2 b$ multiplicaciones. Luego, si expresas $b$ en binario, digamos, $b=1101001$, entonces $a^b = a^{2^6+2^5+2^3+2^0} = a^{2^6}a^{2^5}a^{2^3}a^{2^0}$, y acabas de calcular todos estos poderes de $a$, así que multiplícalos juntos y eso son alrededor de $\log_2 b$ multiplicaciones más.

0 votos

Muchas gracias. ¿Podrías por favor explicar qué parte del código selecciona los bits binarios donde se encuentran un 1 y luego evalúa $a^{2^k}$? Esta es la parte que aún me confunde, pero en general entiendo el enfoque.

0 votos

@Avra El código realmente no está seleccionando los bits binarios. Está utilizando el método de conversión a binario aquí: indepth.dev/posts/1019/… Creo que la mejor manera es trabajar a través de un par de ejemplos manualmente.

2voto

Bernard Puntos 34415

Apenas entiendo tu código, pero aquí está la descripción del algoritmo en pseudo-código: consideramos los valores sucesivos de los cuadrados, denotados por $S$, y por $P$ los valores sucesivos de las potencias del número $a:

Entrada: $\;a, n$

Salida: $\;a^n$

$S\leftarrow a$, $P\leftarrow a$ ;

Mientras $n>1$ hacer

$\qquad$Si impar($n$) $\;P\leftarrow P*S\enspace$ fin \begin{align*} &n\leftarrow \Bigl\lfloor n/2\Bigr\rfloor&\hspace25em\\ &S\leftarrow S^2 \end{align*}

FinMientras

$P\leftarrow P*S$.

Fin

0 votos

¿Encuentras el código mal formateado? Lo siento si es así, haré las ediciones apropiadas.

0 votos

Eso no es importante: solo soy un matemático, no un científico de la computación, y no conozco los códigos.

0 votos

En algun lugar en la traducción de código a seudocódigo algunos errores se filtraron: cuando se instancia, por ejemplo, con n = 2, se produce a^4 en lugar de a^2.

1voto

Patrick Stevens Puntos 5060

Si deseas hacer algo en tiempo logarítmico, probablemente necesites hacer algo con los dígitos del número en alguna base, en lugar de con el número en sí. La representación digital de un número es inherentemente un objeto logarítmico. Además, dado que $b$ es lo que determina principalmente cuán grande es $a^b$, probablemente necesitaremos pensar en $b$ de forma logarítmica, o de lo contrario no tendremos esperanzas de hacer que nuestro algoritmo sea logarítmico.

Si ahora consideras $b$ en base $2$, este es el algoritmo natural que resulta.

También se puede estar inspirado en el algoritmo de búsqueda binaria, que consiste en encontrar un elemento en una lista ordenada. Funciona dividiendo la lista en dos, determinando si el elemento está en la primera (respectivamente segunda) mitad de la lista, y repitiendo el algoritmo usando la lista ahora de la mitad de tamaño que es la primera (respectivamente segunda) mitad de la original. Esta idea de "dividir y conquistar" aparece por todas partes si queremos hacer cosas en tiempo logarítmico; el algoritmo que has dado es una respuesta natural a la pregunta "¿podemos dividir y conquistar esto?".

0 votos

Tal vez esto debería ser un comentario? No veo cómo resuelve o sugiere un enfoque al problema en cuestión..

0 votos

@jayno Considero que es una respuesta completa a "Se supone que esto debería calcular $a^b$ en tiempo logarítmico, pero no pude entender ni encontrar en ningún lugar una manera de llegar a este procedimiento, más que al observar que...". ¿Cuál es exactamente el problema que tienes, si no es ese, o es que mi explicación de alguna manera falla?

0 votos

Parece que el problema está en mi publicación original, lo siento. Voy a editar.

0voto

pol3waf Puntos 81

Intenta exponenciar solo sumando exponentes, hazlo en la menor cantidad de pasos posible. Luego piensa en cómo obtener un exponente real para realizar la multiplicación. La forma en que se hace en el algoritmo asegura que puedes reciclar cada variable en el algoritmo.

Vamos a definir nuestra función

slowExpo(x,y)

pasos:

  1. Genera una secuencia de sumas que sumen y. $y_i$
  2. Genera una secuencia de $x^{y_i}$.
  3. Multiplica cada miembro de la secuencia del paso 2.
  4. Devuelve la salida del paso 3, es x^y.

Esto funciona debido a las leyes de adición de exponentes.

$a^b * a^c = a^{b+c}$

La secuencia generada en tu algoritmo está diseñada para binario y para reciclar cada miembro de la secuencia generada, y realiza los pasos 1, 2 y 3 juntos "en pseudo-paralelo" en un solo bucle, esto reduce la cantidad de trabajo, porque las listas reales de secuencias no tienen que ser almacenadas/recuperadas y la versión exponenciada de $x^{y_i}$, no tiene que ser calculada mediante la recursividad como slowExpo(x,y_i).

i-Ciencias.com

I-Ciencias es una comunidad de estudiantes y amantes de la ciencia en la que puedes resolver tus problemas y dudas.
Puedes consultar las preguntas de otros usuarios, hacer tus propias preguntas o resolver las de los demás.

Powered by:

X