1. 程式人生 > >分治法-大整數乘法

分治法-大整數乘法

問題分析:

在計算機上處理一些大資料相乘時,由於計算機硬體的限制,不能直接進行相乘得到想要的結果。可以將一個大的整數乘法分而治之,將大問題變成小問題,變成簡單的小數乘法再進行合併,從而解決上述問題。

 

當分解到只有一位數時,乘法就很簡單了。

演算法設計:

分解:

首先將2個大整數a(n位)、b(m位)分解為兩部分:ah和al、bh和bl

ah表示大整數a的高位,al表示大整數a的低位,a=ah*10^{\frac{n}{2}}+al,ah、al為n/2位。

bh表示大整數b的高位,bl表示大整數b的低位,b=bh*10^{\frac{m}{2}}+bl,bh、bl為m/2位。

2個大整數a(n位)、b(m位)相乘轉換成了4個乘法運算ah*bh、ah*bl、al*bh、al*bl,而乘數的位數

變為了原來的一半。

求解子問題:

繼續分解兩個乘法運算,直到分解有一個乘數位1位數時停止分解,進行乘法運算並記錄結果。

合併:

將計算出的結果相加並回溯,求出最終結果。

#include<stdlib.h>
#include<cstring>
#include <iostream>

using namespace std;
#define M 100
char sa[1000];
char sb[1000];
typedef struct _Node {
    int s[M];
    int l;
    int c;
} Node, *pNode;

void cp(pNode src, pNode des, int st, int l) {
    int i, j;
    for (i = st, j = 0; i < st + 1; i++, j++) {
        des->s[j] = src->s[i];
    }
    des->l = l;
    des->c = st + src->c;
}

void add(pNode pa, pNode pb, pNode ans) {
    int i, cc, k, palen, pblen, len;
    int ta, tb;
    pNode temp;
    if ((pa->c < pb->c)) {
        temp = pa;
        pa = pb;
        pb = temp;
    }
    ans->c = pb->c;
    cc = 0;
    palen = pa->l + pa->c;
    pblen = pb->l + pb->c;
    if (palen > pblen)
        len = palen;
    else
        len = pblen;
    k = pa->c - pb->c;
    for (i = 0; i < len - ans->c; i++) {
        if (i < k)
            ta = 0;
        else
            ta = pa->s[i - k];
        if (i < pb->l)
            tb = pb->s[i];
        else
            tb = 0;
        if (i >= pa->l + k)
            ta = 0;
        ans->s[i] = (ta + tb + cc) % 10;
        cc = (ta + tb + cc) / 10;
    }
    if (cc)
        ans->s[i++] = cc;
    ans->l = i;
}

void mul(pNode pa, pNode pb, pNode ans) {
    int i, cc, w;
    int ma = pa->l >> 1, mb = pb->l >> 1;
    Node ah, al, bh, bl;
    Node t1, t2, t3, t4, z;
    pNode temp;
    if (!ma || !mb) {
        if (!ma) {
            temp = pa;
            pa = pb;
            pb = temp;
        }
        ans->c = pa->c + pb->c;
        w = pb->s[0];
        cc = 0;
        for (i = 0; i < pa->l; i++) {
            ans->s[i] = (w * pa->s[i] + cc) % 10;
            cc = (w * pa->s[i] + cc) / 10;
        }
        if (cc)
            ans->s[i++] = cc;
        ans->l = i;
        return;
    }
    cp(pa, &ah, ma, pa->l - ma);
    cp(pa, &al, 0, ma);
    cp(pb, &bh, mb, pb->l - mb);
    cp(pb, &bl, 0, mb);

    mul(&ah, &bh, &t1);
    mul(&ah, &bl, &t2);
    mul(&al, &bh, &t3);
    mul(&al, &bl, &t4);

    add(&t3, &t4, ans);
    add(&t2, ans, &z);
    add(&t1, &z, ans);
}


int main() {
    Node ans, a, b;
    cout << "輸入大整數 a:" << endl;
    cin >> sa;
    cout << "輸入大整數 b:" << endl;
    cin >> sb;
    a.l = strlen(sa);
    b.l = strlen(sb);
    int z = 0, i;
    for (i = a.l - 1; i >= 0; i--)
        a.s[z++] = sa[i] - '0';
    a.c = 0;
    z = 0;
    for (i = b.l - 1; i >= 0; i--)
        b.s[z++] = sb[i] - '0';
    b.c = 0;
    mul(&a, &b, &ans);
    cout << "最終結果為:";
    for (i = ans.l - 1; i >= 0; i--)
        cout << ans.s[i];
    cout << endl;
    return 0;
}

程式碼解釋:

1、將兩個輸入的大數,倒序儲存在陣列s[]中,l表示長度,c表示冪,c初始為0。

2、cp函式:將一個n位的數,分成兩個n/2的數並存儲,記錄它的長度和次冪。

3、mul函式,不斷地分解,直到有一個乘數為1位數時停止分解,進行乘法並記錄結果。

4、add函式,將分解得到的數,進行相加合併。

程式碼流程:

初始化:將a、b倒序儲存在陣列a.s[],b.s[]中。

分解:cp函式:將一個n位的數,分成兩個n/2的數並存儲,記錄它的長度和次冪。ah表示高位,al表示低位,l用來表示數的長度,c表示次冪。

轉換為4次乘法運算:ah*bh,ah*bl,al*bh,al*bl:

 求解子問題:

ah*bh,ah*bl,al*bh,al*bl

繼續求解子問題:

上述4個乘法運算都有一個乘數為1位數,可以直接進行乘法運算。以ahh*bhh為例:

3首先和1相乘得到3儲存在下面陣列的第0位,然後3和4相乘得到12,先儲存12%10=2,然後儲存進位12/10=1,那樣結果就為倒序的321,結果的次冪是兩個乘數次冪之和

4個乘法運算結果如下圖:

合併:

合併子問題結果,返回給ah*bh,將上面4個乘法運算的結果加起來返回給ah*bh。

 

由此得到ah*bh=13408x10^4。

用同樣的方法求得ah*bl=832x10^2,al*bh=32682x10^2,al*bl=2028。將這4個子問題結果加起來,合併得到原問題a*b=137433428。

 

演算法複雜度分析:

假設兩個n位大整數相乘的時間複雜度為T(n),則:

 當n>1時,可以遞推求解如下:

 遞推最終的規模為1,令n=2^x,則x=logn,那麼有:

大整數乘法的時間複雜度為O(n^2)。

空間複雜度:

程式中變數佔用了一些輔助空間,都是常數階,但合併時結點陣列佔用的輔助空間為O(n),遞迴呼叫所使用的棧空間時O(logn)。所以,空間複雜度為O(n)。