剑指offer:Python 数值的整数次方 快速幂和快速幂取模详解

题目描述

给定一个double类型的浮点数base和int类型的整数exponent。求base的exponent次方。

保证base和exponent不同时为0

思路及Python实现

  • 先读完题,就是要你模拟一个求一个数的次方,直接用内置函数 pow 哈哈哈
class Solution:
    def Power(self, base, exponent):
        return pow(base,exponent)
  • 整体思路:利用乘法,不过得考虑底数是否为0,以及指数与0的关系。当指数exponent为0时,任何数的0次方都是1;当exponent不等于0,但底数base为0时,0的任何次方都为0;当底数不为0,这时候要区分指数exponent与0的关系:当指数大于0,直接将底数base连乘exponent次就好;当指数小于0,将指数取相反数,换成正数,然后在连乘,最后将结果取倒数。
class Solution:
    def Power(self, base, exponent):
        if exponent == 0:
            return 1
        if base == 0:
            return 0
        flag = True  # 设置一个指数是否小于0的标志,默认为True,即指数为正数。
        if exponent < 0:
            exponent = -exponent
            flag = False
        result = 1
        for i in range(exponent):
            result *= base
        return result if flag else 1 / result

递归做法

在这里插入图片描述

class Solution:
    def Power(self, base, exponent):
        if exponent == 0:
            return 1
        if base == 0:
            return 0
        flag = True  # 设置一个指数是否小于0的标志,默认为True,即指数为正数。
        if exponent < 0:
            exponent = -exponent
            flag = False
        if exponent & 1 == 0:  # 判断指数是否为偶数
            res = self.Power(base, exponent // 2)
            res *= res
        else:
            res = self.Power(base, exponent // 2)
            res *= res
            res *= base
        return res if flag else 1 / res

快速幂

  • 关于快速幂,举个例子:如要求 2 的10次方,之前的做法是:2连续乘以10次 时间复杂度为O(n),我们可以使用快速幂来优化:

  • 关于a ^ b,还是以:2 ^10 为例:
    对于b而言,如果我们将10变成二进制,就是:1010,如果变成加权的情况可以得到表达式:

0 * 2 ^ 0 + 1 * 2 ^ 1 + 0 * 2 ^ 2 + 1 * 2 ^ 3

代入原来的2^10可以得到表达式:

2 ^ (0 * 2 ^ 0 + 1 * 2 ^ 1 + 0 * 2 ^ 2 + 1 * 2^3),把为指数中为0的去掉,因为2 ^ 0是1,1再乘剩下的数还是本身,我们做下可以得到:

2 ^ (2 ^ 1) * 2 ^ (2^3) 仔细观察,指数中就是 二进制1010中 所有1 所对应的权位值

接下来就是如何确定二进制中的哪一位为1,这里可以利用位运算中的 & 和 >>(右移)运算。由于1的二进制除了第一位是1,其他的全是0,因此可以利用 n&1是否为0来判断n的二进制的当前最低位是否为1,如果n&1等于0,说明当前n的最低位不为1;利用右移运算来逐位读取。

所以指数的二进制数,不断右移,碰到0,就累乘;碰到1,就将累乘的值乘到结果中

上面那句话,可能不理解:那详细来说一下,首先为什么碰到0要将底数累乘?
因为,虽然你是位置是0,当前可能不要用到,但是随着右移,前面可能会出现 1 时,即是要求它当前位置的值,就是 2 ^ (位置-1)吧,那么你先帮我累乘,我就可以直接拿来用了,所以需要累乘!

def power(base,exponent):
    res = 1
    while exponent:
        if exponent & 1:  # 判断当前的最后一位是否为1,如果为1的话,就需要把之前的幂乘到结果中。
            res *= base
        base *= base  # 一直累乘,如果最后一位不是1的话,就不用了把这个值乘到结果中,留着为后面服务
        exponent = exponent >> 1 # 右移一位
    return res

快速幂做法

class Solution:
    def Power(self, base, exponent):
        if exponent == 0:
            return 1
        if base == 0:
            return 0
        flag = False
        if exponent < 0:
            exponent = -exponent
            flag = True
        res = 1
        while exponent:
            if exponent & 1:  # 判断当前的最后一位是否为1,如果为1的话,就需要把之前的幂乘到结果中。
                res *= base
            base *= base  # 一直累乘,如果最后一位不是1的话,就不用了把这个值乘到结果中
            exponent = exponent >> 1
        return res if not flag else 1 / res

引申 快速幂取模

定理:(a * b) mod c = ((a mod c)*(b mod c)) mod c

那么根据上面的定理可以推导出另一个定理:

(a ^ b) mod c = (a mod c) ^ b mod c
(a ^ b) % c = (a%c) ^ (b %c)

将 (a%c) ^ (b %c) 化成整体来看,就是 A^B 所以,稍微转换下就能完成如下:

def power(base,exponent):
    res = 1
    base = a%c
    while exponent:
        if exponent & 1:  # 判断当前的最后一位是否为1,如果为1的话,就需要把之前的幂乘到结果中。
            res=(res*base)%c
        base=(base*base)%c  # 一直累乘,如果最后一位不是1的话,就不用了把这个值乘到结果中,留着为后面服务
        exponent = exponent >> 1 # 右移一位
    return res

比较下快慢

from time import *


def orginal_algorithm(a, b, c):  # a^b%c
    ans = 1
    a = a % c  # 预处理,防止出现a比c大的情况
    for i in range(b):
        ans = (ans * a) % c
    return ans


def quick_algorithm(a, b, c):
    a = a % c
    ans = 1
    # 这里我们不需要考虑b<0,因为分数没有取模运算
    while b != 0:
        if b & 1:
            ans = (ans * a) % c
        b >>= 1
        a = (a * a) % c
    return ans


time = clock()
a = eval(input("底数:"))
b = eval(input("指数:"))
c = eval(input("模:"))
print("朴素算法结果%d" % (orginal_algorithm(a, b, c)))
print("朴素算法耗时:%f" % (clock() - time))
time = clock()
print("快速幂算法结果%d" % (quick_algorithm(a, b, c)))
print("快速幂算法耗时:%f" % (clock() - time))
  • 3
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值