《劍指offer》:自己寫一個pow函數

緣由

今天打google在codejam上辦的apactest,成績還行吧(最後排名540),第二題逗比了,自己邏輯後來理清楚,但是代碼還是原來的想法,wa了兩次才找到了bug。
apactest

第二題需要實現一個整數的pow函數,之前只會遞歸的寫法,今天學會了迭代的寫法,回想《劍指offer》裏也有類似的東西,所以整理成這篇博客。

整數的非負整數次冪(不考慮大數)

線性求冪

比如求2的10次冪,可能有人隨手十幾秒就寫完了:

typedef long long LL;
LL pow(int base, int exponent) {
    LL ans = 1;
    for (int i = 0; i < exponent; ++i) {
        ans *= base;
    }
    return ans;
}

如果exponent是比較大的呢?有沒有更快的?

快速冪算法登場

我們把exponent表示成二進制的形式,比如15=1111,那麼其實有:

a11=a10112=a10002+102+12=a10002a102a12=a8a2a1

這樣計算的話,我們只需要計算log2(exponent) 次就夠了!對比23232 ,完全不一樣的數量級!

那麼代碼怎麼寫呢?

typedef long long LL;
LL pow(int base, int exponent) {
    LL ans = 1, last_pow = base;
    while (exponent > 0) {
        // 如果最低位爲1
        if ((exponent & 1) != 0)
            ans = ans * last_pow ;
        exponent = exponent >> 1;
        last_pow = last_pow * last_pow;
    }
    return ans;
}

其實我覺得代碼已經很清楚了,如果還不清楚的話,可以稍微解釋一下(請先自行熟悉位運算):
假如我們使用上面的函數來迭代計算311 ,根據上面所使用的公式,11表示爲二進制是1011,計算過程是這樣的:
第一步:發現1011的最低位爲1,ans乘上31 爲3,1011右移一位變成101;
第二步:發現101的最低位爲1,ans乘以32 變成27,101右移一位變成10;
第三步,發現10的最低位不爲1,ans不變,10右移一位變成1;
第四步,發現1的最低位爲1,ans乘以38 變成177147,1右移一位變成0,退出while循環。

注意每一步裏的31323438 是怎麼來的呢?用的就是last_pow(注意初始值)這個量來每次平方自己計算出來的!第三步裏雖然ans不變,但是last_pow還是得平方一下,否則沒法在第四步裏變成所需要的38

整數次冪

注意現在的問題變成了整數次冪了,也就是包括負數次冪了!
首先回顧一下冪運算的定義:

ax=1axx

a0=1a0

也就是說,對於負數次冪,其實只要計算它的絕對值次冪,再用1去除就可以了,還要注意檢查計算0的0次冪這種非法情況!
typedef double NumberType;

// 計算非負整數次冪的函數
NumberType powWithoutNegativeExp(NumberType base, int exponent) {
    NumberType ans = 1, last_pow = base;
    while (exponent > 0) {
        if ((exponent & 1) != 0)
            ans = ans * last_pow ;
        exponent = exponent >> 1;
        last_pow = last_pow * last_pow;
    }
    return ans;
}

// 浮點數相等的判斷比較特別
bool equalD(NumberType numA, NumberType numB) {
    static const NumberType ERROR = 1e-12;
    return (numA - numB <= ERROR && numA - numB >= -ERROR);
}

// 處理各種情況的冪運算的函數
NumberType pow(NumberType base, int exponent) {
    // 0的0次冪沒有意義,拋出異常
    if (exponent == 0 && equalD(base, 0)) {
        throw logic_error("Zero's zero exponent is undefine.");
    }

    bool isNegative = false;
    if (exponent < 0) {
        isNegative = true;
        exponent = -exponent;
    }

    NumberType result = powWithoutNegativeExp(base, exponent);
    return isNegative ? 1 / result : result;
}

註釋代碼裏有了~應該是很清晰的。

另附上完整的使用示例:

#include <iostream>
#include <stdexcept>
using namespace std;

typedef double NumberType;

NumberType powWithoutNegativeExp(NumberType base, int exponent) {
    NumberType ans = 1, last_pow = base;
    while (exponent > 0) {
        if ((exponent & 1) != 0)
            ans = ans * last_pow ;
        exponent = exponent >> 1;
        last_pow = last_pow * last_pow;
    }
    return ans;
}

bool equalD(NumberType numA, NumberType numB) {
    static const NumberType ERROR = 1e-12;
    return (numA - numB <= ERROR && numA - numB >= -ERROR);
}

NumberType pow(NumberType base, int exponent) {
    if (exponent == 0 && equalD(base, 0)) {
        throw logic_error("Zero's zero exponent is undefine.");
    }

    bool isNegative = false;
    if (exponent < 0) {
        isNegative = true;
        exponent = -exponent;
    }

    NumberType result = powWithoutNegativeExp(base, exponent);
    return isNegative ? 1 / result : result;
}


int main() {
    for (double i = 1.1; i < 1.5; i+=0.1) {
        for (int j = 1; j < 10; ++j)
            cout << i << ' ' << j << ' ' << pow(i, j) << endl;
    }

    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章