算法中常见的数学问题
在计算机世界里,我们通常还是要解决一些数学问题,这里对一些常见数学问题的算法进行总结,希望能够减慢遗忘的速度。
文章目录
最大公约数和最小公倍数
最大公约数
正整数 a 与 b 的最大公约数是指 a 与 b 的所有公约数中最大的那个公约数,一般用 gcd(a, b)
来表示 a 和 b 的最大公约数,而求解最大公约数常用欧几里得算法(辗转相除法)。
欧几里得算法基于下面这个定理:
设a、b 均为正整数,则 gcd(a, b) = gcd(a, a % b)
证明:设 a = kb + r, 其中 k 和 r 分别为 a 除以 b 得到的商和余数
则有 r = a - kb 成立
设 d 为 a 和 b 的一个公约数,
那么由 r = a - kb,得 d 也是 r 的一个约数
因此 d 是 b 和 r 的公约数
而 r = a % b,得 d 为 b 和 a % b 的一个公约数
因此 d 既是 a 和 b 的公约数,也是 b 和 a % b 的公约数
由于 d 的任意性,得 a 和 b 的公约数都是 b 和 a % b 的公约数
由 a = kb + r,同理可证 b 和 a % b 的公约数都是 a 和 b 的公约数
因此 a 和 b 的公约数与 b 和 a % b 的公约数全部相等,故其最大公约数也相等
即 gcd(a, b) = gcd(b, a % b)
如果 a < b ,结果就是将 a 和 b 交换;如果 a > b,那么通过这个定理可以很快的将数据规模变小。那么递归边界呢?众所周知,0 和任意一个整数 a 的最大公约数都是 a,这个结论可以当作递归边界。由此,递归的两个关键得到:
- 递归式:gcd(a, b) = gcd(b, a % b)
- 递归边界:gcd(a, 0) = a
于是可以得到下面的代码:
int gcd(int a, int b) {
if(b == 0) return a;
else return gcd(b, a % b);
}
最小公倍数
一般用 lcm(a, b) 来代表 a 和 b 的最小公倍数。
最小公倍数的求解在最大公约数的基础上进行。当得到 a 和 b 的最大公约数 d 后,可以马上得到最小公倍数是 ab / d,由于 **ab **在实际运算时可能溢出,所以更恰当的写法是 a/db。
a 和 b 的最大公约数即集合 a 与集合 b 的交集,而最小公倍数为 a 和 b 的并集,由于 ab 会使公因子部分多计算一次,故需要除掉一次公因子
分数的四则运算
分数的四则运算是指给定两个分数的分子和分母,求它们加减乘除的结果,下面先介绍如何表示和化简一个分数。
分数的表示和化简
分数的表示
对一根分数来说,最简洁的写法就是写成假分数的形式,即无论分子比分母大或者小,都保留其原数,因此可以使用结构体来保存:
struct Fraction{
int up, down;
};
三项规则:
- 使 down 为非负数,如果分数为负,那么令分子 up 为负即可
- 如果该分数恰好为 0,那么规定其分子为 0,分母为 1
- 分子和分母没有除了 1 以外的公约数
分数的化简
化简主要使用使 Fraction 变量满足分数表示的三项规定,因此化简步骤分为以下三步:
- 如果分母 down 为负数,令分子和分母都变成其相反数
- 如果分子 up 为 0,那么令分母为 1
- 约分:求出分子绝对值和分母绝对值的最大公约数 d,然后令分子分母同时除以 d
代码如下:
Fraction reduction(Fraction result) {
if(result.down < 0) {
// 分母为负数,令分子分母都变味相反数
result.up = -result.up;
result.down = -result.down;
}
if(result.up == 0) {
// 如果分子为 0,令分母为 1
result.down = 1;
}else {
int d = gcd(abs(result.up), abs(result.down)); // 分子分母的最大公约数
result.up /= d;
result.down /= d; // 约去最大公约数
}
return result;
}
分数的四则运算
分数的加减法都需要进行约分,所以需要约分出公式,然后针对公式进行计算即可。
// 加法
Fraction add(Fraction f1, Fraction f2) {
Fraction result;
result.up = f1.up * f2.down + f2.up * f1.down;
result.down = f1.down * f2.down