Java BigInteger類 modPow方法【大數快速冪取模】

爲了探究Java大數自帶的modPow方法(大數快速冪取模)在ACM比賽中時間複雜度的可行性,我以 POJ 1995 Raising Modulo Numbers 進行測試,POJ的編譯器是J2SE 1.5。

這是一個快速冪取模的題目,用long類型手寫快速冪取模即可,但是BigInteger類有自帶快速冪取模的modPow方法,可以直接調用,兩者的運行時間會相差多少呢?

代碼一,直接調用BigInteger類中的modPow方法,1454MS

import java.math.*;
import java.util.*;

public class Main {
	
	public static void main(String [] args) {
        Scanner cin=new Scanner(System.in);
        int T=cin.nextInt();
        while(T--!=0) {
        	BigInteger p=cin.nextBigInteger();
        	int m=cin.nextInt();
        	BigInteger s=BigInteger.ZERO;
        	while(m--!=0) {
        		BigInteger a=cin.nextBigInteger();
        		BigInteger b=cin.nextBigInteger();
        		BigInteger t=a.modPow(b,p);//t=a^b%p
        		s=s.add(t).mod(p);//s=(s+t)%p
        	}
        	System.out.println(s);
        }
    }
}

代碼二,手寫大數快速冪,參數含BigInteger類

①參數均爲BigInteger類,1875MS
①參數中指數爲long類型,底數和模數爲BigInteger類,1454MS

import java.math.*;
import java.util.*;

public class Main {
	
	public static void main(String [] args) {
        Scanner cin=new Scanner(System.in);
        int T=cin.nextInt();
        while(T--!=0) {
        	BigInteger p=cin.nextBigInteger();
        	int m=cin.nextInt();
        	BigInteger s=BigInteger.ZERO;
        	while(m--!=0) {
        		BigInteger a=cin.nextBigInteger();
        		BigInteger b=cin.nextBigInteger();
        		BigInteger t=qpow(a,b,p);
        		s=s.add(t).mod(p);
        	}
        	System.out.println(s);
        }
    }

	static BigInteger qpow(BigInteger a,BigInteger b,BigInteger p) {//大數快速冪 求a^b%p 1875ms
		BigInteger s=BigInteger.ONE;//s=1
		BigInteger two=BigInteger.valueOf(2);
    	while(!b.equals(BigInteger.ZERO)) {
    		if((b.mod(two)).equals(BigInteger.ONE)) s=s.multiply(a).mod(p);
    		a=a.multiply(a).mod(p);
    		b=b.divide(two);
    	}
    	return s;
    }
    
	/*static BigInteger qpow(BigInteger a,long b,BigInteger p) {//快速冪求a^b%p 1454ms
		BigInteger s=BigInteger.ONE;//s=1
    	while(b!=0) {
    		if(b%2==1) s=s.multiply(a).mod(p);
    		a=a.multiply(a).mod(p);
    		b=b/2;
    	}
    	return s;
    }*/
    
}

代碼三,手寫快速冪,參數均爲long類型,875MS

import java.math.*;
import java.util.*;

public class Main {

    public static void main(String [] args) {
        Scanner cin=new Scanner(System.in);
        int T=cin.nextInt();
        while(T--!=0) {
        	long p=cin.nextLong();
        	long m=cin.nextLong();
        	long s=0;
        	while(m--!=0) {
        		long a=cin.nextLong();
        		long b=cin.nextLong();
        		s+=qpow(a,b,p);
        		s%=p;
        	}
        	System.out.println(s);
        }
    }

	static long qpow(long a,long b,long p) {//快速冪求a^b%p
		long s=(long)1;//s=1
    	while(b!=0) {
    		if((b&1)==1) s=s*a%p;
    		a=a*a%p;
    		b=b/2;
    	}
    	return s;
    }
}

分析&&結論

從結果上看,已經很明顯了,比較程序的運行效率:
手寫long範圍快速冪(875ms) > 調用modPow(1454ms) > 手寫大數快速冪(指數爲long範圍 1454ms/三個參數均爲大數 1875ms) 。

說明如果底數、指數、模數都沒有超過long範圍,手寫快速冪最快;
如果需要用到大數,調用modPow比手寫快。

BigInteger.class中的modPow方法源碼

源碼確實還是很強的(不愧是Java創始人寫的),modPow方法要求三個參數均爲BigInteger類,單從這題測試來看,如果要求手寫的快速冪傳入參數均爲BigInteger類,那麼源碼的方法比手寫快了400MS+,這還只是long範圍的測試,如果全是大數運算的快速冪,時間差距應該會更大。

源碼沒太看懂,應該是用數學方法優化了吧,有modInverse求逆元,還有中國剩餘定理等等(Combine results using Chinese Remainder Theorem)。

/**
 * Returns a BigInteger whose value is
 * <tt>(this<sup>exponent</sup> mod m)</tt>.  (Unlike {@code pow}, this
 * method permits negative exponents.)
 *
 * @param  exponent the exponent.
 * @param  m the modulus.
 * @return <tt>this<sup>exponent</sup> mod m</tt>
 * @throws ArithmeticException {@code m} &le; 0 or the exponent is
 *         negative and this BigInteger is not <i>relatively
 *         prime</i> to {@code m}.
 * @see    #modInverse
 */
public BigInteger modPow(BigInteger exponent, BigInteger m) {
    if (m.signum <= 0)
        throw new ArithmeticException("BigInteger: modulus not positive");

    // Trivial cases
    if (exponent.signum == 0)
        return (m.equals(ONE) ? ZERO : ONE);

    if (this.equals(ONE))
        return (m.equals(ONE) ? ZERO : ONE);

    if (this.equals(ZERO) && exponent.signum >= 0)
        return ZERO;

    if (this.equals(negConst[1]) && (!exponent.testBit(0)))
        return (m.equals(ONE) ? ZERO : ONE);

    boolean invertResult;
    if ((invertResult = (exponent.signum < 0)))
        exponent = exponent.negate();

    BigInteger base = (this.signum < 0 || this.compareTo(m) >= 0
                       ? this.mod(m) : this);
    BigInteger result;
    if (m.testBit(0)) { // odd modulus
        result = base.oddModPow(exponent, m);
    } else {
        /*
         * Even modulus.  Tear it into an "odd part" (m1) and power of two
         * (m2), exponentiate mod m1, manually exponentiate mod m2, and
         * use Chinese Remainder Theorem to combine results.
         */

        // Tear m apart into odd part (m1) and power of 2 (m2)
        int p = m.getLowestSetBit();   // Max pow of 2 that divides m

        BigInteger m1 = m.shiftRight(p);  // m/2**p    //shiftRight 右移
        BigInteger m2 = ONE.shiftLeft(p); // 2**p      //shiftLight 左移

        // Calculate new base from m1
        BigInteger base2 = (this.signum < 0 || this.compareTo(m1) >= 0
                            ? this.mod(m1) : this);

        // Caculate (base ** exponent) mod m1.
        BigInteger a1 = (m1.equals(ONE) ? ZERO :
                         base2.oddModPow(exponent, m1));

        // Calculate (this ** exponent) mod m2
        BigInteger a2 = base.modPow2(exponent, p);

        // Combine results using Chinese Remainder Theorem
        BigInteger y1 = m2.modInverse(m1);//逆元
        BigInteger y2 = m1.modInverse(m2);

        if (m.mag.length < MAX_MAG_LENGTH / 2) {
            result = a1.multiply(m2).multiply(y1).add(a2.multiply(m1).multiply(y2)).mod(m);
        } else {
            MutableBigInteger t1 = new MutableBigInteger();
            new MutableBigInteger(a1.multiply(m2)).multiply(new MutableBigInteger(y1), t1);
            MutableBigInteger t2 = new MutableBigInteger();
            new MutableBigInteger(a2.multiply(m1)).multiply(new MutableBigInteger(y2), t2);
            t1.add(t2);
            MutableBigInteger q = new MutableBigInteger();
            result = t1.divide(new MutableBigInteger(m), q).toBigInteger();
        }
    }

    return (invertResult ? result.modInverse(m) : result);
}

希望能有大神能給我解答一下源碼到底是怎麼實現大數快速冪取模的

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