java位运算求幂,程序员必学:快速幂算法
前陣子,有小伙伴在我B站的算法教程底下留言
小伙伴們有任何疑問或者希望我解說任何內(nèi)容,都可以在我的小我私家B站或民眾號(hào)(xmg_mj)留言哦,我會(huì)盡我最大能力、只管抽時(shí)間去寫文章\錄視頻來回應(yīng)人人。
關(guān)于快速冪
實(shí)在快速冪相關(guān)的問題,是加入算法競(jìng)賽(NOI、ACM等)的小伙伴必須要掌握的一小塊基礎(chǔ)內(nèi)容。固然,就算你不設(shè)計(jì)加入算法競(jìng)賽,小我私家以為只要你是一名程序員,就必須要掌握快速冪算法。
在《計(jì)算機(jī)程序設(shè)計(jì)藝術(shù)》一書中就有提到快速冪算法,此書的英文名是The Art of Computer Programming,簡(jiǎn)稱TAOCP。
TAOCP出自Donald Ervin Knuth先輩之手。Knuth先輩是在計(jì)算機(jī)領(lǐng)域成就頗豐的著名科學(xué)家,是著名的KMP算法的發(fā)明人之一,在1974年獲得“計(jì)算機(jī)領(lǐng)域的諾貝爾獎(jiǎng)”:圖靈獎(jiǎng)(昔時(shí)他才36歲)。現(xiàn)在TAOCP已經(jīng)出書了第1、2、3、4A卷,憑據(jù)設(shè)計(jì),另有第4B、5、6、7卷未出書。第一卷首發(fā)于1968年,Knuth先輩今年是82歲高壽,聽說他設(shè)計(jì)在105歲之前完成這部巨著。
關(guān)于TAOCP,微軟創(chuàng)始人Bill Gates曾說過
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.
也許意思是:若是你以為自己是一位異常優(yōu)異的程序員,那就應(yīng)該閱讀Knuth的TAOCP;若是你能讀懂全部?jī)?nèi)容,可以直接給我發(fā)送一份簡(jiǎn)歷。聽說Knuth先輩的言辭加倍犀利:看不懂就別當(dāng)程序員了!不外TAOCP對(duì)于新手來說,閱讀難度簡(jiǎn)直對(duì)照大,書中的所有示例都使用了Knuth先輩自創(chuàng)的MIX匯編語(yǔ)言。
閱讀本文之前的提醒
今天就抽閑寫一篇文章來解說一下經(jīng)典的快速冪算法哈。不外要想徹底看懂本文,有幾個(gè)前提條件
熟悉算法中的2個(gè)基礎(chǔ)觀點(diǎn):時(shí)間復(fù)雜度、空間復(fù)雜度
若是你壓根沒聽過這2個(gè)觀點(diǎn),說明你的算法基礎(chǔ)完全為0,真的沒有在開頑笑!
可以向民眾號(hào)發(fā)送復(fù)雜度獲取相關(guān)教程
熟悉二進(jìn)制和十進(jìn)制的轉(zhuǎn)換
若是連這個(gè)都不熟悉的話,那你的編程基礎(chǔ)就真的需要好好補(bǔ)補(bǔ)啦
可以向民眾號(hào)發(fā)送進(jìn)制獲取相關(guān)教程
熟悉常見的位運(yùn)算操作
n & 1的效果是n最低二進(jìn)制位的值,也可以用于判斷n的奇偶性
求正整數(shù)n / 2,可以用位運(yùn)算取代:n >> 1
若是不明白上述操作的原理,可以向民眾號(hào)發(fā)送位運(yùn)算獲取相關(guān)教程
什么是冪(Power)?
眾所周知,x的n次冪,是指x的n次方,也就是n個(gè)x相乘,好比2的4次冪就是2 * 2 * 2 * 2。
為了簡(jiǎn)化形貌,后面x的n次冪,我就簡(jiǎn)化為x ^ n(本文中的 ^ 并不是按位異或的意思)
那若何通過編程求冪?假設(shè)只思量x、n是整數(shù)且n大于即是0的情形,最容易想到的方式如下所示(這里接納的編程語(yǔ)言是Java,但沒有涉及Java特殊的語(yǔ)法。以是就算你沒用過Java,也可以看懂)
int power(int x, int n) {
int result = 1;
while (n-- > 0) {
result *= x;
}
return result;
}
很顯然,這種方式的時(shí)間復(fù)雜度是O(n)、空間復(fù)雜度是O(1)
什么是快速冪?
所謂快速冪,就是用效率更高(時(shí)間復(fù)雜度更低)的方式求冪,可以將時(shí)間復(fù)雜度優(yōu)化至O(logn)。這里先容2種求解方式:遞歸、非遞歸
遞歸
憑據(jù)上圖中的等式,不難寫出以下代碼
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;
}
這個(gè)方式的時(shí)間、空間復(fù)雜度都是O(logn)。
那若何剖析出這個(gè)方式的復(fù)雜度呢?
若是你的算法功底對(duì)照微弱,可以代入特定值作一個(gè)也許的剖析,好比當(dāng)n為16時(shí),方式的遞歸挪用歷程如下圖所示
不難看出,每次挪用時(shí),n的規(guī)模都減半,以是時(shí)間和空間復(fù)雜度都是O(logn)
若是你的算法功底還行,那就可以用更專業(yè)的方式去剖析它的復(fù)雜度(沒有一定的算法基礎(chǔ),可能會(huì)看不懂)
這實(shí)在是典型的應(yīng)用分治計(jì)謀的算法
假設(shè)T(n)是數(shù)據(jù)規(guī)模為n時(shí)的時(shí)間復(fù)雜度,不難得出遞推式:T(n) = T(n / 2) + O(1)
最后憑據(jù)遞推式 + 主定理(Master Theorem)可以直接得出結(jié)論:T(n) = O(logn)
非遞歸
我們以求3 ^ 21為例子,來剖析一下非遞歸的代碼應(yīng)該怎么寫。
首先21的二進(jìn)制形式是10101
不難得出以下結(jié)論
3 ^ n(n為2、4、8、16)都可以由3 ^ 1累乘出來
每一個(gè)3 ^ n都有對(duì)應(yīng)的二進(jìn)制位
3 ^ 1對(duì)應(yīng)二進(jìn)制位的值是1,實(shí)在是二進(jìn)制10101的最后1位
3 ^ 2對(duì)應(yīng)二進(jìn)制位的值是0,實(shí)在是二進(jìn)制1010的最后1位
3 ^ 4對(duì)應(yīng)二進(jìn)制位的值是1,實(shí)在是二進(jìn)制101的最后1位
3 ^ 8對(duì)應(yīng)二進(jìn)制位的值是0,實(shí)在是二進(jìn)制10的最后1位
3 ^ 16對(duì)應(yīng)二進(jìn)制位的值是1,實(shí)在是二進(jìn)制1的最后1位
若是3 ^ n對(duì)應(yīng)二進(jìn)制位的值是0,就不用乘進(jìn)最終效果
好比3 ^ (8 * 0)、3 ^ (2 * 0)
由于它們最終的值都是3 ^ 0,也就是1
若是3 ^ n對(duì)應(yīng)二進(jìn)制位的值是1,就需要乘進(jìn)最終效果
好比3 ^ (16 * 1)、3 ^ (4 * 1)、3 ^ (1 * 1)
以是,綜合以上種種結(jié)論,可以總結(jié)出以下解題步驟
行使3 ^ 1,不停累乘出3 ^ n(n為2、4、8、16)
每當(dāng)累乘出一個(gè)3 ^ n,就查看其對(duì)應(yīng)二進(jìn)制位的值是1照樣0,來決議是否要將它乘進(jìn)最終效果
int fastPower(int x, int n) {
int result = 1;
while (n != 0) {
if ((n & 1) == 1) {
result *= x;
}
x *= x;
n >>= 1;
}
return result;
}
代入3和21,fastPower(3, 21)的執(zhí)行流程如下
第1輪while循環(huán)
第4行代碼
n的二進(jìn)制是10101(十進(jìn)制是21)
x = 3 ^ 1, 其對(duì)應(yīng)二進(jìn)制位的值是1(n的最后一個(gè)二進(jìn)制位)
以是需要執(zhí)行第5行代碼:將x乘進(jìn)最終效果
result = 3 ^ 1
第7行代碼
x = (3 ^ 1) * (3 ^ 1) = 3 ^ 2
第8行代碼
n右移1位,其二進(jìn)制變成了1010(對(duì)應(yīng)的十進(jìn)制是啥?不重要!!!)
第2輪while循環(huán)
第4行代碼
n的二進(jìn)制是1010
x = 3 ^ 2, 其對(duì)應(yīng)二進(jìn)制位的值是0(n的最后一個(gè)二進(jìn)制位)
以是不需要執(zhí)行第5行代碼:不需要將x乘進(jìn)最終效果
result = 3 ^ 1
第7行代碼
x = (3 ^ 2) * (3 ^ 2) = 3 ^ 4
第8行代碼
n右移1位,其二進(jìn)制變成了101(對(duì)應(yīng)的十進(jìn)制是啥?不重要!!!)
第3輪while循環(huán)
第4行代碼
n的二進(jìn)制是101
x = 3 ^ 4, 其對(duì)應(yīng)二進(jìn)制位的值是1(n的最后一個(gè)二進(jìn)制位)
以是需要執(zhí)行第5行代碼:將x乘進(jìn)最終效果
result = (3 ^ 1) * (3 ^ 4)
第7行代碼
x = (3 ^ 4) * (3 ^ 4) = (3 ^ 8)
第8行代碼
n右移1位,其二進(jìn)制變成了10(對(duì)應(yīng)的十進(jìn)制是啥?不重要!!!)
第4輪while循環(huán)
第4行代碼
n的二進(jìn)制是10
x = 3 ^ 8, 其對(duì)應(yīng)二進(jìn)制位的值是0(n的最后一個(gè)二進(jìn)制位)
以是不需要執(zhí)行第5行代碼:不需要將x乘進(jìn)最終效果
result = (3 ^ 1) * (3 ^ 4)
第7行代碼
x = (3 ^ 8) * (3 ^ 8) = 3 ^ 16
第8行代碼
n右移1位,其二進(jìn)制變成了1(對(duì)應(yīng)的十進(jìn)制是啥?不重要!!!)
第5輪while循環(huán)
第4行代碼
n的二進(jìn)制是1
x = 3 ^ 16, 其對(duì)應(yīng)二進(jìn)制位的值是1(n的最后一個(gè)二進(jìn)制位)
以是需要執(zhí)行第5行代碼:將x乘進(jìn)最終效果
result = (3 ^ 1) * (3 ^ 4) * (3 ^ 16)
第7行代碼
x = (3 ^ 16) * (3 ^ 16) = 3 ^ 32
第8行代碼
n右移1位,其二進(jìn)制變成了0
最后
由于n = 0,以是退出while循環(huán)
最終result = (3 ^ 1) * (3 ^ 4) * (3 ^ 16)
復(fù)雜度剖析
每執(zhí)行一次while的循環(huán)體,n >>= 1, 會(huì)導(dǎo)致n的值減半
以是時(shí)間復(fù)雜度:O(logn)、空間復(fù)雜度:O(1)
Leetcode
Leetcode上的第50號(hào)題50. Pow(x, n),恰好就可以用今天解說的快速冪算法。以下是我的代碼實(shí)現(xiàn)
// 遞歸
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;
}
需要提醒的是
這里我用的編程語(yǔ)言是Java,人人可以憑據(jù)自己熟悉的編程語(yǔ)言,對(duì)一些語(yǔ)法細(xì)節(jié)作出響應(yīng)的調(diào)整
Leetcode上的n可能是個(gè)負(fù)數(shù),以是上面的代碼針對(duì)負(fù)數(shù)的情形作了一些處置
更多快速冪相關(guān)的問題
時(shí)間有限,這篇文章就先說到這了哈。給小伙伴們留2個(gè)快速冪相關(guān)的問題,有空的話,可以去研究一下
使用矩陣快速冪求斐波那契數(shù)列
請(qǐng)?jiān)O(shè)計(jì)一個(gè)算法求x的y次冪模z的效果:(x ^ y) % z
假設(shè)x、y都可能是很大的整數(shù)(y大于即是0,z不即是0)
若是你稀奇希望我寫點(diǎn)什么方面的內(nèi)容,也可以留言建議,謝謝。迎接關(guān)注
原文鏈接:https://www.cnblogs.com/mjios/p/12690097.html
本站聲明:網(wǎng)站內(nèi)容來源于網(wǎng)絡(luò),若有侵權(quán),請(qǐng)聯(lián)系我們,我們將及時(shí)處置。
總結(jié)
以上是生活随笔為你收集整理的java位运算求幂,程序员必学:快速幂算法的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java list 重复数据_java
- 下一篇: Golang——指针的使用、数组指针和指