高级软件工程第一次作业:Personal Project
1. 完成一个大整数算法库
#ifndef BIGINT_H
#define BIGINT_H
#include<string>
#include<iostream>
using namespace std;
class CBigInt
{
public:
CBigInt();
CBigInt(const CBigInt& bigInt);
CBigInt(int num);
CBigInt(string num);
~CBigInt();
CBigInt operator+(const CBigInt& bigInt);
CBigInt operator-(const CBigInt& bigInt);
CBigInt operator*(const CBigInt& bigInt);
CBigInt operator/(const CBigInt& bigInt);
CBigInt operator%(const CBigInt& bigInt);
bool operator>(const CBigInt& bigInt)const;
bool operator<(const CBigInt& bigInt)const;
bool operator==(const CBigInt& bigInt)const;
CBigInt& operator=(const CBigInt& bigInt);
friend ostream& operator<<(ostream& out,const CBigInt& bigInt);
CBigInt intsqrt();
string getValue();
char getFlag();
void setValue(string v);
void setFlag(char f);
private:
string value;
char flag;
};
#endif
1.1 加法与减法
因为设计的大整数库是有符号的,所以减法可以用加法来做
加法的思路是先低位对齐,然后按位加,最后处理进位。思路还是比较简单的。
CBigInt CBigInt::operator+(const CBigInt& bigInt)
{
CBigInt result;
int carry=0;//进位/借位
CBigInt temp1=*this, temp2=bigInt;
if (flag == bigInt.flag)//同号
{
result.flag = flag;
reverse(temp1.value.begin(), temp1.value.end());
reverse(temp2.value.begin(), temp2.value.end());
int i = 0;
for (; i < temp1.value.length() && i < temp2.value.length(); i++)
{
char temAddNum = temp1.value[i] + temp2.value[i] - '0';//先逐位相加,先不处理进位
result.value.push_back(temAddNum);
}
//若两个操作数长度不同,则将那些多出来的位复制过来
for (; i < temp1.value.length(); i++)
{
result.value.push_back(temp1.value[i]);
}
for (; i < temp2.value.length(); i++)
{
result.value.push_back(temp2.value[i]);
}
//现在处理每一位的进位,若某位大于9,则进位
for (i = 0; i < result.value.length(); i++)
{
if (result.value[i] + carry > '9')
{
result.value[i] = result.value[i] - 10 + carry;
carry = 1;
}
else
{
result.value[i] = result.value[i] + carry;
carry = 0;
}
}
if (carry == 1)
{
result.value.push_back('1');
}
reverse(result.value.begin(), result.value.end());
return result;
}
else//异号
{//先判断结果的符号,再计算结果的绝对值
if (this->value.length() == bigInt.value.length())
{
if (this->value > bigInt.value)
{
result.flag = this->flag;
temp1 = *this, temp2 = bigInt;
}
else if (this->value < bigInt.value)
{
result.flag = bigInt.flag;
temp2 = *this, temp1 = bigInt;
}//绝对值不等,符号由绝对值大的决定
else
{
result.flag = 0;
result.value = "0";//绝对值相等,相加为0
return result;
}
}
else
{
if (this->value.length() > bigInt.value.length())
{
result.flag = this->flag;
temp1 = *this, temp2 = bigInt;
}
else
{
result.flag = bigInt.flag;
temp2 = *this, temp1 = bigInt;
}
}
reverse(temp1.value.begin(), temp1.value.end());
reverse(temp2.value.begin(), temp2.value.end());
int i = 0;
for (; i < temp1.value.length() && i < temp2.value.length(); i++)
{
char temAddNum = temp1.value[i] - temp2.value[i] + '0';//先逐位相减,先不处理借位
result.value.push_back(temAddNum);
}
//若两个操作数长度不同,则将那些多出来的位复制过来
for (; i < temp1.value.length(); i++)
{
result.value.push_back(temp1.value[i]);
}//已经确定temp1的长度不小于temp2的长度
//现在处理每一位的进位,若某位小于0,则借位
for (i = 0; i < result.value.length(); i++)
{
if (result.value[i] - carry < '0')
{
result.value[i] = result.value[i] + 10 - carry;
carry = 1;
}
else
{
result.value[i] = result.value[i] - carry;
carry = 0;
}
}
reverse(result.value.begin(), result.value.end());
//删除减法后高位出现的0
for (i = 0; i < result.value.length(); i++)
{
if (result.value[i] == '0')
{
result.value.erase(i, 1);
i--;
}
else
{
break;
}
}
return result;
}
}
减法就只需把右操作数的符号取反,然后调用加法即可。
1.2 乘法
因为是大整数,所以没有采用循环累加的办法,那样效率极低,我采用按位计算的方法。已知乘法结果的每一位都是由左右操作数的对应位相乘累加并考虑低位进位之后得到的。因此只需计算对应位的乘法,然后累加即可。
CBigInt CBigInt::operator*(const CBigInt& bigInt)
{
CBigInt result,temp1,temp2;
if (*this == CBigInt(0) || bigInt == CBigInt(0))
{
result.flag = 0;
result.value = "0";
return result;
}
if (flag == bigInt.flag)
{
result.flag = 0;
}
else
{
result.flag = 1;
}
temp1 = *this;
temp2 = bigInt;
reverse(temp1.value.begin(), temp1.value.end());
reverse(temp2.value.begin(), temp2.value.end());
result.value=string(temp1.value.length() + temp2.value.length(), '0');
for (int i = 0; i < temp1.value.length(); i++)
{
for (int j = 0; j < temp2.value.length(); j++)
{
int temAddNum = result.value[i + j] + (temp1.value[i] - '0')*(temp2.value[j] - '0')-'0';
int carry = temAddNum / 10;
result.value[i + j] = temAddNum % 10 + '0';//处理进位
int k;
for (k = i + j + 1; carry != 0 && k < result.value.length(); k++)
{
result.value[k - 1] = temAddNum % 10 + '0';
temAddNum = result.value[k] + carry - '0';
result.value[k] = temAddNum % 10 + '0';
carry = temAddNum / 10;
}
}
}
reverse(result.value.begin(), result.value.end());
//删除高位出现的0
for (int i = 0; i < result.value.length(); i++)
{
if (result.value[i] == '0')
{
result.value.erase(i, 1);
i--;
}
else
{
break;
}
}
return result;
}
1.3 除法和求余
除法采用移位-相减的策略,这样的操作最多进行abs(leftOperator.length()-rightOperator.length())次,可以很好的提高除法效率
CBigInt CBigInt::operator/(const CBigInt& bigInt)
{
CBigInt result = 0;
if (bigInt == CBigInt(0))
{
cout << "Error! Divisor is 0!" << endl;
return NULL;
}
CBigInt temp1 = *this, temp2 = bigInt;
temp1.setFlag(0);
temp2.setFlag(0);
if (temp1 < temp2)
{
result.setFlag(0);
//cout << result.getFlag();
return result;
}
int subCount = temp1.value.length() - temp2.value.length();
CBigInt tenMulti = 10;
do{
CBigInt temp = temp2;
CBigInt bigInt_multi = 1;
for (int i = 0; i < subCount; i++)
{
bigInt_multi = bigInt_multi*tenMulti;
}
temp = temp*bigInt_multi;//相当于移位
int count = 0;
while (!(temp1 < temp))
{
temp1 = temp1 - temp;
count++;
}
result = result*CBigInt(10) + CBigInt(count);
subCount--;
} while (subCount >= 0);
if (this->flag == bigInt.flag)
{
result.flag = 0;
}
else
{
result.flag = 1;
}
return result;
}
有了除法,求余操作就很简单了,只需减去能整除的部分,剩下的就是余数。
1.4 开方
开方采用的是分段试商算法,两位为一段
参考:http://blog.youkuaiyun.com/hondely/article/details/6939895
CBigInt CBigInt::intsqrt()
{
CBigInt result;
if (this->flag == 1)
{
cout << "Error!" << endl;
return NULL;
}
CBigInt temp = *this;
CBigInt num;
int loc;
if (temp.value == "0")
{
result = 0;
return result;
}
if (temp.value.length() % 2 == 1)
{
num = temp.value.at(0) - '0';
loc = -1;
}
else
{
num = (temp.value.at(0) - '0') * 10 + temp.value.at(1) - '0';
loc = 0;
}
CBigInt shang = 0;
while (true)
{
CBigInt i = 0;
while (!((i*(i + CBigInt(20) * shang)) > num))
{
i = i + 1;
}
i = i - 1;
num = num - CBigInt(i*(i + CBigInt(20) * shang));
shang = shang * 10 + i;
result.value.push_back(i.value[0]);
loc += 2;
if (loc >= temp.value.length())
{
break;
}
num = num * 100 + (temp.value[loc] - '0') * 10 + (temp.value[loc + 1] - '0');
}
return result;
}
1.5 >、<、==运算符的重载
这就是类似于字符串大小的比较。结合减法以后就很简单了。代码就不附了。
2. 利用筛法求质数
用筛法求质数的基本思想是:把从1开始的、某一范围内的正整数从小到大顺序排列, 1不是素数,首先把它筛掉。剩下的数中选择最小的数是素数,然后去掉它的倍数。依次类推,直到筛子为空时结束。
2.1 用int类型完成任务
要求的最大质数是第1,000,000个,根据质数的相关理论,因为17,000,000/ln(17,000,000)=1,021,099>1,000,000,所以,第1,000,000个质数应该小于17,000,000。可见用int类型是可以完成任务的。
筛法求质数的最近简单直接的思想在上面已经说了,我在实现时做了一些优化。优化的点包括,初始化时就将偶数的标志写为false,表示不是质数,这样在之后的判断中就只判断奇数,可以少判断一半的数,另外在筛出质数的倍数时,这个倍数从该质数开始递增,可以避免重复筛除。另外,每次加的是两倍的该质数,再次避免偶数的判断,可进一步提高速度。在最后的遍历计数时,也是跳过了所有偶数的判断,又可以提高一点速度。
#include<iostream>
#include<fstream>
#include<ctime>
#include<Windows.h>
#define RANGE 17000000//由有关质数的理论 17000000/ln(17000000)=1021099>1000000 所以,第1000000个质数应该小于17000000
#define N 1000000
using namespace std;
void Prime_in(int n)//筛法求质数
{
int count = 0;//统计当前是第几个质数
string path = "c:\\Users\\v-chixma\\Documents\\Visual Studio 2013\\Projects\\prime\\prime\\";
ofstream outfile1, outfile2;
outfile1.open(path + "all_the_prime_number.txt");//存储RANGE范围内的所有质数
outfile2.open(path + "the_homework_result.txt");//存储作业要求的结果,第10000,100000,1000000个质数
if (!outfile1 || !outfile2)
{
cout << "Open file failed!" << endl;
return;
}
bool* isPrimes = new bool[n + 1];
bool flag = true;
isPrimes[2] = true;
for (int i = 3; i < n + 1; i++)
{
isPrimes[i] = flag;
flag = !flag;
}//去掉偶数
for (int i = 3; i < n + 1; i+=2)//只判断奇数
{
if (isPrimes[i] == true)
{
if (i>5000)
{
continue;//防止溢出
}
for (unsigned int j = i*i; j < n + 1; j += 2*i)//从i*i比从i*2开始要快,而且避免重复删除,每次加2*i,可以跳过偶数
{
isPrimes[j] = false;
}
}
}
outfile1 << 2 << '\n';//先把2写入文件
count++;
for (int i = 3; i < n + 1; i+=2)//跳过所有偶数
{
if (isPrimes[i] == true)
{
//outfile1 << i << '\n';//将所有质数写入文件
count++;
if (count == N / 100 || count == N / 10 || count == N)
{
outfile2 << i << '\n';//将要求的结果写入文件
}
}
}
}
int check()
{//对比计算得到的质数与网上查到的质数表是否一致
int count = 0;
string path = "c:\\Users\\v-chixma\\Documents\\Visual Studio 2013\\Projects\\prime\\prime\\";
ifstream infile1, infile2;
infile1.open(path + "all_the_prime_number.txt");
infile2.open(path + "prime_number_list.txt");//网上查到的质数表
if (!infile1 || !infile2)
{
cout << "Open file failed!" << endl;
return 0;
}
int num1, num2;
while ((infile1 >> num1) && (infile2 >> num2))
{
if (num1 != num2)
{
cout << "Error!" << endl << num1 << " " << num2 << endl;
return 1;
}
}
infile1.close();
infile2.close();
cout << "All matched!" << endl;
return 0;
}
int main()
{
clock_t start, finish;
int num;
num = RANGE;
start = clock();
Prime_in(num);
finish = clock();
cout << double(finish - start) / CLOCKS_PER_SEC << " (s) " << endl;
check();
return 0;
}
2.2 大整数库,筛法求质数
算法思想与上面相同,代码如下:
#include<iostream>
#include<fstream>
#include<ctime>
#include<vector>
#include<iterator>
#include"BigInt.h"
#define N 1000000
using namespace std;
void Prime_in(CBigInt n)
{
CBigInt count = 0;//统计当前是第几个质数
string path = "c:\\Users\\v-chixma\\Documents\\Visual Studio 2013\\Projects\\Homework1\\Homework1\\";
ofstream outfile1, outfile2;
outfile1.open(path + "all_the_prime_number.txt");
outfile2.open(path + "the_homework_result.txt");
if (!outfile1 || !outfile2)
{
cout << "Open file failed!" << endl;
return;
}
vector<bool>isPrimes;
//bool* isPrimes = new bool[n + 1];
//bool flag = true;
isPrimes.push_back(false);//0
isPrimes.push_back(false);//1
isPrimes.push_back(true);//2
bool flag = true;
for (CBigInt i = 3; i < n; i = i + 1)
{
isPrimes.push_back(true);
flag = !flag;
}
vector<bool>::iterator iter;
iter = isPrimes.begin();
iter++; iter++; iter++;
outfile1 << "2" << '\n';
for (CBigInt i = 3; i < n; i = i + 2)
{
if (*iter == true)
{
//cout << i.getValue() << endl;
outfile1 << i.getValue() << '\n';
count = count + 1;
if (count == N / 100 || count == N / 10 || count == N)
{
outfile2 << i.getValue() << '\n';
}
vector<bool>::iterator temp_iter = iter;
for (CBigInt j = i*2; j < n; j = i + j)
{
for (CBigInt k = 0; k < i; k = k + 1)
{
temp_iter++;
}
*temp_iter = false;
//isPrimes[j] = false;
}
}
iter++; iter++;
}
}
int check()
{//对比计算得到的质数与查到的质数表是否一致
int count = 0;
string path = "c:\\Users\\v-chixma\\Documents\\Visual Studio 2013\\Projects\\Homework1\\Homework1\\";
ifstream infile1, infile2;
infile1.open(path + "all_the_prime_number.txt");
infile2.open(path + "prime_number_list.txt");
if (!infile1 || !infile2)
{
cout << "Open file failed!" << endl;
return 0;
}
int num1, num2;
while ((infile1 >> num1) && (infile2 >> num2))
{
if (num1 != num2)
{
cout << "Error!" << endl << num1 << " " << num2 << endl;
return 1;
}
}
infile1.close();
infile2.close();
cout << "All matched!" << endl;
return 0;
}
int main()
{
clock_t start, finish;
CBigInt num = 17000000;//10000;
start = clock();
Prime_in(num);
finish = clock();
cout << double(finish - start) / CLOCKS_PER_SEC << " (s) " << endl;
check();
return 0;
}
但是由于在使用大整数库时,向量的随机访问变得很困难,导致算法效率并不高。
3. 作业的结果
prime number:
10,000th : 104,729
100,000th:1,299,709
1,000,000th:15,485,863
代码地址:https://github.com/v-chixma/Advanced-software-engineering-course-