Stanford Algorithms(一): 大数相乘(c++版)
刚不就在中国大学Mooc上参加了陈越老师的数据结构
的课程,收获很大.觉得趁热打铁,也把算法的部分也给一块学了吧,就在Coursera上注册了一个斯坦福大学的算法课,课程的量很重,估计要学一个学期吧,慢慢的学,稳扎稳打.
课程里推荐了很多书,我找了一本, 书名就叫Algorithms
,作者是S.Dasgupta教授,简单翻看了一下,觉得写的挺不错,就姑且把这本书当做教材了.
还是那句话,贵精不贵多,一门学深入了,收获就会很大,总之:
不要做一个浮躁的人.
第一节课就出了一个很有意思的题:两个数相乘.
也许会有人问,这有什么难,程序里直接就能算,但是题目出的是两个64个数字相乘,这就有意思了.
这个计算两个数字相乘的算法,视频里介绍过了,就是分治的思想,递归的调用,把原本的O(n^2)的问题变成了O(logn),效率无以提升很多.
我到网上一查,发现这是个很出名的,叫做大数相乘的算法问题,参考了一些文章,但感觉写的都不是很详细.
我是用C++实现的.先看看思路吧:
1.数字太多,int肯定不行,要用string
2.具体的算法已经有了,实现的困难,在于实现两个string数字之间的加,减,以及乘法.
要用的函数大概是这些:
string multiply(string x, string y);
string simplyMultiply(string x, string y);
int string2int(string x);
string int2string(int x);
string add(string x, string y);
string Minus(string x, string y);
string addZero(string x, int zeroNum);
string addPreZero(string x, int zeroNum);
string reverseString(string s);
int Max(int x, int y);
其中有三个函数比较简单:
int Max(int x, int y){
/*
* Description: find max number
* Input: Two integers
* Output: Return max between x and y
*/
return x > y ? x : y;
}
int string2int(string x){
/*
* Description: Change string to int
* Input: A string
* Output: Return a integer represents origin string
*/
int n = x.length();
int s = 0;
for(int i = 0; i < n; ++i){
s = 10 * s + x[i] - '0';
}
return s;
}
string int2string(int x){
/*
* Description: Change int to string
* Input: An integers
* Output: Return a string represents origin integers
*/
string result;
stringstream stream;
stream << x;
stream >> result;
return result;
}
这里借助了stringstream,可以轻松实现类型之间的转换,当然是涉及到string的.
两个string的加和减,考虑平时手算的方式,是尾对齐的,因此用程序实现的话,先把它们倒转,变成头对齐,就方便计算了.
string simplyMultiply(string x, string y){
/*
* Description: multiply two string, whose length = 1
* Input: Two string
* Output: Return product
*/
if(x.empty() | y.empty()){
return int2string(0);
}else{
int result = string2int(x) * string2int(y);
return int2string(result);
}
}
string reverseString(string s){
/*
* Description: Reverse the string
* Input: A string
* Output: Return a reversed string
*/
string result;
for(auto temp = s.end() - 1; temp >= s.begin(); --temp){
result.push_back(*temp);
}
return result;
}
还有两个额外的操作,就是在string前面和后面添加0,在前面添加0是为了让两个string的位数相等,因为这个算法处理的是两个等长string,因此要补位,不然会出问题;后面加0,是要用到与10^n相乘这种情况.
string addZero(string x, int zeroNum){
/*
* Description: Add zero between a string, simulate x * 10^n
* Input: A string, a integer represents zero's number after it
* Output: Return a string, which is added n's 0
*/
string temp(zeroNum, '0');
x.append(temp);
return x;
}
string addPreZero(string x, int zeroNum){
/*
* Description: Add zero before a string to fill in empty place
* Input: A string, a integer represents zero's number
* Output: Return a string, which is added n's 0 before it
*/
string temp(zeroNum, '0');
temp.append(x);
return temp;
}
比较精彩的是模拟两个string加减的操作.有了前面几个方法做铺垫,实现起来就不困难了.其中,
Add操作模仿的是到10进1
Minus操作模仿的是减时不够高位来补
细节一定要注意,否则bug很难看出来.
string add(string x, string y){
/*
* Description: Add two string
* Input: Two strings
* Output: Return their sum
*/
int i, more = 0, tempSum = 0;
x = reverseString(x);
y = reverseString(y);
int maxSize = Max(x.size(), y.size());
string s(maxSize + 1, '0');
for(i = 0; i < x.size() && i < y.size(); ++i){
tempSum = x[i] - '0' + y[i] - '0' + more;
s[i] = tempSum % 10 + '0';
more = tempSum / 10;
}
if(i != y.size()){
for(; i < y.size(); ++i){
tempSum = y[i] - '0' + more;
s[i] = tempSum % 10 + '0';
more = tempSum / 10;
}
}else if(i != x.size()){
for(; i < x.size(); ++i){
tempSum = x[i] - '0' + more;
s[i] = tempSum % 10 + '0';
more = tempSum / 10;
}
}
if(more != 0){
s[i] += more;
}else{
s.pop_back();
}
s = reverseString(s);
return s;
}
string Minus(string x, string y){
/*
* Description: Minus between strings
* Input: Two strings
* Output: Return their difference
*/
int i;
x = reverseString(x);
y = reverseString(y);
string s(x.size(), '0');
for(i = 0; i < y.size(); ++i){
if(x[i] < y[i]){
x[i] += 10;
x[i + 1] -= 1;
}
s[i] = x[i] - y[i] + '0';
}
for(; i < x.size(); ++i){
s[i] = x[i];
}
for(i = x.size() - 1; i > 0; --i){
if(s[i] == '0'){
s.pop_back();
}else{
break;
}
}
s = reverseString(s);
return s;
}
有了前面的这些,multi()写起来就很简单了,这里要注意的是数字位数为奇数时的处理.
string multiply(string x, string y){
/*Description: Multiply between two strings
*Input: Two strings, represents two positive integers
*Output: Return product of x and y
*/
int xSize = x.length();
int ySize = y.length();
int n = Max(xSize, ySize);
if(n == xSize){
y = addPreZero(y, n - ySize);
}else{
x = addPreZero(x, n - xSize);
}
if(n == 1){
return simplyMultiply(x, y);
}
string xLeft = x.substr(0, n / 2);
string xRight = x.substr(n / 2);
string yLeft = y.substr(0, n / 2);
string yRight = y.substr(n / 2);
string p1 = multiply(xLeft, yLeft);
string p2 = multiply(xRight, yRight);
string p3 = multiply(add(xLeft, xRight), add(yLeft, yRight));
string p4 = Minus(Minus(p3, p1), p2);
string result = add(add(addZero(p1, 2 * (n - n / 2)),
addZero(p4, n - n / 2)), p2);
return result;
}
现在,可以尽情的相乘了,两个64位数也可以.
代码在这里:大数相乘