分治法的原理
討論問題時,先來了解一下什么是分治法。
分治法的意思就是,分而治之,也就是把一個問題,拆分成幾個小問題,最后再匯總解決的方法
通過大整數相乘問題來了解分治法
假如現在我們要求兩個大整數相乘的乘積,如1234 * 1234(這里為了了分析簡便,所以不舉形如1234567891234567這樣的大整數,不必要在此糾結),那么按照我們小學學的乘法,就是用乘數的每一項去和1234相乘,這樣很明顯,算法的時間復雜度是O(n^2),效率很低下,那么有沒有一種更好的方式?我們可以使用分治法來解決。
算法分析
- 首先將X和Y分成A,B,C,D
- 此時將X和Y的乘積轉化為圖中的式子,把問題轉化為求解式子的值
看起來好像有點變化,分析一下:對這個式子,我們一共要進行4次 n / 2的乘法(AC2次, AD, BC)和 3次加法,因而該算法的時間復雜度為:
T(n) = 4 * T(n / 2) + θ(n)
通過master定理可以求得 T(n) = θ(n ^ 2),跟小學算法的時間復雜度沒有區別。
但是我們再來看看,我們是否可以用加法來換取乘法?因為多一個加法操作,也是常數項,對時間復雜度沒有影響,如果減少一個乘法則不同。
XY=AC2^n+[(A-B)(D-C)+AC+BD]2^n/2+BD
現在的時間復雜度為:
T(n) = 3 * T(n / 2) + θ(n),通過master定理求得,T(n) = O(n^log2(3) ) = O(n^1.59 )。
Master定理:
Master定理實例:
時間復雜度O的意義
實例分析
對X = 1234 和 Y = 5678來分析:
divideConquer(1234, 5678, 4)
————————————————————————
X=1234 | A=12 | B=34 | A-B=-22
Y=5678 | C=56 | D=78 | D-C=22
三次遞歸:
AC = divideConquer(12, 56, 2)
BD = divideConquer(34, 78, 2)
(A-B)(D-C) = divideConquer(-22, 22, 2)
AC遞歸
————————————————————————
X=12 | A1=1 | B1=2 | A1-B1=-1
Y=56 | C1=5 | D1=6 | D1-C1=1
A1*C1 = divideConquer(1, 5, 1) = 5
B1*D1 = divideConquer(2, 6, 1) = 12
(A1-B1) * (D1-C1) = divideConquer(-1, 1, 1) = -1;
所以AC遞歸求得的值為:5 * 10 ^ 2 + (-1 + 5 + 12)* 10 + 12 = 672
同理可得 BD = 2652 和 (A-B)(D-C) = -484
最終可得 X * Y = 7006652
代碼實現
#include<cstdio> #include<cmath> using namespace std; #define SIGN(A) ((A > 0) ? 1 : -1) int divideConquer(int X, int Y, int n){ int sign = SIGN(X) * SIGN(Y); int x = abs(X); int y = abs(Y); if(x == 0 || y == 0){ return 0; }else if(n == 1){ return sign * x * y; }else{ int A = (int) x / pow(10, (int)(n / 2)); int B = x - A * pow(10, n / 2); int C = (int) y / pow(10, (int)(n / 2)); int D = y - C * pow(10, n / 2); int AC = divideConquer(A, C, n / 2); int BD = divideConquer(B, D, n / 2); int ABDC = divideConquer((A - B), (D - C), n / 2) + AC + BD; return sign * (AC * pow(10 , n) + ABDC * pow(10, (int)(n / 2)) + BD); } } int main(){ int x, y, n; scanf("%d%d%d", &x, &y, &n); printf("x 和 y的乘積為:%d", divideConquer(x, y, n)); }
結果: