分治法解大整数相乘

ochonglangzheo 2013-08-19 04:30:01
从网上下的一个比较靠谱的大整数相乘的程序,但是就是效率非常低,求大神帮忙优化下,或者提供一个供小弟参考下


#include <iostream>
#include <sstream>
#include <string>

using namespace std;
//string类型转换成int类型

int string_to_num(string k)//string字符串变整数型例如str="1234",转换为整数的1234.
{
int back;
stringstream instr(k);
instr>>back;
return back;
}

//整形数转换为string类型
string num_to_string(int intValue)
{
string result;
stringstream stream;
stream << intValue;//将int输入流
stream >> result;//从stream中抽取前面放入的int值
return result;
}

//在字符串str前添加s个零
string stringBeforeZero(string str,int s)
{
for(int i=0;i<s;i++)
str.insert(0,"0");

return str;
}

//两个大整数字符串相加,超出计算机表示范围的数也能实现相加(本函数可以实现大整数加法运算)
string stringAddstring(string str1,string str2)
{
//假定str1和str2是相等的长度,不相等时在前面自动补零,使两个字符串长度相等
if (str1.size() > str2.size())
str2 = stringBeforeZero(str2,str1.size() - str2.size());
else if (str1.size() < str2.size())
str1 = stringBeforeZero(str1,str2.size() - str1.size());


string result;
int flag=0;//前一进位是否有标志,0代表无进位,1代表有进位
for(int i=str1.size()-1;i>=0;i--)
{
int c = (str1[i] - '0') + (str2[i] - '0') + flag;//利用ASCII码对字符进行运算,这里加上flag代表的是:当前一位有进位时加1,无进位时加0
flag = c/10;//c大于10时,flag置为1,否则为0
c %= 10;//c大于10时取模,否则为其本身
result.insert(0,num_to_string(c));//在result字符串最前端插入新生成的单个字符
}
if (0 != flag) //最后一为(最高位)判断,如果有进位则再添一位
result.insert(0,num_to_string(flag));

return result;
}

/*两个大整数字符串相减,超出计算机表示范围的数也能实现相减(在这里比较特殊,第一个参数一定大于第二个参数,
因为:a1*b0+a0*b1=(a1+a0)*(b1+b0)-(a1*b1+a0*b0) > 0 ,所以(a1+a0)*(b1+b0) > (a1*b1+a0*b0)
这个函数的两个参数,第一个代表的其实就是(a1+a0)*(b1+b0),第二个代表的其实就是(a1*b1+a0*b0)
所以本函数里不用考虑最终得到结果为负数的情况,至于计算有关大整数负数相乘的问题可以通过其他途径判断
*/
string stringSubtractstring(string str1,string str2)
{
//对传进来的两个数进行修剪,如果前面几位有0则先去掉,便于统一处理
while ('0' == str1[0]&&str1.size()>1)
str1=str1.substr(1,str1.size()-1);

while ('0' == str2[0]&&str2.size()>1)
str2=str2.substr(1,str2.size()-1);


//使两个字符串长度相等
if (str1.size() > str2.size())
str2 = stringBeforeZero(str2,str1.size() - str2.size());


string result;
for(int i=str1.size()-1;i>=0;i--)
{
int c = (str1[i] - '0') - (str2[i] - '0');//利用ASCII码进行各位减法运算
if (c < 0) //当不够减时向前一位借位,前一位也不够位时再向前一位借位,依次如下
{
c +=10;
int prePos = i-1;
char preChar = str1[prePos];
while ('0' == preChar)
{
str1[prePos]='9';
prePos -= 1;
preChar = str1[prePos];
}

str1[prePos]-=1;
}

result.insert(0,num_to_string(c));//在result字符串最前端插入新生成的单个字符
}
return result;
}

//在字符串str后跟随s个零
string stringFollowZero(string str,int s)
{
for(int i=0;i<s;i++)
str.insert(str.size(),"0");

return str;
}

//分治法大整数乘法实现函数
string IntMult(string x,string y)//递归函数
{
//对传进来的第一个数进行修剪,如果前面几位有0则先去掉,便于统一处理
while ('0' == x[0]&&x.size()>1)
x=x.substr(1,x.size()-1);

//对传进来的第二个数进行修剪,如果前面几位有0则先去掉,便于统一处理
while ('0' == y[0]&&y.size()>1)
y=y.substr(1,y.size()-1);

/*这里的f变量代表在两个数据字符串长度不想等或者不是2的指数倍的情况下,所要统一的长度,这样做是为了让数据长度为2的n次方的情况下便于利用分治法处理*/

int f=4;
/*当两字符串中有任意一个字符串长度大于2时都要通过与上面定义的f值进行比较,使其达到数据长度为2的n次方,实现方式是在前面补0,这样可以保证数据值大小不变*/
if (x.size()>2 || y.size()>2)
{
if (x.size() >= y.size()) //第一个数长度大于等于第二个数长度的情况
{
while (x.size()>f) //判断f值
f*=2;
if (x.size() != f)
{
x = stringBeforeZero(x,f-x.size());
y = stringBeforeZero(y,f-y.size());
}
}else//第二个数长度大于第一个数长度的情况
{
while (y.size()>f) //判断f值
f*=2;

if (y.size() != f)
{
x = stringBeforeZero(x,f-x.size());
y = stringBeforeZero(y,f-y.size());
}
}
}

if (1 == x.size()) //数据长度为1时,在前面补一个0(这里之所以会出现长度为1的数据是因为前面对数据修剪过)
x=stringBeforeZero(x,1);

if (1 == y.size()) //数据长度为1时,在前面补一个0(这里之所以会出现长度为1的数据是因为前面对数据修剪过)
y=stringBeforeZero(y,1);

if (x.size() > y.size()) //最后一次对数据校正,确保两个数据长度统一
y = stringBeforeZero(y,x.size()-y.size());

if (x.size() < y.size()) //最后一次对数据校正,确保两个数据长度统一
x = stringBeforeZero(x,y.size()-x.size());

int s = x.size();
string a1,a0,b1,b0;
if( s > 1)
{
a1 = x.substr(0,s/2);
a0 = x.substr(s/2,s-1);
b1 = y.substr(0,s/2);
b0 = y.substr(s/2,s-1);
}
string result;
if( s == 2) //长度为2时代表着递归的结束条件
{
int na = string_to_num(a1);
int nb = string_to_num(a0);
int nc = string_to_num(b1);
int nd = string_to_num(b0);
result = num_to_string((na*10+nb) * (nc*10+nd));
}
else{ //长度不为2时利用分治法进行递归运算
/***************************************************
小提示:
c = a*b = c2*(10^n) + c1*(10^(n/2)) + c0;
a = a1a0 = a1*(10^(n/2)) + a0;
b = b1b0 = b1*(10^(n/2)) + b0;
c2 = a1*b1; c0 = a0*b0;
c1 = (a1 + a0)*(b1 + b0)-(c2 + c0);
****************************************************/
string c2 = IntMult(a1,b1);// (a1 * b1)
string c0 = IntMult(a0,b0);// (a0 * b0)
string c1_1 = stringAddstring(a1,a0);// (a1 + a0)
string c1_2 = stringAddstring(b1,b0);// (b1 + b0)
string c1_3 = IntMult(c1_1,c1_2);// (a1 + a0)*(b1 + b0)
string c1_4 = stringAddstring(c2,c0);// (c2 + c0)
string c1=stringSubtractstring(c1_3,c1_4);// (a1 + a0)*(b1 + b0)-(c2 + c0)
string s1=stringFollowZero(c1,s/2);// c1*(10^(n/2))
string s2=stringFollowZero(c2,s);// c2*(10^n)
result = stringAddstring(stringAddstring(s2,s1),c0);// c2*(10^n) + c1*(10^(n/2)) + c0
}

return result;
}

void main()
{
int f=1;
while (1 == f)
{
string A,B,C,D;
string num1,num2;
string r;
cout<<"大整数乘法运算(分治法实现)"<<endl;
cout<<"请输入第一个大整数(任意长度):";
cin>>num1;
int i=0;
//判断第一个输入的数据是否合法,当字符串中包含非数字字符时提示用户重新输入
for(i=0 ;i < num1.size();i++)
{
if (num1[i]<'0' || num1[i]>'9')
{
cout<<"您输入的数据不合法,请输入有效的整数!"<<endl;
cin>>num1;
i=-1;
}
}



cout<<"请输入第二个大整数(任意长度):";
cin>>num2;
//判断第二个输入的数据是否合法,当字符串中包含非数字字符时提示用户重新输入
for(i=0 ;i < num2.size();i++)
{
if (num2[i]<'0' || num2[i]>'9')
{
cout<<"您输入的数据不合法,请输入有效的整数!"<<endl;
cin>>num2
i=-1;
}
}

r=IntMult(num1,num2);

//对求得的结果进行修剪,去掉最前面的几个0
while ('0' == r[0]&&r.size()>1)
{
r=r.substr(1,r.size()-1);
}
cout<<"两数相乘结果为:"<<endl;
cout<<num1<<" "<<"*"<<" "<<num2<<" "<<"="<<" "<<r<<endl<<endl;
}
}


...全文
2912 26 打赏 收藏 转发到动态 举报
写回复
用AI写文章
26 条回复
切换为时间正序
请发表友善的回复…
发表回复
lm_whales 2013-08-22
  • 打赏
  • 举报
回复
引用 23 楼 zhao4zhong1 的回复:
[quote=引用 16 楼 fetag 的回复:] [quote=引用 12 楼 ochonglangzheo 的回复:] 我找的这个核心用的是Karatsuba 算法,但是效率太低了,是不是补0造成的效率降低,大神你实现过Karatsuba 算法吗,可不可以拿来参考下
我没写过。帮你google了下,这里有C++的,你参考下吧 http://ozark.hendrix.edu/~burch/proj/karat/ [/quote]
// fast Karatsuba multiplication
// 21 Jan 1999, Carl Burch, cburch@cmu.edu
//
// This program implements a reasonably efficient multiplication
// algorithm (Karatsuba multiplication) and times it against the
// traditional grade-school technique.
//
// (c) 1999, Carl Burch
// This may not be copied without retaining this copyright notice,
// and it may not be distributed in modified form.
#include <stdlib.h>
#include <stdio.h>
#include <time.h>

#define MAX_DIGITS 1024
// Numbers will be stored as an array arr of integers.
// arr[0] is a one's digit, arr[1] the 10's digit, etc.
// Note that this means that how we normally write numbers and how
// we normally draw arrays is reversed, which is a bit confusing.
//
// Before changing this constant, note the warning mentioned below
// about potential overflow problems.

#define KARAT_CUTOFF 4
// When karatsuba() gets to numbers with at most KARAT_CUTOFF
// digits, it reverts to straight grade-school multiplication.
// (This helps because karatsuba() is slower than grade-school
// multiplication for tiny values of n.)

// Within karatsuba() and gradeSchool(), we do not worry about whether
// the `digits' are actually between 0 and 9; this is fixed after we
// return from the call with doCarry().
//
// WARNING! This is potentially problematic if the `digits' get so
// large that we have overflow. With 32-bit ints and KARAT_CUTOFF==4,
// we are safe up to 1024 digits; more than this is potentially
// problematic. One easy way to avoid this is to call doCarry()
// for larger values, but the below code does not do this.

void            karatsuba(int *a, int *b, int *ret, int d);
void            gradeSchool(int *a, int *b, int *ret, int d);
void            doCarry(int *a, int d);
void            getNum(int *a, int *d_a);
void            printNum(int *a, int d);

int
main() {
    int             a[MAX_DIGITS]; // first multiplicand
    int             b[MAX_DIGITS]; // second multiplicand
    int             r[6 * MAX_DIGITS]; // result goes here
    int             d_a; // length of a
    int             d_b; // length of b
    int             d; // maximum length
    int             i; // counter
    clock_t         start; // for timing
    clock_t         stop; // for timing

    getNum(a, &d_a);
    getNum(b, &d_b);

    if(d_a < 0 || d_b < 0) {
        printf("0\n");
        exit(0);
        return 0;
    }

    // let d be the smallest power of 2 greater than d_a and d_b,
    // and zero out the rest of a and b.
    i = (d_a > d_b) ? d_a : d_b;
    for(d = 1; d < i; d *= 2);
    for(i = d_a; i < d; i++) a[i] = 0;
    for(i = d_b; i < d; i++) b[i] = 0;

    // do the trials, first for Karatsuba, then for grade-school.
    // For each trial, we print the result, followed by the time
    // taken per multiplication, followed by the number of
    // multiplications done. We do as many multiplications as we
    // can until we pass away an entire second.
    start = clock();
    stop = start + CLOCKS_PER_SEC;
    for(i = 0; clock() < stop; i++) {
        karatsuba(a, b, r, d); // compute product w/o regard to carry
        doCarry(r, 2 * d); // now do any carrying
    }
    start = clock() - start;
    printNum(r, 2 * d);
    printf(" %f ms (%d trials)\n", 1000 * (double) start / CLOCKS_PER_SEC / i, i);

    start = clock();
    stop = start + CLOCKS_PER_SEC;
    for(i = 0; clock() < stop; i++) {
        gradeSchool(a, b, r, d); // compute product in old way
        doCarry(r, 2 * d); // now do any carrying
    }
    start = clock() - start;
    printNum(r, 2 * d);
    printf(" %f ms (%d trials)\n", 1000 * (double) start / CLOCKS_PER_SEC / i, i);
}

// ret must have space for 6d digits.
// the result will be in only the first 2d digits
// my use of the space in ret is pretty creative.
// | ar*br  | al*bl  | asum*bsum | lower-recursion space | asum | bsum |
//  d digits d digits  d digits     3d digits              d/2    d/2
void
karatsuba(int *a, int *b, int *ret, int d) {
    int             i;
    int             *ar = &a[0]; // low-order half of a
    int             *al = &a[d/2]; // high-order half of a
    int             *br = &b[0]; // low-order half of b
    int             *bl = &b[d/2]; // high-order half of b
    int             *asum = &ret[d * 5]; // sum of a's halves
    int             *bsum = &ret[d * 5 + d/2]; // sum of b's halves
    int             *x1 = &ret[d * 0]; // ar*br's location
    int             *x2 = &ret[d * 1]; // al*bl's location
    int             *x3 = &ret[d * 2]; // asum*bsum's location

    // when d is small, we're better off just reverting to
    // grade-school multiplication, since it's faster at this point.
    if(d <= KARAT_CUTOFF) {
        gradeSchool(a, b, ret, d);
        return;
    }

    // compute asum and bsum
    for(i = 0; i < d / 2; i++) {
        asum[i] = al[i] + ar[i];
        bsum[i] = bl[i] + br[i];
    }

    // do recursive calls (I have to be careful about the order,
    // since the scratch space for the recursion on x1 includes
    // the space used for x2 and x3)
    karatsuba(ar, br, x1, d/2);
    karatsuba(al, bl, x2, d/2);
    karatsuba(asum, bsum, x3, d/2);

    // combine recursive steps
    for(i = 0; i < d; i++) x3[i] = x3[i] - x1[i] - x2[i];
    for(i = 0; i < d; i++) ret[i + d/2] += x3[i];
}

void
gradeSchool(int *a, int *b, int *ret, int d) {
    int             i, j;

    for(i = 0; i < 2 * d; i++) ret[i] = 0;
    for(i = 0; i < d; i++) {
        for(j = 0; j < d; j++) ret[i + j] += a[i] * b[j];
    }
}

void
doCarry(int *a, int d) {
    int             c;
    int             i;

    c = 0;
    for(i = 0; i < d; i++) {
        a[i] += c;
        if(a[i] < 0) {
            c = -(-(a[i] + 1) / 10 + 1);
        } else {
            c = a[i] / 10;
        }
        a[i] -= c * 10;
    }
    if(c != 0) fprintf(stderr, "Overflow %d\n", c);
}

void
getNum(int *a, int *d_a) {
    int             c;
    int             i;

    *d_a = 0;
    while(true) {
        c = getchar();
        if(c == '\n' || c == EOF) break;
        if(*d_a >= MAX_DIGITS) {
            fprintf(stderr, "using only first %d digits\n", MAX_DIGITS);
            while(c != '\n' && c != EOF) c = getchar();
        }
        a[*d_a] = c - '0';
        ++(*d_a);
    }
    // reverse the number so that the 1's digit is first
    for(i = 0; i * 2 < *d_a - 1; i++) {
        c = a[i], a[i] = a[*d_a - i - 1], a[*d_a - i - 1] = c;
    }
}

void
printNum(int *a, int d) {
    int i;
    for(i = d - 1; i > 0; i--) if(a[i] != 0) break;
    for(; i >= 0; i--) printf("%d", a[i]);
}
[/quote] ++ 这个有点靠谱 字符串的,不是很实用
a2471388918 2013-08-21
  • 打赏
  • 举报
回复
引用 1 楼 zhao4zhong1 的回复:
仅供参考
#include <iostream>
#include <string>
using namespace std;
inline int compare(string str1,string str2) {//相等返回0,大于返回1,小于返回-1
         if (str1.size()>str2.size()) return 1; //长度长的整数大于长度小的整数
    else if (str1.size()<str2.size()) return -1;
    else                              return str1.compare(str2); //若长度相等,则头到尾按位比较
}
string SUB_INT(string str1,string str2);
string ADD_INT(string str1,string str2) {//高精度加法
    int sign=1; //sign 为符号位
    string str;
    if (str1[0]=='-') {
        if (str2[0]=='-') {
            sign=-1;
            str=ADD_INT(str1.erase(0,1),str2.erase(0,1));
        } else {
            str=SUB_INT(str2,str1.erase(0,1));
        }
    } else {
        if (str2[0]=='-') {
            str=SUB_INT(str1,str2.erase(0,1));
        } else { //把两个整数对齐,短整数前面加0补齐
            string::size_type L1,L2;
            int i;
            L1=str1.size();
            L2=str2.size();
            if (L1<L2) {
                for (i=1;i<=L2-L1;i++) str1="0"+str1;
            } else {
                for (i=1;i<=L1-L2;i++) str2="0"+str2;
            }
            int int1=0,int2=0; //int2 记录进位
            for (i=str1.size()-1;i>=0;i--) {
                int1=(int(str1[i])-'0'+int(str2[i])-'0'+int2)%10;
                int2=(int(str1[i])-'0'+int(str2[i])-'0'+int2)/10;
                str=char(int1+'0')+str;
            }
            if (int2!=0) str=char(int2+'0')+str;
        }
    }
    //运算后处理符号位
    if ((sign==-1)&&(str[0]!='0')) str="-"+str;
    return str;
}
string SUB_INT(string str1,string str2) {//高精度减法
    int sign=1; //sign 为符号位
    string str;
    int i,j;
    if (str2[0]=='-') {
        str=ADD_INT(str1,str2.erase(0,1));
    } else {
        int res=compare(str1,str2);
        if (res==0) return "0";
        if (res<0) {
            sign=-1;
            string temp =str1;
            str1=str2;
            str2=temp;
        }
        string::size_type tempint;
        tempint=str1.size()-str2.size();
        for (i=str2.size()-1;i>=0;i--) {
            if (str1[i+tempint]<str2[i]) {
                j=1;
                while (1) {//zhao4zhong1添加
                    if (str1[i+tempint-j]=='0') {
                        str1[i+tempint-j]='9';
                        j++;
                    } else {
                        str1[i+tempint-j]=char(int(str1[i+tempint-j])-1);
                        break;
                    }
                }
                str=char(str1[i+tempint]-str2[i]+':')+str;
            } else {
                str=char(str1[i+tempint]-str2[i]+'0')+str;
            }
        }
        for (i=tempint-1;i>=0;i--) str=str1[i]+str;
    }
    //去除结果中多余的前导0
    str.erase(0,str.find_first_not_of('0'));
    if (str.empty()) str="0";
    if ((sign==-1) && (str[0]!='0')) str ="-"+str;
    return str;
}
string MUL_INT(string str1,string str2) {//高精度乘法
    int sign=1; //sign 为符号位
    string str;
    if (str1[0]=='-') {
        sign*=-1;
        str1 =str1.erase(0,1);
    }
    if (str2[0]=='-') {
        sign*=-1;
        str2 =str2.erase(0,1);
    }
    int i,j;
    string::size_type L1,L2;
    L1=str1.size();
    L2=str2.size();
    for (i=L2-1;i>=0;i--) { //模拟手工乘法竖式
        string tempstr;
        int int1=0,int2=0,int3=int(str2[i])-'0';
        if (int3!=0) {
            for (j=1;j<=(int)(L2-1-i);j++) tempstr="0"+tempstr;
            for (j=L1-1;j>=0;j--) {
                int1=(int3*(int(str1[j])-'0')+int2)%10;
                int2=(int3*(int(str1[j])-'0')+int2)/10;
                tempstr=char(int1+'0')+tempstr;
            }
            if (int2!=0) tempstr=char(int2+'0')+tempstr;
        }
        str=ADD_INT(str,tempstr);
    }
    //去除结果中的前导0
    str.erase(0,str.find_first_not_of('0'));
    if (str.empty()) str="0";
    if ((sign==-1) && (str[0]!='0')) str="-"+str;
    return str;
}
string DIVIDE_INT(string str1,string str2,int flag) {//高精度除法。flag==1时,返回商; flag==0时,返回余数
    string quotient,residue; //定义商和余数
    int sign1=1,sign2=1;
    if (str2 == "0") {  //判断除数是否为0
        quotient= "ERROR!";
        residue = "ERROR!";
        if (flag==1) return quotient;
        else         return residue ;
    }
    if (str1=="0") { //判断被除数是否为0
        quotient="0";
        residue ="0";
    }
    if (str1[0]=='-') {
        str1   = str1.erase(0,1);
        sign1 *= -1;
        sign2  = -1;
    }
    if (str2[0]=='-') {
        str2   = str2.erase(0,1);
        sign1 *= -1;
    }
    int res=compare(str1,str2);
    if (res<0) {
        quotient="0";
        residue =str1;
    } else if (res == 0) {
        quotient="1";
        residue ="0";
    } else {
        string::size_type L1,L2;
        L1=str1.size();
        L2=str2.size();
        string tempstr;
        tempstr.append(str1,0,L2-1);
        for (int i=L2-1;i<L1;i++) { //模拟手工除法竖式
            tempstr=tempstr+str1[i];
            tempstr.erase(0,tempstr.find_first_not_of('0'));//zhao4zhong1添加
            if (tempstr.empty()) tempstr="0";//zhao4zhong1添加
            for (char ch='9';ch>='0';ch--) { //试商
                string str;
                str=str+ch;
                if (compare(MUL_INT(str2,str),tempstr)<=0) {
                    quotient=quotient+ch;
                    tempstr =SUB_INT(tempstr,MUL_INT(str2,str));
                    break;
                }
            }
        }
        residue=tempstr;
    }
    //去除结果中的前导0
    quotient.erase(0,quotient.find_first_not_of('0'));
    if (quotient.empty()) quotient="0";
    if ((sign1==-1)&&(quotient[0]!='0')) quotient="-"+quotient;
    if ((sign2==-1)&&(residue [0]!='0')) residue ="-"+residue ;
    if (flag==1) return quotient;
    else         return residue ;
}
string DIV_INT(string str1,string str2) {//高精度除法,返回商
    return DIVIDE_INT(str1,str2,1);
}
string MOD_INT(string str1,string str2) {//高精度除法,返回余数
    return DIVIDE_INT(str1,str2,0);
}
int main() {
    char ch;
    string s1,s2,res;

    while (cin>>s1>>ch>>s2) {
        switch (ch) {
            case '+':res=ADD_INT(s1,s2);break;
            case '-':res=SUB_INT(s1,s2);break;
            case '*':res=MUL_INT(s1,s2);break;
            case '/':res=DIV_INT(s1,s2);break;
            case '%':res=MOD_INT(s1,s2);break;
            default :                   break;
        }
        cout<<res<<endl;
    }
    return(0);
}
看到这些我已经严重自卑了,不知道什么时候能有这样的水平,都不想学了
嗷嗷叫的老马 2013-08-21
  • 打赏
  • 举报
回复
独孤过儿 2013-08-20
  • 打赏
  • 举报
回复
引用 17 楼 FancyMouse 的回复:
分治法从来都是n^c复杂度,c是个大于1小于等于log3/log2的常数。分治从来就没达到过n*logn级别。到n*logn的只有FFT一个。
只有引用的内容不允许回复。
FancyMouse 2013-08-20
  • 打赏
  • 举报
回复
引用 15 楼 fetag 的回复:
[quote=引用 10 楼 FancyMouse 的回复:] [quote=引用 3 楼 fetag 的回复:] 大数相乘最常用的有两种,开销都是O(n*logn*loglogn),分别是Karatsuba algorithm和鼎鼎大名的FFT。你自己去网上搜代码吧 楼上赵神棍贴的代码是O(n^2)的开销,这样的开销在大数相乘的时候完全是废柴。
分治法从来都是n^c复杂度,c是个大于1小于等于log3/log2的常数。分治从来就没达到过n*logn级别。到n*logn的只有FFT一个。[/quote] 分治法的指数因子是多少,取决于递归过程中每步分解成的子问题数量,不局限于log3/log2。比如Strassen algorithm for matrix multiplication,那里的c就是2.8左右,总的开销O(n^2.8) 严格来讲,FFT的开销不是Big-O n*logn,而是O-Theta n*logn,这两个是不相等的。这里的tricky在于相乘的两个数通常是十进制的,而n是二进制表示时的长度。在FFT转换过程中,涉及到系数的变换,系数的数量大概约等于n/10。因为此处是约等于,所以实际的开销会比nlogn大一点点,是O-Theta n*logn。[/quote] 我说过c=log3/log2?
独孤过儿 2013-08-20
  • 打赏
  • 举报
回复
引用 12 楼 ochonglangzheo 的回复:
我找的这个核心用的是Karatsuba 算法,但是效率太低了,是不是补0造成的效率降低,大神你实现过Karatsuba 算法吗,可不可以拿来参考下
我没写过。帮你google了下,这里有C++的,你参考下吧 http://ozark.hendrix.edu/~burch/proj/karat/
独孤过儿 2013-08-20
  • 打赏
  • 举报
回复
引用 10 楼 FancyMouse 的回复:
[quote=引用 3 楼 fetag 的回复:] 大数相乘最常用的有两种,开销都是O(n*logn*loglogn),分别是Karatsuba algorithm和鼎鼎大名的FFT。你自己去网上搜代码吧 楼上赵神棍贴的代码是O(n^2)的开销,这样的开销在大数相乘的时候完全是废柴。
分治法从来都是n^c复杂度,c是个大于1小于等于log3/log2的常数。分治从来就没达到过n*logn级别。到n*logn的只有FFT一个。[/quote] 分治法的指数因子是多少,取决于递归过程中每步分解成的子问题数量,不局限于log3/log2。比如Strassen algorithm for matrix multiplication,那里的c就是2.8左右,总的开销O(n^2.8) 严格来讲,FFT的开销不是Big-O n*logn,而是O-Theta n*logn,这两个是不相等的。这里的tricky在于相乘的两个数通常是十进制的,而n是二进制表示时的长度。在FFT转换过程中,涉及到系数的变换,系数的数量大概约等于n/10。因为此处是约等于,所以实际的开销会比nlogn大一点点,是O-Theta n*logn。
赵4老师 2013-08-20
  • 打赏
  • 举报
回复
引用 11 楼 wangdahu888 的回复:
[quote=引用 5 楼 fetag 的回复:] [quote=引用 4 楼 zhao4zhong1 的回复:] [quote=引用 3 楼 fetag 的回复:] 大数相乘最常用的有两种,开销都是O(n*logn*loglogn),分别是Karatsuba algorithm和鼎鼎大名的FFT。你自己去网上搜代码吧 楼上赵神棍贴的代码是O(n^2)的开销,这样的开销在大数相乘的时候完全是废柴。
我的代码能和那些真正的大师比吗?[/quote] 你脸肯定比大师们大!这个毋庸置疑[/quote] 赵大师,可是偶的偶像也[/quote] 古今中外几乎所有偶像都是既可以用来膜拜也可以用来质疑还可以用来鄙视的。
FancyMouse 2013-08-20
  • 打赏
  • 举报
回复
引用 12 楼 ochonglangzheo 的回复:
[quote=引用 3 楼 fetag 的回复:] 大数相乘最常用的有两种,开销都是O(n*logn*loglogn),分别是Karatsuba algorithm和鼎鼎大名的FFT。你自己去网上搜代码吧 楼上赵神棍贴的代码是O(n^2)的开销,这样的开销在大数相乘的时候完全是废柴。
我找的这个核心用的是Karatsuba 算法,但是效率太低了,是不是补0造成的效率降低,大神你实现过Karatsuba 算法吗,可不可以拿来参考下[/quote] 分治其实不慢的。没到十万级别的数据的话速度和FFT应该是差不多的。你说效率低你得告诉我们效率怎么低了。
ochonglangzheo 2013-08-20
  • 打赏
  • 举报
回复
引用 3 楼 fetag 的回复:
大数相乘最常用的有两种,开销都是O(n*logn*loglogn),分别是Karatsuba algorithm和鼎鼎大名的FFT。你自己去网上搜代码吧 楼上赵神棍贴的代码是O(n^2)的开销,这样的开销在大数相乘的时候完全是废柴。
我找的这个核心用的是Karatsuba 算法,但是效率太低了,是不是补0造成的效率降低,大神你实现过Karatsuba 算法吗,可不可以拿来参考下
  • 打赏
  • 举报
回复
引用 5 楼 fetag 的回复:
[quote=引用 4 楼 zhao4zhong1 的回复:] [quote=引用 3 楼 fetag 的回复:] 大数相乘最常用的有两种,开销都是O(n*logn*loglogn),分别是Karatsuba algorithm和鼎鼎大名的FFT。你自己去网上搜代码吧 楼上赵神棍贴的代码是O(n^2)的开销,这样的开销在大数相乘的时候完全是废柴。
我的代码能和那些真正的大师比吗?[/quote] 你脸肯定比大师们大!这个毋庸置疑[/quote] 赵大师,可是偶的偶像也
FancyMouse 2013-08-20
  • 打赏
  • 举报
回复
引用 3 楼 fetag 的回复:
大数相乘最常用的有两种,开销都是O(n*logn*loglogn),分别是Karatsuba algorithm和鼎鼎大名的FFT。你自己去网上搜代码吧 楼上赵神棍贴的代码是O(n^2)的开销,这样的开销在大数相乘的时候完全是废柴。
分治法从来都是n^c复杂度,c是个大于1小于等于log3/log2的常数。分治从来就没达到过n*logn级别。到n*logn的只有FFT一个。
赵4老师 2013-08-20
  • 打赏
  • 举报
回复
引用 16 楼 fetag 的回复:
[quote=引用 12 楼 ochonglangzheo 的回复:] 我找的这个核心用的是Karatsuba 算法,但是效率太低了,是不是补0造成的效率降低,大神你实现过Karatsuba 算法吗,可不可以拿来参考下
我没写过。帮你google了下,这里有C++的,你参考下吧 http://ozark.hendrix.edu/~burch/proj/karat/ [/quote]
// fast Karatsuba multiplication
// 21 Jan 1999, Carl Burch, cburch@cmu.edu
//
// This program implements a reasonably efficient multiplication
// algorithm (Karatsuba multiplication) and times it against the
// traditional grade-school technique.
//
// (c) 1999, Carl Burch
// This may not be copied without retaining this copyright notice,
// and it may not be distributed in modified form.
#include <stdlib.h>
#include <stdio.h>
#include <time.h>

#define MAX_DIGITS 1024
// Numbers will be stored as an array arr of integers.
// arr[0] is a one's digit, arr[1] the 10's digit, etc.
// Note that this means that how we normally write numbers and how
// we normally draw arrays is reversed, which is a bit confusing.
//
// Before changing this constant, note the warning mentioned below
// about potential overflow problems.

#define KARAT_CUTOFF 4
// When karatsuba() gets to numbers with at most KARAT_CUTOFF
// digits, it reverts to straight grade-school multiplication.
// (This helps because karatsuba() is slower than grade-school
// multiplication for tiny values of n.)

// Within karatsuba() and gradeSchool(), we do not worry about whether
// the `digits' are actually between 0 and 9; this is fixed after we
// return from the call with doCarry().
//
// WARNING! This is potentially problematic if the `digits' get so
// large that we have overflow. With 32-bit ints and KARAT_CUTOFF==4,
// we are safe up to 1024 digits; more than this is potentially
// problematic. One easy way to avoid this is to call doCarry()
// for larger values, but the below code does not do this.

void            karatsuba(int *a, int *b, int *ret, int d);
void            gradeSchool(int *a, int *b, int *ret, int d);
void            doCarry(int *a, int d);
void            getNum(int *a, int *d_a);
void            printNum(int *a, int d);

int
main() {
    int             a[MAX_DIGITS]; // first multiplicand
    int             b[MAX_DIGITS]; // second multiplicand
    int             r[6 * MAX_DIGITS]; // result goes here
    int             d_a; // length of a
    int             d_b; // length of b
    int             d; // maximum length
    int             i; // counter
    clock_t         start; // for timing
    clock_t         stop; // for timing

    getNum(a, &d_a);
    getNum(b, &d_b);

    if(d_a < 0 || d_b < 0) {
        printf("0\n");
        exit(0);
        return 0;
    }

    // let d be the smallest power of 2 greater than d_a and d_b,
    // and zero out the rest of a and b.
    i = (d_a > d_b) ? d_a : d_b;
    for(d = 1; d < i; d *= 2);
    for(i = d_a; i < d; i++) a[i] = 0;
    for(i = d_b; i < d; i++) b[i] = 0;

    // do the trials, first for Karatsuba, then for grade-school.
    // For each trial, we print the result, followed by the time
    // taken per multiplication, followed by the number of
    // multiplications done. We do as many multiplications as we
    // can until we pass away an entire second.
    start = clock();
    stop = start + CLOCKS_PER_SEC;
    for(i = 0; clock() < stop; i++) {
        karatsuba(a, b, r, d); // compute product w/o regard to carry
        doCarry(r, 2 * d); // now do any carrying
    }
    start = clock() - start;
    printNum(r, 2 * d);
    printf(" %f ms (%d trials)\n", 1000 * (double) start / CLOCKS_PER_SEC / i, i);

    start = clock();
    stop = start + CLOCKS_PER_SEC;
    for(i = 0; clock() < stop; i++) {
        gradeSchool(a, b, r, d); // compute product in old way
        doCarry(r, 2 * d); // now do any carrying
    }
    start = clock() - start;
    printNum(r, 2 * d);
    printf(" %f ms (%d trials)\n", 1000 * (double) start / CLOCKS_PER_SEC / i, i);
}

// ret must have space for 6d digits.
// the result will be in only the first 2d digits
// my use of the space in ret is pretty creative.
// | ar*br  | al*bl  | asum*bsum | lower-recursion space | asum | bsum |
//  d digits d digits  d digits     3d digits              d/2    d/2
void
karatsuba(int *a, int *b, int *ret, int d) {
    int             i;
    int             *ar = &a[0]; // low-order half of a
    int             *al = &a[d/2]; // high-order half of a
    int             *br = &b[0]; // low-order half of b
    int             *bl = &b[d/2]; // high-order half of b
    int             *asum = &ret[d * 5]; // sum of a's halves
    int             *bsum = &ret[d * 5 + d/2]; // sum of b's halves
    int             *x1 = &ret[d * 0]; // ar*br's location
    int             *x2 = &ret[d * 1]; // al*bl's location
    int             *x3 = &ret[d * 2]; // asum*bsum's location

    // when d is small, we're better off just reverting to
    // grade-school multiplication, since it's faster at this point.
    if(d <= KARAT_CUTOFF) {
        gradeSchool(a, b, ret, d);
        return;
    }

    // compute asum and bsum
    for(i = 0; i < d / 2; i++) {
        asum[i] = al[i] + ar[i];
        bsum[i] = bl[i] + br[i];
    }

    // do recursive calls (I have to be careful about the order,
    // since the scratch space for the recursion on x1 includes
    // the space used for x2 and x3)
    karatsuba(ar, br, x1, d/2);
    karatsuba(al, bl, x2, d/2);
    karatsuba(asum, bsum, x3, d/2);

    // combine recursive steps
    for(i = 0; i < d; i++) x3[i] = x3[i] - x1[i] - x2[i];
    for(i = 0; i < d; i++) ret[i + d/2] += x3[i];
}

void
gradeSchool(int *a, int *b, int *ret, int d) {
    int             i, j;

    for(i = 0; i < 2 * d; i++) ret[i] = 0;
    for(i = 0; i < d; i++) {
        for(j = 0; j < d; j++) ret[i + j] += a[i] * b[j];
    }
}

void
doCarry(int *a, int d) {
    int             c;
    int             i;

    c = 0;
    for(i = 0; i < d; i++) {
        a[i] += c;
        if(a[i] < 0) {
            c = -(-(a[i] + 1) / 10 + 1);
        } else {
            c = a[i] / 10;
        }
        a[i] -= c * 10;
    }
    if(c != 0) fprintf(stderr, "Overflow %d\n", c);
}

void
getNum(int *a, int *d_a) {
    int             c;
    int             i;

    *d_a = 0;
    while(true) {
        c = getchar();
        if(c == '\n' || c == EOF) break;
        if(*d_a >= MAX_DIGITS) {
            fprintf(stderr, "using only first %d digits\n", MAX_DIGITS);
            while(c != '\n' && c != EOF) c = getchar();
        }
        a[*d_a] = c - '0';
        ++(*d_a);
    }
    // reverse the number so that the 1's digit is first
    for(i = 0; i * 2 < *d_a - 1; i++) {
        c = a[i], a[i] = a[*d_a - i - 1], a[*d_a - i - 1] = c;
    }
}

void
printNum(int *a, int d) {
    int i;
    for(i = d - 1; i > 0; i--) if(a[i] != 0) break;
    for(; i >= 0; i--) printf("%d", a[i]);
}
独孤过儿 2013-08-20
  • 打赏
  • 举报
回复
引用 21 楼 FancyMouse 的回复:
对于大数乘法来说,正常的分治就是log3/log2,优化也只是分成更多的部分。如果划分成3份的话就是5次乘法,指数是log5/log3。但是这种方法分得再怎么多也不可能突破到1的,这就是我大于1的意思。如果你指的是上限突破log3/log2,那只是说明算法设计蠢了,没有讨论必要。 在大数乘法这个上下文里,分治一般都特指Karatsuba以及用它的idea做的变形。
我该说的差不多都说了,没有讨论的必要了
FancyMouse 2013-08-20
  • 打赏
  • 举报
回复
引用 20 楼 fetag 的回复:
[quote=引用 19 楼 FancyMouse 的回复:] 大于1"小于等于"log3/log2
我说的“不局限于”的意思是说:那个值不是限定于 大于1小于log3/log2范围的。 你去算法导论第四章看看 master theorem 吧,T(n)=aT(n/b)+f(n)这种形式。 另外,你们说的分治法是一类算法思想,并不等于Karatsuba algorithm。实际上gauss,Karatsuba,FFT,Schönhage–Strassen这些乘法算法全都是基于分治法的思想。[/quote] 对于大数乘法来说,正常的分治就是log3/log2,优化也只是分成更多的部分。如果划分成3份的话就是5次乘法,指数是log5/log3。但是这种方法分得再怎么多也不可能突破到1的,这就是我大于1的意思。如果你指的是上限突破log3/log2,那只是说明算法设计蠢了,没有讨论必要。 在大数乘法这个上下文里,分治一般都特指Karatsuba以及用它的idea做的变形。
独孤过儿 2013-08-20
  • 打赏
  • 举报
回复
引用 19 楼 FancyMouse 的回复:
大于1"小于等于"log3/log2
我说的“不局限于”的意思是说:那个值不是限定于 大于1小于log3/log2范围的。 你去算法导论第四章看看 master theorem 吧,T(n)=aT(n/b)+f(n)这种形式。 另外,你们说的分治法是一类算法思想,并不等于Karatsuba algorithm。实际上gauss,Karatsuba,FFT,Schönhage–Strassen这些乘法算法全都是基于分治法的思想。
FancyMouse 2013-08-20
  • 打赏
  • 举报
回复
引用 18 楼 fetag 的回复:
[quote=引用 17 楼 FancyMouse 的回复:] 分治法从来都是n^c复杂度,c是个大于1小于等于log3/log2的常数。分治从来就没达到过n*logn级别。到n*logn的只有FFT一个。
只有引用的内容不允许回复。[/quote] 大于1"小于等于"log3/log2
lpcads 2013-08-19
  • 打赏
  • 举报
回复
多谢。 等回到学校,看看学校里是否能下到那篇论文
独孤过儿 2013-08-19
  • 打赏
  • 举报
回复
引用 7 楼 lpcads 的回复:
请教下,那种fft的方法,是不是把大数当成以10为基的多项式,然后再做乘法? 但是这样的话,时间复杂度却是 theta(N*logN),是不是我遗漏了什么?
是的。FFT的《算法导论》第三版第30章有详细介绍。 关于复杂度的,你说的完全正确!但我也没写错,我写的是Big-O,你写的是O-Theta。O-Theta的定义是比Big-O多了个(log n)^k次方。在《算法导论》或者《Computational Complexity: A Modern Approach》中,能找到关于多个复杂度符号的定义。 另外,现在乘法最快的算法,是Martin Fürer在2007年发表,论文在Martin的主页有链接: http://www.cse.psu.edu/~furer/
lpcads 2013-08-19
  • 打赏
  • 举报
回复
引用 3 楼 fetag 的回复:
大数相乘最常用的有两种,开销都是O(n*logn*loglogn),分别是Karatsuba algorithm和鼎鼎大名的FFT。你自己去网上搜代码吧 楼上赵神棍贴的代码是O(n^2)的开销,这样的开销在大数相乘的时候完全是废柴。
请教下,那种fft的方法,是不是把大数当成以10为基的多项式,然后再做乘法? 但是这样的话,时间复杂度却是 theta(N*logN),是不是我遗漏了什么?
加载更多回复(6)

64,654

社区成员

发帖
与我相关
我的任务
社区描述
C++ 语言相关问题讨论,技术干货分享,前沿动态等
c++ 技术论坛(原bbs)
社区管理员
  • C++ 语言社区
  • encoderlee
  • paschen
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
  1. 请不要发布与C++技术无关的贴子
  2. 请不要发布与技术无关的招聘、广告的帖子
  3. 请尽可能的描述清楚你的问题,如果涉及到代码请尽可能的格式化一下

试试用AI创作助手写篇文章吧