大数据乘法/加法/减法

大数乘法

大数据乘法最容易想到的就是用数组或者字符串,string来模拟,由于数字无法用一个整形变量存储,很自然的想到用字符串来表示一串数字。然后按照乘法的运算规则,用一个乘数的每一位乘以另一个乘数,然后将所有中间结果按正确位置相加得到最终结果。可以分析得出如果乘数为A和B,A的位数为m,B的位数为n,则乘积结果为m+n-1位(最高位无进位)或m+n位(最高位有进位)。因此可以分配一个m+n的辅存来存储最终结果。为了节约空间,所有的中间结果直接在m+n的辅存上进行累加。最后为了更符合我们的乘法运算逻辑,可以讲数字逆序存储,这样数字的低位就在数组的低下标位置,进行累加时确定下标位置较容易些。

举个直观的例子:

大数据乘法/加法/减法_第1张图片

整个乘法过程就可以大致分成两部分:

第一部分对应位相乘再加上乘法进位MulFlag,结果是2位数(最大是9*9=81,则最加起来的最大值只有81+9),2位数中十位则是下一轮循环的乘法进位MulFlag,个位数是当前的一个临时中间结果,例如上图的6*4,当前MulFlag=0,则有24+0,十位的2是下一轮的MulFlag,4是当前中间结果。同样地,4*4+MulFlag(2)=18,十位的1是新的MulFlag,而8是当前的临时中间结果。

第二部分当前对应位结果+第一步的临时中间结果+AddFlag(加法进位),个位数为新的当前对应位结果,十位数为新的AddFlag.

例如:当前对应位(个位)结果是0,第一步的临时中间结果是4,AddFlag=0,则结果是4,个位数即为当前新的对应位结果,十位数为新的AddFlag=0。

具体的思路从代码中体会:

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

void Reverse(string& str,int len)
{
	for(int i=0,j=len-1;i<j;++i,--j)
		swap(str[i],str[j]);
}

string MulLarge(string str1,string str2)
{
	string strResult(str1.length()+str2.length(),'0'); // 构造一个str1.length+str2.length的字符串

	int len1 = str1.length();
	int len2 = str2.length();
	Reverse(str1,len1);
	Reverse(str2,len2);

	for( int i=0 ; i<len1 ;++i )
	{
		int MulFlag = 0 ;
		int AddFlag = 0 ;

		for( int j=0 ; j<len2 ; ++j )
		{
			int MulResult = (str1[i]-'0') * (str2[j]-'0') + MulFlag ; //乘积的结果,是一个两位数

			MulFlag = MulResult / 10 ;		//MulResult的十位数即为乘法进位
			MulResult = MulResult % 10 ;	 //着眼于个位数的运算(做加法)

			int AddResult = (strResult[i+j]-'0') + MulResult + AddFlag ; //AddFlag进位是前一次迭代进位上来的。

			strResult[i+j] = AddResult % 10 + '0'; //个位数即为当前结果
			AddFlag = AddResult / 10 ;		 //加法进位,为1或为0
		}
		strResult[i+len2] += MulFlag + AddFlag ; //注意是+=,不是=,最高位是乘法进位和加法进位加起来的结果
	}
	Reverse(strResult,len1+len2);
	return strResult;
}
void main()
{
	string str1="352";
	string str2="120";
	cout<<MulLarge(str1,str2)<<endl;
}
另外一种更好理解的代码:基于下图的方式。(输出的时候应该忽略开始的0,这里不处理这个细节问题)


#include<iostream>
#include<string>
using namespace std;
void reserve(string& str,int len)
{
	for(int i=0,j=len-1;i<j;++i,--j)
		swap(str[i],str[j]);
}
string Mul(string str1,string str2)
{

	int len1=str1.length();
	int len2=str2.length();
	reserve(str1,len1); //反转字符串,符合人类习惯
	reserve(str2,len2);

	string strResult(len1+len2,'0');//实例化一个len1+len2长度的字符串

	for(int i=0; i<len1; ++i)
	{
		for( int j=0 ; j<len2 ; ++j )
		{
			strResult[i+j] += (str1[i]-'0')*(str2[j]-'0') ; // 累加乘积的和,最后再求进位
		}
	}
	for( int j=0 ; j<len1+len2 ; ++j )
	{
		strResult[j+1] += (strResult[j]-'0') / 10 ;  //将十位上的数向前进位,并加上原来这个位上的数
		strResult[j] = (strResult[j]-'0') % 10 +'0' ;//将剩余的数存原来的位置上
	}
	reserve(strResult,len1+len2);
	return strResult;
}
void main()
{
	string str1="16",str2="16";
	cout<<Mul(str1,str2);
}

http://acm.hdu.edu.cn/showproblem.php?pid=1042

大数相乘求N!.

思路:模拟手算,一位一位往上进位.(一次AC)

#include<stdio.h>
#include<string.h>
int A[70000],numLen=1;
void Factorial(int N)
{
	int i=0, index=0, carry=0;
	memset(A,0,sizeof(A));
	A[0]=1;
	for(i=1;i<=N;++i)//从 1 到 N
	{
		carry=0;
		for(index=0;index<numLen;++index)//模拟手算,一位一位地向前进位
		{
			carry=A[index]*i+carry;
			A[index]=carry % 10 ;
			carry = carry / 10 ;
		}
		while(carry)//如果前边的进位>1,继续向前进位
		{
			A[index++]=carry % 10 ;
			carry = carry / 10 ;
		}
		if( index > numLen )//更新数据长度
			numLen = index ;
	}
}
int main()
{
	int n;
	while(scanf("%d",&n)!=EOF)
	{
		numLen=1;
		Factorial(n);
		for(int i=numLen-1;i>=0;--i)
			printf("%d",A[i]);
		printf("\n");
	}
	return 0;
}

大数加法

思路可以模拟手算,思路简单,但是,细节较多。可以有两种方向,一种是将相应位累加起来存放在result数组中,最后再统一进位,这种思路比较直观简便,同时需要注意中途细节不要出错,第二种方式可以是相应位相加再加上加法进位addflag.同样需要注意细节问题。

#include<iostream>
using namespace std;

void reserve(char* str)
{
	int len=strlen(str);
	for(int i=0,j=len-1;i<j;++i,--j)
		swap(str[i],str[j]);
}

char* add1(char* str1,char* str2)
{
	reserve(str1) ;
	reserve(str2) ;

	int len1=strlen(str1) ;
	int len2=strlen(str2) ;

	char* result=new char[max(len1,len2)+1] ;

	result[max(len1,len2)+1]='\0' ;

	int addflag=0 ;
	int i=0 ;
	//case1:在共公范围内
	for( ; i<len1 && i<len2 ; ++i)
	{
		result[i] = ((str1[i]-'0')+(str2[i]-'0') + addflag) % 10 + '0' ;
		addflag = ((str1[i]-'0')+(str2[i]-'0')) / 10 ;
	}
	//case2:不在公共范围内 ,3个for可以用一个for合并在一起,中间加上if控制语句
	for( ; i<len1 ;++i)
	{
		result[i] = (str1[i]-'0' + addflag) % 10 + '0' ;
		addflag = (str1[i]-'0') / 10 ;
	}
	for( ; i<len2 ;++i)
	{
		result[i] = (str2[i]-'0' + addflag) % 10 + '0' ;
		addflag = (str2[i]-'0') / 10 ;
	}
	result[i]='0' + addflag;
	reserve(result);
	return result[0] == '0' ? result+1 :result ;
}

char* add2(char* str1,char* str2)
{
	reserve(str1) ;
	reserve(str2) ;

	int len1=strlen(str1) ;
	int len2=strlen(str2) ;

	char* result=new char[max(len1,len2)+1] ;
	memset( result,'0',sizeof(char)*(max(len1,len2)+1) ) ;
	result[max(len1,len2)+1]='\0' ;

	for(int i=0 ; i<len1 || i<len2 ; ++i)
	{
		if(i< len1 && i<len2) //如果在公共范围内,求和并保存在result中
			result[i] = ((str1[i]-'0')+(str2[i]-'0')) ; //先将整型数存储在result中,下一步计算进位再变成字符型
		else if(i < len1) //如果不在公共范围内,将长的一段保存在result中
			result[i] = (str1[i]-'0');
		else
			result[i] = (str2[i]-'0');
	}
	//统一处理进位问题
	for(int i=0 ; i<len1 || i<len2 ;++i)//i<len1 || i<len2 等价于 i<max(len1,len2)
	{
		result[i+1] += result[i] / 10  ;
		result[i] = result[i] % 10 +'0';
	}

	reserve(result);
	return result[0] == '0' ? result+1 :result ;
}

void main()
{
	char str1[]="139" ;
	char str2[]="961" ;

	cout<<add1(str1,str2)<<endl;

	//cout<<add2(str1,str2)<<endl;
}

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

string Add(string fNum,string sNum)
{
	if( fNum.length() < sNum.length() ) 
		fNum.swap(sNum);
	string A = "0";
	A += fNum;
	for ( int i=1;i<=fNum.length() && i<=sNum.length();i++)
			A[A.length()-i]+=sNum[sNum.length()-i]-'0';
	for( int i=1;i<A.length();i++)
	{
		if(A[A.length()-i]>'9')
		{ 
			A[A.length()-i]-=10;
			A[A.length()-i-1]+=1;
		}
	}
	while(A[0]=='0') A.erase(0,1);
	return A;
}

string F[1001] = { "0", "1", "1" };
void setNum ()
{
	for ( int i = 3; i != 1001; ++ i )
	{
		F[i] = Add ( F[i-1] , F[i-2] ) ; 
	}  
}
int main ()
{
	int T;
	setNum ();
	
	while ( cin >> T )
	{
		while ( T -- )
		{
			int N;
			cin >> N;
			cout << F[N] << endl; 
		}
	}
	return 0; 
}

大数相加实训1:HDU1047

http://acm.hdu.edu.cn/showproblem.php?pid=1047

经过N次错误之后,终于搞定,用的是上面的模板.

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

void Add(string& Dst, string& Src)
{
	if( Dst.length() < Src.length() ) //令Dst的长度最长
		Dst.swap(Src);
	
	Dst = "0" + Dst; //前边补0,防止进位
	int i;
	//判断条件是取length最小
	for ( i=1; i<=Dst.length() && i<=Src.length(); ++i)
			Dst[Dst.length()-i]+=Src[Src.length()-i]-'0';
	for( i=1;i<Dst.length();i++)
	{
		if(Dst[Dst.length()-i]>'9')
		{ 
			Dst[Dst.length()-i]-=10;
			Dst[Dst.length()-i-1]+=1;
		}
	}
	while(Dst.length()!=1 && Dst[0]=='0') 
		Dst.erase(0,1);
}
int main()
{
	string dst,src;
	int T;
	cin>>T;
	while(T--)
	{
		dst="0";
		while(cin>>src && src != "0")
		{
			Add(dst,src);
		}
		if( T != 0 )
			cout<<dst<<endl<<endl;
		else
			cout<<dst<<endl;
	}
	return 0;
}

大数相加实训2:HDU1002

:http://acm.hdu.edu.cn/showproblem.php?pid=1002

这道题要求十分简单,实现两个大数相加,需要的是仔细,耐心

先给出几个测试用例

0001 1000
0 0
000 0000
9999 1
1 9999
99900 00999
00999 99900

发现上面的代码在处理000+0000是出问题的,这个好解决,重写一个print函数即可。处理完了print函数之后,我以为OK了,提交发现Runtime Error (ACCESS_VIOLATION),网上搜了下,这个错误一般是数组出现了问题,于是看了下数组部分,果然是数组开小了,改成max(len1,len2)+2之后,以后这下可以了。再提交,发现还是挂了,这次是Presentation Error,这个就完全蒙了,搞了半天才发现,原来两个solution之间要留下一个空行,这个真是无语了。下面是AC代码,从上面的代码改进而来。

#include<stdio.h>
#include<string.h>
int max(int a,int b)
{
	return a > b ? a : b;
}
void reserve(char* str)
{
	int len=strlen(str);
	for(int i=0,j=len-1;i<j;++i,--j)
	{
		char temp=str[i];str[i]=str[j];str[j]=temp;
	}
}
char* add2(char* str1,char* str2)
{
	reserve(str1) ;
	reserve(str2) ;

	int len1=strlen(str1) ;
	int len2=strlen(str2) ;

	char* result=new char[max(len1,len2)+1] ;
	memset( result,'0',sizeof(char)*(max(len1,len2)+1) ) ;
	result[max(len1,len2)+1]='\0' ;

	for(int i=0 ; i<len1 || i<len2 ; ++i)
	{
		if(i< len1 && i<len2) //如果在公共范围内,求和并保存在result中
			result[i] = ((str1[i]-'0')+(str2[i]-'0')) ; //先将整型数存储在result中,下一步计算进位再变成字符型
		else if(i < len1) //如果不在公共范围内,将长的一段保存在result中
			result[i] = (str1[i]-'0');
		else
			result[i] = (str2[i]-'0');
	}
	//统一处理进位问题
	for(int i=0 ; i<len1 || i<len2 ;++i)//i<len1 || i<len2 等价于 i<max(len1,len2)
	{
		result[i+1] += result[i] / 10  ;
		result[i] = result[i] % 10 +'0';
	}

	reserve(result);
	return result[0] == '0' ? result+1 :result ;
}
void printans(char ans[],int anslen) 
{ 
	int i;
    for( i=0;i<anslen && ans[i] == '0';++i)
		;
	printf("%s\n",ans+i);
} 
int main()
{
	char str1[1002];//="112233445566778899";
	char str2[1002];//="998877665544332211";
	int n;
	scanf("%d",&n);
	for(int i=1;i<=n;++i)
	{
		scanf("%s%s",str1,str2);
		printf("Case %d:\n%s + %s = ",i,str1,str2);
		char* result=add2(str1,str2);
		//printf("%s\n",);
		printans(result,strlen(result)-1);
		if( i != n)
			printf("\n");
	}
	return 0;
}

注意,上面的代码00+000=000,不符合人类的习惯,我们需要写一个print函数将0000格式化成0,这个比较好解决)

大数相加实训3:HDU1753

http://acm.hdu.edu.cn/showproblem.php?pid=1753

思路:题意十分简单明了,就是求两个小数之和.实际编程比较麻烦.分为几个频骤想.

1.将小数分割成小数部分和整数部分,两个小数就可以分成4个部分,然后分别求小数部分之和与整数部分之和.最好是先和小数部分,因为需要考虑进位问题.

2.小数部分的求和模拟手算,从小数点以后需要对齐,然后,模拟手算加法,进位.

3.整数部分求和参见上面的模板.然后,组合整数和小数部分即可.

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

void DecimalAdd(string& dst,string& src)
{
	string DecDst,DecSrc;
	int dot;
	if( dst.find('.') !=string::npos)
	{
		dot = dst.find('.');
		DecDst.assign(dst,dot+1,dst.size()-dot); //dst小数部分
		dst.erase(dot);							 //dst整数部分
	}
	if( src.find('.') != string::npos)
	{
		dot = src.find('.');
		DecSrc.assign(src,dot+1,src.size()-dot); //src小数部分
		src.erase(dot);							 //src整数部分
	}
	//小数部分相加
	if(DecDst.size() < DecSrc.size())
		DecDst.swap(DecSrc);
	DecDst = "0"+DecDst; 
	int i,sz=DecSrc.size();
	for(i=sz-1;i>=0;--i)
	{
		DecDst[i+1] += DecSrc[i]-'0';
		if( DecDst[i+1] > '9')
		{
			DecDst[i+1] -= 10;
			DecDst[i] +=1 ;
		}
	}
	if( DecDst[0]>'0' )//有进位,至多进1,因为9+9=18
	{
		dst[dst.size()-1] +=1;
	}
	//DecDst.erase(0,1); //删除进位,无论1,0
	DecDst[0]='.'; //用.覆盖高位的进位

	//整数部分相加
	if( dst.size() < src.size())
		dst.swap(src);		//dst.len >= src.size
	dst = "0"+dst;			//前边添0
	int srcsz=src.size(),dstsz=dst.size();
	for( i=0; i<srcsz; ++i)
		dst[dstsz-1-i] += src[srcsz-1-i]-'0';
	for( i=dstsz-1; i>=1; --i)
	{
		if( dst[i]>'9')
		{
			dst[i] -=10;
			dst[i-1] +=1;
		}
	}
	for( i=0; dst[i] == '0'; ++i )
		;
	dst.erase(0,i); //清除前端0
	for( i=DecDst.size()-1; i>=0 && DecDst[i]=='0'; --i )
		;
	DecDst.erase(i+1);//清除后端0
	if( dst.empty())
		dst="0";
	cout<<dst;
	if( DecDst != ".")
		cout<<DecDst;
	cout<<endl;
}
int main()
{
	string dst,src;
	while(cin>>dst>>src)
	{
		DecimalAdd(dst,src);
	}
	return 0;
}

测试用例:

1.1 2.9
1.1111111111 2.3444323343
1 1.1
1.00000000000003 .43
34345.34 32425345
8523400000 777.700
3435 4554
0.000 0.0000
99999 1
1.0001 2.9999
1.235262578623 2.29375824758243527200
23546756.345326547567454 .2142356754225653425346

输出
4
3.4555434454
2.1
1.43000000000003
32459690.34
8523400777.7
7989
0
100000
4
3.529020826205435272
23546756.5595622229900193425346

大数相加实训4:HDU1316

http://acm.hdu.edu.cn/showproblem.php?pid=1316

暴力打表,然后找出[a,b)的区间元素个数.

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

string Add(string dst,string src)
{
	if( dst.size() < src.size())
		dst.swap(src);
	string res="0"+dst;
	int i,rlen=res.size(),slen=src.size();
	for(i=0;i<slen;++i)
	{
		res[rlen-1-i] += src[slen-1-i]-'0';
	}
	for(i=1;i<rlen;++i)
	{
		if(res[rlen-i] > '9')
		{
			res[rlen-i] -= 10; //不要减9
			res[rlen-i-1] += 1;
		}
	}
	for(i=0; i<rlen && res[i]=='0'; ++i);
	res.erase(0,i);
	return res;
}

string result[500]; //保存fib数,打表
void Fibinaci(int n)
{
	result[0]="1",result[1]="2";
	int i;
	for(i=3;i<=n;++i)
	{
		result[i-1] = Add(result[i-2],result[i-3]);
	}
}

bool isSmaller(const string& str1,const string& str2)
{
	if( str1.size() == str2.size())
		return str1 < str2 ;
	else
		return str1.size() < str2.size();
}
int main()
{
	Fibinaci(500);
	string a,b;
	int beg,end;
	while(cin>>a>>b && !(a=="0" && b=="0"))
	{
		for(beg=0;isSmaller(result[beg],a);++beg)// result[beg] < a
			;
		// result[beg]>=a;
		for(end=beg; !isSmaller(b,result[end]);++end)// b >= result[end]
			;
		// result[end] > b;
		cout<<end-beg<<endl;
	}
	return 0;
}
/*
1 2 3 5 8 13 21 34 55
[2 , 6]
*/

大数据减法

同样模拟手算,和加法相逆,从低位开始,相减,如果不够减则向高位借位.
1.程序不处理前置0的情况,例如:03423 - 00042的情况.这种情况下,需要用户自己预处理去除前置的0.
2程序不处理前置+,-的情况,例如:+331 -(-3)这种情况需要用户自己转换成加法.
#include<iostream>
#include<string>
using namespace std;
bool isLess(const string& str1,const string& str2) // 小于
{
	if( str1.length() == str2.length() ) //如果位数相同,则相应位比较
		return str1 < str2 ;
	else								//如果位数不同,则位数多的数更大
		return str1.length() < str2.length();
}
string Sub(string Minuend ,string Subtrahend)
{
	if( Minuend == Subtrahend ) //if equal
		return "0";
	bool isNegative=false; //默认是正数
	if( isLess(Minuend , Subtrahend) )
	{
		isNegative=true;
		Minuend.swap(Subtrahend);
	}
	int i;
	for(i=0; i<Subtrahend.size(); ++i)
	{
		if( Minuend[Minuend.size()-1-i] >= Subtrahend[Subtrahend.size()-1-i] )//==号不能省略,否则出错
		{
			Minuend[Minuend.size()-1-i] -= (Subtrahend[Subtrahend.size()-1-i]-'0');
		}
		else
		{
			Minuend[Minuend.size()-1-i] =Minuend[Minuend.size()-1-i] +10 -
				(Subtrahend[Subtrahend.size()-1-i]-'0'); //模拟手算,从高位借位
			Minuend[Minuend.size()-2-i] -= 1;//不必担心越界,因为不会向Minuend的最高位借位
		}
	}
	while(Minuend[0]=='0')
		Minuend.erase(0,1);
	if( isNegative )
		Minuend = '-' + Minuend;
	return Minuend;
}
int main()
{
	char Minuend[]="134",Subtrahend[]="132";
	while(cin>>Minuend>>Subtrahend)
		cout<<Sub(Minuend,Subtrahend)<<endl;
	return 0;
}

大数相乘不需要reserve:

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

string mul( string str1, string str2 )
{
	int i,j,result,addcarry = 0,mulcarry = 0;
	int len1 = str1.size(),len2 = str2.size();
	string str(len1+len2,'0');

	for(i=len1-1; i>=0; --i)
	{
		addcarry = mulcarry = 0;
		for(j=len2-1; j>=0; --j)
		{
			result = (str1[i]-'0')*(str2[j]-'0') + mulcarry;
			mulcarry = result / 10;
			result %= 10;
			result += (str[i+j+1]-'0') + addcarry;
			addcarry = result / 10;
			result = result % 10;
			str[i+j+1] = result + '0';
		}
		str[i] += addcarry+mulcarry;
	}
	while(str[0] == '0')
		str.erase(0,1);
	return str;
}
int main()
{
	string str1 = "99",str2 = "99";
	cout<<mul(str1,str2)<<endl;
	return 0;
}

总结:往往看起来简单的东西做起来远没有想象中那么简单,就像上面的加法与乘法一样,平时觉得理所当然,但是,实现起来的细节还是有很多值得注意的地方,这种模拟手算的方式效率其实并不高,迟一些还需要写一篇用递归的方法求大数运算的博客(续)。


  

你可能感兴趣的:(大数据,面试题)