Programmers must learn: fast power algorithm

Some time ago, some friends

left a message under the algorithm tutorial of my B station. If you have any questions or want to explain anything, you can leave a message on my personal B station or public account (xmg_mj) Ability, try to take the time to write articles \ record videos to respond to everyone.

About fast power

In fact, the problem related to fast power is a small piece of basic content that must be mastered by small partners participating in the algorithm competition (NOI, ACM, etc.). Of course, even if you do not plan to participate in the algorithm competition, personally feel that as long as you are a programmer, you must master the fast power algorithm.

In the book "The Art of Computer Programming", the fast power algorithm is mentioned. The English name of this book is The Art of Computer Programming, or TAOCP for short.

TAOCP comes from the hands of Donald Ervin Knuth . Senior Knuth is a well-known scientist who has made a lot of achievements in the computer field. He is one of the inventors of the famous KMP algorithm. In 1974, he won the "Nobel Prize in Computer Field": Turing Award (he was only 36 years old). Currently, TAOCP has published volumes 1, 2, 3, and 4A, and according to the plan, volumes 4B, 5, 6, and 7 have not been published. The first volume was first published in 1968. Senior Knuth is 82 years old this year. It is said that he plans to complete the masterpiece before the age of 105.

About TAOCP, Microsoft founder Bill Gates once said

If you think you're a really good programmer… read (Knuth's) Art of Computer Programming… You should definitely send me a resume if you can read the whole thing.

Probably means: If you think you are a very good programmer, you should read TNUCP of Knuth; if you can read everything, you can send me a resume directly. It is said that Senior Knuth's words are sharper: Don't be a programmer if you don't understand! However, TAOCP is really difficult for novices to read. All the examples in the book use Knuth's own MIX assembly language .

Read the reminder before this article

Take the time to write an article today to explain the classic fast power algorithm. However, to thoroughly understand this article, there are several prerequisites

  • Familiar with the 2 basic concepts in the algorithm: time complexity, space complexity
    • If you haven't heard these two concepts at all, it means that your algorithm base is completely 0, and you are really not kidding!
    • You can send the complexity to the public account to get related tutorials
  • Familiar with binary and decimal conversion
    • If you are not familiar with this, then your programming foundation really needs to be filled
    • You can send a hexadecimal to the public account to get related tutorial
  • Familiar with common bit operations
    • The result of n & 1 is the value of the lowest binary bit of n, which can also be used to judge the parity of n
    • Find the positive integer n / 2, can be replaced by bit operation: n >> 1
    • If you do n’t understand the principle of the above operation, you can send a bit operation to the public account to get related tutorials

What is power?

As we all know, the nth power of x refers to the nth power of x, that is, the multiplication of n xs. For example, the power of 2 is 2 * 2 * 2 * 2.

In order to simplify the description, the power of n in the back of x will be simplified to x ^ n (^ in this article does not mean bitwise XOR)

Then how to seek power through programming? Assuming that only x and n are integers and n is greater than or equal to 0 , the easiest way to think of is as follows (the programming language used here is Java, but no special syntax of Java is involved. So even if you have not used Java Can understand)

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

Obviously, the time complexity of this method is O (n) and the space complexity is O (1)

What is fast power?

The so-called fast power is to use a more efficient (lower time complexity) method to find the power, and the time complexity can be optimized to O (logn) . Here are two solutions: recursive, non-recursive

Recursive

According to the equation in the above figure, it is not difficult to write the following code

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;
}

The time and space complexity of this method are both O (logn) .

How to analyze the complexity of this method?

If your algorithm is weak, you can substitute a specific value for a rough analysis. For example, when n is 16, the recursive calling process of the method is shown in the following figure.

It is not difficult to see that the size of n is halved each time it is called, so the time and space complexity are both O (logn)

If your algorithm is good, you can use a more professional method to analyze its complexity (without a certain algorithm foundation, you may not understand it)

  • This is actually a typical algorithm for applying the divide and conquer strategy
  • Assuming that T (n) is the time complexity when the data size is n, it is not difficult to derive the recursion formula: T (n) = T (n / 2) + O (1)
  • Finally, according to the recursion + master theorem (Master Theorem), we can directly draw the conclusion: T (n) = O (logn)

Non-recursive

Let's take 3 ^ 21 as an example to analyze how non-recursive code should be written.

First the binary form of 21 is 10101

It is not difficult to draw the following conclusions

  • 3 ^ n (n is 2, 4, 8, 16) can be multiplied by 3 ^ 1
  • Each 3 ^ n has a corresponding binary bit
    • 3 ^ 1 corresponds to a binary bit value of 1, which is actually the last bit of 10101 binary
    • 3 ^ 2 corresponds to a binary value of 0, which is actually the last bit of 1010 binary
    • 3 ^ 4 corresponds to a binary value of 1, which is actually the last bit of binary 101
    • 3 ^ 8 corresponds to a binary value of 0, which is actually the last bit of binary 10
    • 3 ^ 16 corresponds to a binary value of 1, which is actually the last bit of binary 1 .
  • If 3 ^ n corresponds to a binary value of 0 , then there is no need to multiply the final result
    • For example, 3 ^ (8 * 0 ), 3 ^ (2 * 0 )
    • Because their final value is 3 ^ 0, which is 1
  • If 3 ^ n corresponds to a binary value of 1 , it needs to be multiplied into the final result
    • For example, 3 ^ (16 * 1 ), 3 ^ (4 * 1 ), 3 ^ (1 * 1 )

Therefore, based on the above conclusions, the following problem-solving steps can be summarized

  • Using 3 ^ 1, keep accumulating 3 ^ n (n is 2, 4, 8, 16)
  • Whenever a 3 ^ n is accumulated, check whether the corresponding binary value is 1 or 0 to decide whether to multiply it into the final result
int fastPower(int x, int n) {
    int result = 1;
    while (n != 0) {
        if ((n & 1) == 1) {
            result *= x;
        }
        x *= x;
        n >>= 1;
    }
    return result;
}

Substituting 3 and 21, the execution flow of fastPower (3, 21) is as follows

Round 1 while loop

  • Line 4 code
    • n's binary is 10101 (decimal is 21)
    • x = 3 ^ 1, the value of the corresponding binary bit is 1 (the last binary bit of n)
    • So we need to execute the fifth line of code: multiply x into the final result
    • result = 3 ^ 1
  • Line 7 code
    • x = (3 ^ 1) * (3 ^ 1) = 3 ^ 2
  • Line 8 code
    • n is shifted to the right by 1 bit, and its binary becomes 1010 (what is the corresponding decimal system? Not important !!!)

Round 2 while loop

  • Line 4 code
    • n's binary is 1010
    • x = 3 ^ 2, the value of the corresponding binary bit is 0 (the last binary bit of n)
    • So there is no need to execute the fifth line of code: there is no need to multiply x into the final result
    • result = 3 ^ 1
  • Line 7 code
    • x = (3 ^ 2) * (3 ^ 2) = 3 ^ 4
  • Line 8 code
    • n is shifted to the right by 1 bit, and its binary becomes 101 (what is the corresponding decimal system? Not important !!!)

Round 3 while loop

  • Line 4 code
    • n's binary is 101
    • x = 3 ^ 4, the value of the corresponding binary bit is 1 (the last binary bit of n)
    • So we need to execute the fifth line of code: multiply x into the final result
    • result = (3 ^ 1) * (3 ^ 4)
  • Line 7 code
    • x = (3 ^ 4) * (3 ^ 4) = (3 ^ 8)
  • Line 8 code
    • n is shifted to the right by 1 bit, and its binary becomes 10 (what is the corresponding decimal system? Not important !!!)

Round 4 while loop

  • Line 4 code
    • n's binary is 10
    • x = 3 ^ 8, the value of the corresponding binary bit is 0 (the last binary bit of n)
    • So there is no need to execute the fifth line of code: there is no need to multiply x into the final result
    • result = (3 ^ 1) * (3 ^ 4)
  • Line 7 code
    • x = (3 ^ 8) * (3 ^ 8) = 3 ^ 16
  • Line 8 code
    • n is shifted to the right by 1 bit, and its binary becomes 1 (what is the corresponding decimal system? Not important !!!)

Round 5 while loop

  • Line 4 code
    • n's binary is 1
    • x = 3 ^ 16, the value of the corresponding binary bit is 1 (the last binary bit of n)
    • So we need to execute the fifth line of code: multiply x into the final result
    • result = (3 ^ 1) * (3 ^ 4) * (3 ^ 16)
  • Line 7 code
    • x = (3 ^ 16) * (3 ^ 16) = 3 ^ 32
  • Line 8 code
    • n shifted to the right by 1 bit, its binary becomes 0

At last

  • Since n = 0, so exit the while loop
  • Final result = (3 ^ 1) * (3 ^ 4) * (3 ^ 16)
  • Complexity analysis
    • Each time the body of the while loop is executed, n >> = 1, will cause the value of n to be halved
    • So time complexity: O (logn) , space complexity: O (1)

Leetcode

Question 50. Pow (x, n) on Leetcode , just use the fast power algorithm explained today. The following is my code implementation

// 递归
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;
}

What needs to be reminded is

  • The programming language I use here is Java. You can adjust some grammatical details according to your familiar programming language.
  • The n on Leetcode may be a negative number, so the above code does some processing for the case of negative numbers

More fast power related questions

Time is limited, this article will talk about this first. Leave 2 quick power-related questions for your friends, if you have time, you can go to study

  • Use matrix to exponentiate Fibonacci sequence quickly
  • Please design an algorithm to find the result of the y-power modulus z of x: (x ^ y)% z
    • Suppose that both x and y may be large integers (y is greater than or equal to 0, z is not equal to 0)

If you particularly want me to write something, you can also leave a suggestion, thank you. Welcome attention

Guess you like

Origin www.cnblogs.com/mjios/p/12690097.html