算法笔记 模版之大数字

简介

好久没更新算法这个专栏了…水一篇…

相信不少同学刷算法题的时候都碰到过必须用大数才能处理的题型, 有的甚至用大数都不能简单地拿到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()

五种基本比较

重头戏开始了, 首先我们来梳理一下比较的逻辑:

  1. 比较符号位
  2. 比较数字长度
  3. 按位依次比较

所有整数的比较都适用上面的原则.

下面来梳理一下五种比较之间的关系:

比较运算符 等价运算
<
>
== !< && !>
<= !>
>= !<

由此来看, 我们只需要实现<和>两个比较, 就可以实现所有的五中基本比较.

小于运算 <

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, 原因如下:

  1. 虽然在这个函数内没有任何区别, 但是在实际应用中, 比如排序, 一旦返回了a<b返回了true, 就表示a和b需要换位, 即便a和b都是5, 这就引起了排序算法的不稳定, 更不提更复杂的应用中, 被排序的都是对象的指针, 引起的后果很严重.
  2. 一旦这里写了return true, 那么判断a==b的时候就不能够使用 return !(a<b) && !(a>b), 因为只有a<b和a>b都返回false, 才欧能判断a==b是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已经在同一个量级了, 这时候只要几步简单的减法就行.

具体操作如下:

  • 1234 * 10^4 = 12340000
  • 123456789 - 12340000 = 56789, 发现不够减12340000, 也不够减1234000, 123400
  • 56789 - 1234*10^1 = 44449, 够减12340
  • 44449 - 12340 = 32109
  • 32109 - 12340 = 19769
  • 19769 - 12340 = 7429, 不够减12340, 说明此时已经和1234在一个量级, 反复减就行了.

四种基本运算和赋值运算的结合

至此我们已经大概实现了正数大整数的加减乘余以及输出, 但是还有一些小细节没有实现

尽管我们已经重载了大整数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 <iostream>
#include <string>
#include <cstring>
#include <numeric>
#include <cmath>
#include <vector>
#include <algorithm>
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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章