算法筆記 模版之大數字

簡介

好久沒更新算法這個專欄了…水一篇…

相信不少同學刷算法題的時候都碰到過必須用大數才能處理的題型, 有的甚至用大數都不能簡單地拿到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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章