日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > python >内容正文

python

【面试相关】python实现快速幂取余算法详解

發布時間:2023/12/19 python 31 豆豆
生活随笔 收集整理的這篇文章主要介紹了 【面试相关】python实现快速幂取余算法详解 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

假設我們要計算 2102^{10}210 對1000取模的結果,可以很簡單的得到24。但是如果要求 210002^{1000}21000 對1000取模的結果,常規方法就行不通了,因為常規的變量無法容納這么大的數值。為此,需要借助數學技巧求解。

循環求余法

首先引入一個好理解的取模運算公式:
(a×b)%p=(a%p×b%p)%p(a \times b) \% p = (a \% p \times b \% p) \% p (a×b)%p=(a%p×b%p)%p
舉個例子:
a=9,b=9(9×9)%6=(9%6×9%6)%6=(3×3)%6=3a=9, b=9\\ (9 \times 9) \% 6 = (9\%6 \times 9\%6)\%6 = (3 \times 3) \% 6 = 3 a=9,b=9(9×9)%6=(9%6×9%6)%6=3×3)%6=3
受上面計算過程的啟發,不難得到下面的取模運算公式:
xa%p=((x%p)a)%px^a \% p = \left( (x \% p)^a \right) \% p xa%p=((x%p)a)%p
即 a 個 x 的乘積可以分解為 a 個 x 對 p 余數的乘積再對 p 取余,即 ((x%p)a)%p\left( (x \% p)^a \right) \% p((x%p)a)%p

舉個例子:
x=9,a=2,p=6(92)%6=(9%6)2%6=32%6=3x=9, a=2, p=6\\ (9^2) \% 6 =(9\%6 )^2\%6 = 3 ^2 \% 6 = 3 x=9,a=2,p=6(92)%6=(9%6)2%6=32%6=3
對于上面的例子,如果 a = 1000,難道最后還得計算 31000%63^{1000} \% 631000%6 嗎?其實可以這么做:

在進行 a 次迭代計算 x 對 p 余數的過程中,先將第 iii 次迭代得到的余數用變量 rem 保存起來,在進行第 i+1i + 1i+1 次迭代的時候,由 (rem * (x%p)) % p 所得到的結果就是 xi+1%px^{i+1} \%pxi+1%p 的結果。

舉個例子,求 x3%px^3 \% px3%p 的過程如下:

1,rem = x % p 2,rem = x^2 % p = ((x%p) * (x%p)) % p = (rem * (x%p)) % p 3,rem = x^3 % p = ((x^2%p) * (x%p)) % p = (rem * (x%p)) % p

可以看到這個過程的時間復雜度是 O(N),其中 N = a,代碼實現如下:

# 求 (x^a) % p —— 循環求余法 def reminder1(x, a, p):rem = 1for _ in range(a):rem = (rem * (x%p)) % preturn remx, a, p = 2,100000000, 3 print(reminder1(x, a, p)) # 用時:9.06s

觀察(rem * (x%p)) % p,發現既然括號的外面要用 p 取模,那么內部的(x%p)就顯得沒有必要了,因此可以簡化為:(rem * x) % p :

# 求 (x^a) % p —— 循環求余法 def remainder2(x, a, p):rem = 1for _ in range(a):rem = (rem * x) % preturn remx, a, p = 2,100000000, 3 print(reminder2(x, a, p)) # 用時:6.76s

快速冪求余

上面的過程中,我們每一次迭代都是計算 x%px\% px%p,所以需要迭代 a 次。其實這個過程還有優化的空間:

如果我們每次都計算 x2%px^2 \% px2%p,那只需要迭代 a/2 次。也就是說,可以把指數 a 級的問題轉換為 a/2 級的問題,如果繼續分解這個計算過程,又可以得到:
xa%p=(x2%p)a/2%p=((x2%p)2%p)a/4%p=...x^a \% p = (x^2 \% p)^{a/2} \% p = \left( (x^2 \% p)^2\%p \right)^{a/4} \% p = ... xa%p=x2%pa/2%p=((x2%p)2%p)a/4%p=...
最終計算 xa%px^a \%pxa%p 的時間復雜度僅為 O(logN)O(logN)O(logN)

實際上,上面的式子忽略了一個問題, a 可能是奇數也可能是偶數,因此對 a/2 的處理上需要遵循下面的公式:
xa%p={(x2%p)a//2%p,a為偶數[(x%p)(xa?1%p)]%p=[x(x2%p)a//2]%p,a為奇數x^{a} \% p=\left\{\begin{array}{l} \left(x^{2} \% p\right)^{a / / 2} \% p \qquad \qquad \qquad \qquad \qquad \qquad \qquad, a 為偶數\\ {\left[(x \% p)\left(x^{a-1} \% p\right)\right] \% p=\left[x\left(x^{2} \% p\right)^{a / / 2}\right] \% p} \quad\; ,a為奇數 \end{array}\right. xa%p={(x2%p)a//2%p,a[(x%p)(xa?1%p)]%p=[x(x2%p)a//2]%p,a?
式子中的 // 表示除法運算的結果向下取整。此計算過程的代碼實現如下:

# 求 (x^a) % p —— 快速冪求余 def remainder3(x, a, p):rem = 1while a > 0:# 無論 a 是奇數或偶數,a//2的結果是一樣的# 因此,對于奇數要進行特殊處理if a % 2: rem = (rem * x) % p# 底數變大為原來的平方:x = x ** 2 % p# 指數變為原來的一半a //= 2return rem x, a, p = 2,100000000, 3 print(remainder3(x, a, p)) # 幾乎等于0

為什么 rem 只在 a 為奇數的時候才計算呢?

假設 a = 4,那么

  • 第一次迭代后 a=2, x = x^2 % p;
  • 第二次迭代后 a=1,x = (x^2 % p)^2 %p;
  • 第三次迭代時,可以求得 rem = (1 * x) % p,且 a = 0;
  • 結束循環,返回了 rem。

假設 a = 5,那么:

  • 第一次迭代后 rem = x%p,a=2, x = x^2 % p;
  • 第二次迭代后 a=1,x = (x^2 % p)^2 %p;
  • 第三次迭代時,可以求得 rem = (rem * x) % p,且 a = 0;
  • 結束循環,返回了 rem。

也就是說無論 a 是奇數還是偶數, a//2的計算結果都是交替出現的奇數和偶數,并且最終都會得到 a = 1 計算出 rem,然后結束循環,返回 rem。因此只需要在 a 為奇數的時候計算 rem 即可保證最后得到的 rem 就是我們想要的值。

參考文章:

面試題14- II. 剪繩子 II(數學推導 / 貪心思想 + 快速冪求余,清晰圖解)

快速冪算法(全網最詳細地帶你從零開始一步一步優化)

總結

以上是生活随笔為你收集整理的【面试相关】python实现快速幂取余算法详解的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。