好久没更新算法这个专栏了…水一篇…
相信不少同学刷算法题的时候都碰到过必须用大数才能处理的题型, 有的甚至用大数都不能简单地拿到AC。
这里我参考了一些资料, 将上面的大数模板进行了一些修改, 借助C++的重载, 实现了基于大数的一些处理。
目前的版本为1.0, 针对正大数有非常理想的效率和正确的结果, 但是处理负数的时候可能会碰到一些问题,会在后续更新中解决。
另外请各位在写demo的时候尽量不要挑一些容易异常的数据(比如100%0), 我几乎没有进行异常处理, 所以尽可能使用正常的数据, 这个版本目前对负数的处理是肯定存在不少问题的, 尽量使用正数
BigInteger(); // 默认构造函数
BigInteger(string str); // 将字符串str转换成一个大数字存储, 正数str省略'+', 负数str必须以'-'开头s
bint1 + bint2; // 两个大数字相加
bint1 - bint2; // 两个大数字相减, 结果可能为负
bint1 * bint2; // 两个大数字相乘
bint1 % bint2; // 大数bint1对bint2取余
cout << bint << endl; // 输出重载
toString(BigInteger); // 转字符串
/*
五中基本比较
*/
bint1 < bint2;
bint1 == bint2;
bint1 > bint2;
bint1 <= bint2;
bint1 >= bint2;
原理就简单提一下, 老生常谈的话题了, 用数组d来存储每一位数字, 但是本文采用的是逆序存储, 比如数字12345, 在数组d中存储的是54321. 这样做的好处之一是方便运算和进位. 用len存储数字长度, 不包括符号位. 最后用positive来存储是否为正数
class BigInteger {
private:
string __version__ = "1.0";
int d[MAX]; // 左对齐存储数字, d[0]存储大数字的最低位, d[MAX-1]存储最高位
int len;
bool positive; // 正负
public:
BigInteger(); // 无参构造
BigInteger(string str); // 有参构造, 返回字符串对应的大整数, 字符串第一位为符号位, +省略
int getLength(); // 整数长度, 不包括符号位
bool isPositive(); // 是否为正
string getVersion();
string toString(bool withFlag); // withFlag表示是否返回符号位
// 重载五种基本比较
bool operator < (const BigInteger& bint)const;
bool operator > (const BigInteger& bint)const;
bool operator == (const BigInteger& bint)const;
bool operator >= (const BigInteger& bint)const;
bool operator <= (const BigInteger& bint)const;
// 重载输出格式
friend ostream& operator<< (ostream& out, const BigInteger& bint);
// 大整数之间的运算
BigInteger operator+(BigInteger& bint);
BigInteger operator-(BigInteger& bint);
BigInteger operator*(BigInteger& bint);
BigInteger operator/(BigInteger& bint);
BigInteger operator%(BigInteger& bint);
void operator+=(BigInteger& bint);
void operator-=(BigInteger& bint);
void operator*=(BigInteger& bint);
void operator%=(BigInteger& bint);
};
这里没有太多需要解释的, 只是把所有的成员变量和函数列举出来. 毕竟如果所有功能都在类里面定义, 占据的篇幅也太大了.
唯一需要注意的是, 所有成员变量都是private私有的, 使用的时候只能调用public所属下的成员函数, 这是为了保证安全性, 避免被人为更改引起非法访问内存的问题.
另外再次强调, 这里的存储方式是逆序存储
无参构造会返回一个长度为0的大数字, 数组d全部置0, positive置true
BigInteger::BigInteger() {
memset(this->d, 0, sizeof(this->d));
this->positive = true;
this->len = 0;
}
思路: 输入一个字符串str, 按位将其保存到数组d中即可, 符号位单独讨论.
BigInteger::BigInteger(string str) { // 将str存到数组中
memset(this->d, 0, sizeof(this->d));
if ('-' == str[0]) {
this->len = str.size() - 1;
this->positive = false;
for (int i = 0; i < this->len; i++) { // 逆着赋值
this->d[i] = str[this->len - i] - '0'; // 右边将ascii码转换成对应的0~9整数
}
}else {
this->len = str.size();
this->positive = true;
for (int i = 0; i < this->len; i++) { // 逆着赋值
this->d[i] = str[this->len - i - 1] - '0';
}
}
}
毫无技术含量, 直接返回即可
int BigInteger::getLength() { return this->len; }
bool BigInteger::isPositive() { return this->positive; }
string BigInteger::getVersion() { return this->__version__; }
输出重载的目的是为了能够通过c++标准化的std::cout对象, 按照用户自定义的规则输出一个自定义对象.
通常情况下, 如果我们需要cout一个角色Character对象, 我们可能需要以下的代码
cout << "id:" << character.id << " ,name:" << character.name << endl;
可以解决问题, 但是如果这个对象需要输出的属性太多呢? 每次都这么写就很麻烦.
一种解决法案是定义函数printCharacter(Character chara):
void printCharacter(Character chara){
cout << "id:" << chara.id << " ,name:" << chara.name << endl;
}
虽然省力很多了, 但是这不符合面向对象的原则, 所幸C++提供了**重载(overload)**功能, 能够使同一个对象的同一个函数, 针对不同的输入参数有不同的效果. 但是必须严格遵循相应的格式! 甚至连一个const关键字都不能少
这里要强调, cin, cout都是对象, 不是函数!
言归正传, 一个大数的输出, 我们希望它能有符号位以及每一位数字就行了.
ostream& operator<< (ostream& out, const BigInteger& bint) {
if (0 == bint.len) { // 如果这个大数长度为0, 说明是未初始化的, 直接输出0即可
out << 0;
return out;
}else {
if (!bint.positive) { out << "-"; } // 负号不能省略
for (int i = bint.len - 1; i >= 0; i--) { out << bint.d[i]; } // 逆序存储, 故逆序输出
return out;
}
}
还是强调一下格式的问题, <<的输出重载一般通过友元函数来实现, 而且格式非常固定, 如下:
// 类内添加以下这句
friend ostream& operator<< (ostream& out, const 自定义类& 对象名);
// 类外通过以下函数来实现, 函数定义中一个字都不能少, 尤其是两个引用号&
ostream& operator<< (ostream& out, const 自定义类& 对象名) {
// 具体输出
return out;
}
和上一个输出非常相似, 就不赘述原理了, 接近一模一样的逻辑
string BigInteger::toString(bool withFlag) {
string str;
if (withFlag) {
if (this->isPositive()) {
str += "+";
}
else {
str += "-";
}
}
for (int i = this->len - 1; i >= 0; i--) str += to_string(this->d[i]);
return str;
}
本来想重载内置函数to_string()的, 结果差了资料发现to_string()的重载非常麻烦, 所以就自定义了一个函数. 日后更新重载的to_string()
重头戏开始了, 首先我们来梳理一下比较的逻辑:
所有整数的比较都适用上面的原则.
下面来梳理一下五种比较之间的关系:
比较运算符 | 等价运算 |
---|---|
< | 无 |
> | 无 |
== | !< && !> |
<= | !> |
>= | !< |
由此来看, 我们只需要实现<和>两个比较, 就可以实现所有的五中基本比较.
bool BigInteger::operator < (const BigInteger& bint)const {
if (this->positive && !bint.positive) return false; // this正, bint负
else if (!this->positive && bint.positive) { // this负, bint正, 需要讨论是否为0
int thisSum = accumulate(this->d, this->d + this->len, 0);
int bintSum = accumulate(bint.d, bint.d + bint.len, 0);
if (0 == thisSum && 0 == bintSum) return false;
return true;
}else if (this->len != bint.len)return this->positive ? this->len<bint.len : this->len> bint.len;
else {
for (int i = this->len - 1; i >= 0; i--)
if (this->d[i] != bint.d[i])
return this->positive ? this->d[i] < bint.d[i] : this->d[i] > bint.d[i];
return false; // 两者同号且同绝对值
}
}
唯一需要注意的是, 最后的return false处, 虽然能够运行到这一句说明被比较的两者是完全一样的数, 但是依然不能写成return true, 原因如下:
和小雨一样, 只是结果反一下
但是注意, 最后依然是return false! 原因同上
bool BigInteger::operator > (const BigInteger& bint)const {
if (this->positive && !bint.positive) return true; // this正, bint负
else if (!this->positive && bint.positive) return false; // this负, bint正
if (this->len != bint.len) // 两者同号
return this->positive ? this->len > bint.len : this->len < bint.len;
else {
for (int i = this->len - 1; i >= 0; i--) {
if (this->d[i] != bint.d[i])
return this->positive ? this->d[i] > bint.d[i] : this->d[i] < bint.d[i];
}
return false; // 两者同号且同绝对值
}
}
bool BigInteger::operator == (const BigInteger& bint)const {
return (!(this->operator<(bint))) && (!(this->operator>(bint)));
}
bool BigInteger::operator >= (const BigInteger& bint)const { return !(this->operator<(bint)); }
bool BigInteger::operator <= (const BigInteger& bint)const { return !(this->operator>(bint)); }
这里就体现出逆序存储的优越性了, 可以习惯性地从左到右处理数据, 而不用考虑由于最高位进位产生的数组移位的问题
按位相加即可, 记得进位
BigInteger BigInteger::operator+(BigInteger& bint) {
BigInteger result;
result.positive = this->positive;
int carry = 0; // 进位
for (int i = 0; i < this->len || i < bint.len; i++) {
int temp = this->d[i] + bint.d[i] + carry;
result.d[result.len++] = temp % 10;
carry = temp / 10;
}
if (carry != 0) result.d[result.len++] = carry;
return result;
}
BigInteger BigInteger::operator-(BigInteger& bint) {
if (*this == bint) return BigInteger("0");
if (*this > bint) { // 结果为正
BigInteger sub;
for (int i = 0; i < this->len || i < bint.len; i++) {
if (this->d[i] < bint.d[i]) { // 不够减, 借位
this->d[i + 1]--;
this->d[i] += 10;
}
sub.d[sub.len++] = this->d[i] - bint.d[i];
}
// 去除高位的0
while (sub.len - 1 >= 1 && sub.d[sub.len - 1] == 0) sub.len--;
return sub;
}else { // 结果为负
BigInteger sub;
sub = bint - (*this);
sub.positive = false;
return sub;
}
}
BigInteger BigInteger::operator*(BigInteger& bint) {
// 先计算绝对值
BigInteger product;
for (int j = 0; j < bint.len; j++) {
int b = bint.d[j];
for (int i = 0; i < this->len; i++) {
product.d[i + j] += b * this->d[i];
}
}
// 处理进位, 积的长度至少是两个数的长度之和再-1, 至多为两个数的长度之和.
product.len = this->len + bint.len - 1;
for (int i = 0; i < product.len; i++) {
int tmp = product.d[i];
if (i == product.len - 1 && tmp > 10) product.len++; // 最高位有进位, 长度+1
product.d[i] = tmp % 10;
product.d[i + 1] += tmp / 10;
}
if (this->positive ^ bint.positive) product.positive = false; // 最后处理符号
return product;
}
BigInteger BigInteger::operator%(BigInteger& bint) {
if (*(this) < bint) return *(this);
BigInteger a = *(this);
int tmp = a.len - bint.len - 1;
if (tmp <= 1) tmp = 1;
for (int i = tmp; i >= 1; i--) {
BigInteger b = BigInteger(to_string(int(pow(10, i))));
b *= bint;
while (a >= b) {
a -= b;
}
}
while (a >= bint) {
a -= bint;
}
return a;
}
这里说一下思路, 计算bint1 % bint2的时候, 由于两者的数量级可能相差非常大, 比如bint1 = 123456789, bint2 = 1234, 我采用的算法为先计算两者的长度之差-1, 这里即9-4-1 = 4, 说明bint1至少比bint2大4个量级, 那么我们就把这4个量级一层层去减, 直到不能减为止, 说明这时候bint1和bint2已经在同一个量级了, 这时候只要几步简单的减法就行.
具体操作如下:
至此我们已经大概实现了正数大整数的加减乘余以及输出, 但是还有一些小细节没有实现
尽管我们已经重载了大整数BigInteger的+, -, 5, *, 但是此时我们输入 bint1 += bint2依然会报错, 因为我们还没有重载+=这个运算符, 同样的++和–也没有重载.
void BigInteger::operator+=(BigInteger& bint) { *(this) = *(this) + bint; }
void BigInteger::operator-=(BigInteger& bint) { *(this) = *(this) - bint; }
void BigInteger::operator*=(BigInteger& bint) { *(this) = *(this) * bint; }
void BigInteger::operator%=(BigInteger& bint) { *(this) = *(this) % bint; }
#include
#include
#include
#include
#include
#include
#include
using namespace std;
const int MAX = 20000;
// 大整数的加减乘除
/*
注意: 负数的处理可能有一些问题, 但是正数的处理都是对的.
*/
class BigInteger {
private:
string __version__ = "1.0";
int d[MAX]; // 左对齐存储数字, d[0]存储大数字的最低位, d[MAX-1]存储最高位
int len;
bool positive; // 正负
public:
BigInteger(); // 无参构造
BigInteger(string str); // 有参构造, 返回字符串对应的大整数, 字符串第一位为符号位, +省略
int getLength(); // 整数长度, 不包括符号位
bool isPositive(); // 是否为正
string getVersion();
string toString(bool withFlag);
// 重载五种基本比较
bool operator < (const BigInteger& bint)const;
bool operator > (const BigInteger& bint)const;
bool operator == (const BigInteger& bint)const;
bool operator >= (const BigInteger& bint)const;
bool operator <= (const BigInteger& bint)const;
// 重载输出格式
friend ostream& operator<< (ostream& out, const BigInteger& bint);
// 大整数之间的运算
BigInteger operator+(BigInteger& bint);
BigInteger operator-(BigInteger& bint);
BigInteger operator*(BigInteger& bint);
BigInteger operator/(BigInteger& bint);
BigInteger operator%(BigInteger& bint);
void operator+=(BigInteger& bint);
void operator-=(BigInteger& bint);
void operator*=(BigInteger& bint);
void operator%=(BigInteger& bint);
};
BigInteger::BigInteger() {
memset(this->d, 0, sizeof(this->d));
this->positive = true;
this->len = 0;
}
BigInteger::BigInteger(string str) { // 将str存到数组中
memset(this->d, 0, sizeof(this->d));
if ('-' == str[0]) {
this->len = str.size() - 1;
this->positive = false;
for (int i = 0; i < this->len; i++) { // 逆着赋值
this->d[i] = str[this->len - i] - '0';
}
}
else {
this->len = str.size();
this->positive = true;
for (int i = 0; i < this->len; i++) { // 逆着赋值
this->d[i] = str[this->len - i - 1] - '0';
}
}
}
int BigInteger::getLength() { return this->len; }
bool BigInteger::isPositive() { return this->positive; }
string BigInteger::getVersion() { return this->__version__; }
ostream& operator<< (ostream& out, const BigInteger& bint) {
if (0 == bint.len) {
out << 0;
return out;
}else {
if (!bint.positive) { out << "-"; }
for (int i = bint.len - 1; i >= 0; i--) { out << bint.d[i]; }
return out;
}
}
string BigInteger::toString(bool withFlag) {
string str;
if (withFlag) {
if (this->isPositive()) {
str += "+";
}
else {
str += "-";
}
}
for (int i = this->len - 1; i >= 0; i--) str += to_string(this->d[i]);
return str;
}
bool BigInteger::operator < (const BigInteger& bint)const {
if (this->positive && !bint.positive) return false; // this正, bint负
else if (!this->positive && bint.positive) { // this负, bint正, 需要讨论是否为0
int thisSum = accumulate(this->d, this->d + this->len, 0);
int bintSum = accumulate(bint.d, bint.d + bint.len, 0);
if (0 == thisSum && 0 == bintSum) return false;
return true;
}else if (this->len != bint.len)return this->positive ? this->len<bint.len : this->len> bint.len;
else {
for (int i = this->len - 1; i >= 0; i--)
if (this->d[i] != bint.d[i])
return this->positive ? this->d[i] < bint.d[i] : this->d[i] > bint.d[i];
return false; // 两者同号且同绝对值
}
}
bool BigInteger::operator > (const BigInteger& bint)const {
if (this->positive && !bint.positive) return true; // this正, bint负
else if (!this->positive && bint.positive) return false; // this负, bint正
if (this->len != bint.len) // 两者同号
return this->positive ? this->len > bint.len : this->len < bint.len;
else {
for (int i = this->len - 1; i >= 0; i--) {
if (this->d[i] != bint.d[i])
return this->positive ? this->d[i] > bint.d[i] : this->d[i] < bint.d[i];
}
return false; // 两者同号且同绝对值
}
}
bool BigInteger::operator == (const BigInteger& bint)const { return (!(this->operator<(bint))) && (!(this->operator>(bint))); }
bool BigInteger::operator >= (const BigInteger& bint)const { return !(this->operator<(bint)); }
bool BigInteger::operator <= (const BigInteger& bint)const { return !(this->operator>(bint)); }
BigInteger BigInteger::operator+(BigInteger& bint) {
BigInteger result;
result.positive = this->positive;
int carray = 0; // 进位
for (int i = 0; i < this->len || i < bint.len; i++) {
int temp = this->d[i] + bint.d[i] + carray;
result.d[result.len++] = temp % 10;
carray = temp / 10;
}
if (carray != 0) result.d[result.len++] = carray;
return result;
}
BigInteger BigInteger::operator-(BigInteger& bint) {
if (*this == bint) return BigInteger("0");
if (*this > bint) { // 结果为正
BigInteger sub;
for (int i = 0; i < this->len || i < bint.len; i++) {
if (this->d[i] < bint.d[i]) { // 不够减
this->d[i + 1]--;
this->d[i] += 10;
}
sub.d[sub.len++] = this->d[i] - bint.d[i];
}
while (sub.len - 1 >= 1 && sub.d[sub.len - 1] == 0) sub.len--;// 去除高位的0,
return sub;
}
else { // 结果为负
BigInteger sub;
sub = bint - (*this);
sub.positive = false;
return sub;
}
}
BigInteger BigInteger::operator*(BigInteger& bint) {
// 先计算绝对值
BigInteger product;
for (int j = 0; j < bint.len; j++) {
int b = bint.d[j];
for (int i = 0; i < this->len; i++) {
product.d[i + j] += b * this->d[i];
}
}
// 处理进位, 积的长度至少是两个数的长度之和-1, 至多为两个数的长度之和.
product.len = this->len + bint.len - 1;
for (int i = 0; i < product.len; i++) {
int tmp = product.d[i];
if (i == product.len - 1 && tmp > 10) product.len++; // 最高位有进位, 长度+1
product.d[i] = tmp % 10;
product.d[i + 1] += tmp / 10;
}
if (this->positive ^ bint.positive) product.positive = false; // 最后处理符号
return product;
}
BigInteger BigInteger::operator%(BigInteger& bint) {
if (*(this) < bint) return *(this);
BigInteger a = *(this);
int tmp = a.len - bint.len - 1;
if (tmp <= 1) tmp = 1;
for (int i = tmp; i >= 1; i--) {
BigInteger b = BigInteger(to_string(int(pow(10, i))));
b *= bint;
while (a >= b) {
a -= b;
}
}
while (a >= bint) {
a -= bint;
}
return a;
}
void BigInteger::operator+=(BigInteger& bint) { *(this) = *(this) + bint; }
void BigInteger::operator-=(BigInteger& bint) { *(this) = *(this) - bint; }
void BigInteger::operator*=(BigInteger& bint) { *(this) = *(this) * bint; }
void BigInteger::operator%=(BigInteger& bint) { *(this) = *(this) % bint; }
int main() {
string str1 = "2354312514613614";
string str2 = "5623424";
BigInteger bint1 = BigInteger(str1);
BigInteger bint2 = BigInteger(str2);
cout << bint1 << endl;
cout << bint2 << endl;
cout << bint1.toString(true) << endl;
cout << bint2.toString(false) << endl;
cout << (bint1 < bint2) << endl;
cout << (bint1 <= bint2) << endl;
cout << (bint1 == bint2) << endl;
cout << (bint1 > bint2) << endl;
cout << (bint1 >= bint2) << endl;
cout << (bint1 + bint2) << endl;
cout << (bint1 - bint2) << endl;
cout << (bint1 * bint2) << endl;
cout << (bint1 % bint2) << endl;
return 0;
}