Los programadores deben aprender: algoritmo de potencia rápida

Hace algún tiempo, algunos amigos

dejaron un mensaje bajo el algoritmo tutorial de mi estación B. Si tiene alguna pregunta o desea explicar algo, puede dejar un mensaje en mi estación B personal o cuenta pública (xmg_mj) Capacidad, trate de tomarse el tiempo para escribir artículos \ grabar videos para responder a todos.

Sobre potencia rápida

De hecho, el problema relacionado con la potencia rápida es un pequeño contenido básico que deben dominar los pequeños socios que participan en la competencia de algoritmos (NOI, ACM, etc.). Por supuesto, incluso si no planea participar en la competencia de algoritmos, personalmente considere que mientras sea un programador, debe dominar el algoritmo de potencia rápida.

En el libro "The Art of Computer Programming", se menciona el algoritmo de potencia rápida. El nombre en inglés de este libro es The Art of Computer Programming, o TAOCP para abreviar.

TAOCP proviene de las manos de Donald Ervin Knuth . Senior Knuth es un conocido científico que ha logrado muchos logros en el campo de la informática. Es uno de los inventores del famoso algoritmo KMP. En 1974, ganó el "Premio Nobel en el campo de la informática": Premio Turing (tenía solo 36 años). Actualmente, TAOCP ha publicado los volúmenes 1, 2, 3 y 4A, y de acuerdo con el plan, los volúmenes 4B, 5, 6 y 7 no se han publicado. El primer volumen se publicó por primera vez en 1968. Knuth tiene 82 años este año y se dice que planea completar la obra maestra antes de los 105 años.

Sobre TAOCP, el fundador de Microsoft Bill Gates dijo una vez

Si crees que eres un buen programador ... lee el Arte de la programación informática de (Knuth) ... Definitivamente deberías enviarme un currículum si puedes leer todo.

Probablemente significa: si crees que eres un muy buen programador, deberías leer TNUCP of Knuth; si puedes leer todo, puedes enviarme un currículum directamente. Se dice que las palabras de Senior Knuth son más agudas: ¡No seas un programador si no entiendes! Sin embargo, TAOCP es realmente difícil de leer para los principiantes. Todos los ejemplos en el libro usan el lenguaje ensamblador MIX de Knuth .

Lee el recordatorio antes de este artículo

Tómese el tiempo para escribir un artículo hoy para explicar el clásico algoritmo de potencia rápida. Sin embargo, para comprender a fondo este artículo, hay varios requisitos previos

  • Familiarizado con los 2 conceptos básicos del algoritmo: complejidad temporal, complejidad espacial
    • Si no ha escuchado estos dos conceptos, significa que su base de algoritmo es completamente 0, ¡y realmente no está bromeando!
    • Puede enviar la complejidad a la cuenta pública para obtener tutoriales relacionados
  • Familiarizado con la conversión binaria y decimal
    • Si no está familiarizado con esto, entonces su base de programación realmente necesita ser completada.
    • Puede enviar un número hexadecimal a la cuenta pública para obtener tutoriales relacionados
  • Familiarizado con operaciones de bit comunes
    • El resultado de n & 1 es el valor del bit binario más bajo de n, que también puede usarse para juzgar la paridad de n
    • Encuentre el entero positivo n / 2, puede ser reemplazado por la operación de bit: n >> 1
    • Si no comprende el principio de la operación anterior, puede enviar una operación un poco a la cuenta pública para obtener tutoriales relacionados

¿Qué es el poder?

Como todos sabemos, la enésima potencia de x se refiere a la enésima potencia de x, es decir, la multiplicación de n xs. Por ejemplo, la potencia de 2 es 2 * 2 * 2 * 2.

Para simplificar la descripción, la potencia de n en la parte posterior de x se simplificará a x ^ n (^ en este artículo no significa XOR bit a bit)

Entonces, ¿cómo buscar poder a través de la programación? Suponiendo que solo x y n son enteros yn es mayor o igual a 0 , la forma más fácil de pensar es la siguiente (el lenguaje de programación utilizado aquí es Java, pero no hay una sintaxis especial de Java involucrada. Entonces, incluso si no ha utilizado Java Puede entender)

int power(int x, int n) {
    int result = 1;
    while (n-- > 0) {
        result *= x;
    }
    return result;
}

Obviamente, la complejidad temporal de este método es O (n) y la complejidad espacial es O (1)

¿Qué es el poder rápido?

La llamada potencia rápida es utilizar un método más eficiente (menor complejidad de tiempo) para encontrar la potencia, y la complejidad de tiempo puede optimizarse a O (log) . Aquí hay dos soluciones: recursivas, no recursivas

Recursivo

De acuerdo con la ecuación de la figura anterior, no es difícil escribir el siguiente código

int fastPower(int x, int n) {
    if (n == 0) return 1;
    int result = fastPower(x, n >> 1);
    result *= result;
    return (n & 1) == 0 ? result : result * x;
}

La complejidad de tiempo y espacio de este método son O (logn) .

¿Cómo analizar la complejidad de este método?

Si su algoritmo es débil, puede sustituir un valor específico por un análisis aproximado. Por ejemplo, cuando n es 16, el proceso de llamada recursiva del método se muestra en la siguiente figura.

No es difícil ver que el tamaño de n se reduce a la mitad cada vez que se llama, por lo que la complejidad del tiempo y el espacio son O (logn)

Si su algoritmo es bueno, puede usar un método más profesional para analizar su complejidad (sin una cierta base de algoritmo, es posible que no lo entienda)

  • Este es en realidad un algoritmo típico para aplicar la estrategia de divide y vencerás
  • Suponiendo que T (n) es la complejidad temporal cuando el tamaño de los datos es n, no es difícil derivar la fórmula de recursión: T (n) = T (n / 2) + O (1)
  • Finalmente, de acuerdo con la recursividad + teorema maestro (Master Theorem), podemos sacar directamente la conclusión: T (n) = O (logn)

No recursivo

Tomemos 3 ^ 21 como ejemplo para analizar cómo se debe escribir el código no recursivo.

Primero la forma binaria de 21 es 10101

No es difícil sacar las siguientes conclusiones

  • 3 ^ n (n es 2, 4, 8, 16) se puede multiplicar por 3 ^ 1
  • Cada 3 ^ n tiene un bit binario correspondiente
    • 3 ^ 1 corresponde a un valor de bit binario de 1, que en realidad es el último bit de 10101 binario
    • 3 ^ 2 corresponde a un valor binario de 0, que en realidad es el último bit de 1010 binario
    • 3 ^ 4 corresponde a un valor binario de 1, que en realidad es el último bit del binario 101
    • 3 ^ 8 corresponde a un valor binario de 0, que en realidad es el último bit del binario 10
    • 3 ^ 16 corresponde a un valor binario de 1, que en realidad es el último bit del binario 1 .
  • Si 3 ^ n corresponde a un valor binario de 0 , entonces no hay necesidad de multiplicar el resultado final
    • Por ejemplo, 3 ^ (8 * 0 ), 3 ^ (2 * 0 )
    • Debido a que su valor final es 3 ^ 0, que es 1
  • Si 3 ^ n corresponde a un valor binario de 1 , debe multiplicarse en el resultado final
    • Por ejemplo, 3 ^ (16 * 1 ), 3 ^ (4 * 1 ), 3 ^ (1 * 1 )

Por lo tanto, en base a las conclusiones anteriores, se pueden resumir los siguientes pasos para resolver problemas

  • Usando 3 ^ 1, sigue acumulando 3 ^ n (n es 2, 4, 8, 16)
  • Siempre que se acumule un 3 ^ n, verifique si el valor binario correspondiente es 1 o 0 para decidir si multiplicarlo en el resultado final
int fastPower(int x, int n) {
    int result = 1;
    while (n != 0) {
        if ((n & 1) == 1) {
            result *= x;
        }
        x *= x;
        n >>= 1;
    }
    return result;
}

Sustituyendo 3 y 21, el flujo de ejecución de fastPower (3, 21) es el siguiente

Ronda 1 mientras bucle

  • Código de línea 4
    • El binario de n es 10101 (el decimal es 21)
    • x = 3 ^ 1, el valor del bit binario correspondiente es 1 (el último bit binario de n)
    • Entonces necesitamos ejecutar la quinta línea de código: multiplicar x en el resultado final
    • resultado = 3 ^ 1
  • Código de línea 7
    • x = (3 ^ 1) * (3 ^ 1) = 3 ^ 2
  • Código de línea 8
    • n se desplaza a la derecha 1 bit, y su binario se convierte en 1010 (¿cuál es el sistema decimal correspondiente? ¡¡¡No es importante !!!)

Ronda 2 mientras bucle

  • Código de línea 4
    • El binario de n es 1010
    • x = 3 ^ 2, el valor del bit binario correspondiente es 0 (el último bit binario de n)
    • Por lo tanto, no es necesario ejecutar la quinta línea de código: no es necesario multiplicar x en el resultado final
    • resultado = 3 ^ 1
  • Código de línea 7
    • x = (3 ^ 2) * (3 ^ 2) = 3 ^ 4
  • Código de línea 8
    • n se desplaza a la derecha 1 bit, y su binario se convierte en 101 (¿cuál es el sistema decimal correspondiente? ¡¡¡¡No es importante !!!)

Ronda 3 mientras bucle

  • Código de línea 4
    • El binario de n es 101
    • x = 3 ^ 4, el valor del bit binario correspondiente es 1 (el último bit binario de n)
    • Entonces necesitamos ejecutar la quinta línea de código: multiplicar x en el resultado final
    • resultado = (3 ^ 1) * (3 ^ 4)
  • Código de línea 7
    • x = (3 ^ 4) * (3 ^ 4) = (3 ^ 8)
  • Código de línea 8
    • n se desplaza a la derecha 1 bit, y su binario se convierte en 10 (¿cuál es el sistema decimal correspondiente? ¡¡¡No es importante !!!)

Ronda 4 mientras bucle

  • Código de línea 4
    • El binario de n es 10
    • x = 3 ^ 8, el valor del bit binario correspondiente es 0 (el último bit binario de n)
    • Por lo tanto, no es necesario ejecutar la quinta línea de código: no es necesario multiplicar x en el resultado final
    • resultado = (3 ^ 1) * (3 ^ 4)
  • Código de línea 7
    • x = (3 ^ 8) * (3 ^ 8) = 3 ^ 16
  • Código de línea 8
    • n se desplaza a la derecha 1 bit, y su binario se convierte en 1 (¿cuál es el sistema decimal correspondiente? ¡¡¡¡No es importante !!!)

Ronda 5 mientras bucle

  • Código de línea 4
    • binario de n es 1
    • x = 3 ^ 16, el valor del bit binario correspondiente es 1 (el último bit binario de n)
    • Entonces necesitamos ejecutar la quinta línea de código: multiplicar x en el resultado final
    • resultado = (3 ^ 1) * (3 ^ 4) * (3 ^ 16)
  • Código de línea 7
    • x = (3 ^ 16) * (3 ^ 16) = 3 ^ 32
  • Código de línea 8
    • n desplazado a la derecha 1 bit, su binario se convierte en 0

Pasado

  • Como n = 0, salga del ciclo while
  • Resultado final = (3 ^ 1) * (3 ^ 4) * (3 ^ 16)
  • Análisis de complejidad.
    • Cada vez que se ejecuta el cuerpo del ciclo while, n >> = 1, hará que el valor de n se reduzca a la mitad
    • Entonces, la complejidad del tiempo: O (log) , la complejidad del espacio: O (1)

Leetcode

Pregunta 50. Pow (x, n) en Leetcode , solo use el algoritmo de potencia rápida explicado hoy. La siguiente es mi implementación de código

// 递归
public double myPow(double x, int n) {
    if (n == 0) return 1;
    if (n == -1) return 1 / x;
    double half = myPow(x, n >> 1);
    half *= half;
    return ((n & 1) == 1) ? half * x : half;
}

// 非递归
public double myPow(double x, int n) {
    long y = (n < 0) ? -((long) n) : n;
    double result = 1.0;
    while (y > 0) {
        if ((y & 1) == 1) {
            result *= x;
        }
        x *= x;
        y >>= 1;
    }
    return (n < 0) ? (1 / result) : result;
}

Lo que hay que recordar es

  • El lenguaje de programación que uso aquí es Java. Puede ajustar algunos detalles gramaticales de acuerdo con su lenguaje de programación familiar.
  • El n en Leetcode puede ser un número negativo, por lo que el código anterior procesa un poco para el caso de números negativos

Preguntas más rápidas relacionadas con el poder

El tiempo es limitado, este artículo hablará sobre esto primero. Deja 2 preguntas rápidas relacionadas con el poder para tus amigos, si tienes tiempo, puedes ir a estudiar

  • Use la matriz para exponer rápidamente la secuencia de Fibonacci
  • Diseñe un algoritmo para encontrar el resultado del módulo de potencia y z z de x: (x ^ y)% z
    • Suponga que tanto x como y pueden ser enteros grandes (y es mayor o igual que 0, z no es igual a 0)

Si particularmente quieres que escriba algo, también puedes dejar una sugerencia, gracias. Bienvenida atención

Supongo que te gusta

Origin www.cnblogs.com/mjios/p/12690097.html
Recomendado
Clasificación