《算法(第4版)》理解取模 Java和Python区别
初识算法《算法(第4版)》
纯小白小白理解,记录学习过程
标题1 理解取模 Java和Python区别
- 其中有我自己的理解不对的可以自己查资料问AI,欢迎讨论。
- 算法就是根据事务的规律找到通用方法,比如数学公式,代入运算,提高你计算效率,成本更低,心算都要公式套用就行的, 算法本质上就是解决问题的一套方法描述,说直白一点就是我们常说的解题思路。如果你感觉看完后说出了妙啊(信心大增) 那么大概率你已经学到了皮毛,后面还会反复求证_
问题:9%6 , 6%9 ,-9%6 , 6%-9 ,-9%-6 Java,c,c++(他们属于同一个公式,商算发一样)和Python这些结果是什么?Java和Python结果会不会不一样?先给出答案:
Python 输出结果
9%6 → 3
6%9 → 6
-9%6 → 3
-6%9 → 3
9%-6 → -3
6%-9 → -3
Java输出结果
9%6 → 3
6%9 → 6
-9%6 → -3
-6%9 → -6
9%-6 → 3
6%-9 → 6
现在给出公式
% 是取模运算符(求余数)核心公式
对于整数 a % b:
即:a = b × q + r
被除数 = 除数 × 商 + 余数
a:被除数
b:除数
q:商(向负无穷方向取整,编程语言通用规则)
r:余数,也就是 a % b 的结果
规定 0 ≤ r < b(余数必须大于等于0且小于除数)。
注意 0 ≤ r < b是 欧几里得标准带余除法,但是Java和Python没有用这个只用了 |r|< |b| 这是个坑1 为什么不用?许多人会记混。
-
计算 9 % 6
a=9,b=6
先算商:9/6 = 1.5,向负无穷取整 就是向下取整q=1
代入公式求余数:
r = a - b * q
= 9 - 6 * 1
= 9 - 6
= 3
结果余数是3 -
计算 6 % 9
先算商:6/9 = 0.666...,向负无穷取整 就是向下取整q=0
代入公式求余数:
r = a - b * q
= 6 - 9 * 0
= 6 - 0
= 6
结果余数是6
负数情况:
3. 计算 -9 % 6
a=-9,b=6
先算商:-9/6 = -1.5,向负无穷取整 就是向下取整
注意 Python是向下取整q=-2
Java 是向0靠近取整q=-1
Python 代入公式求余数:
r = a - b * q
= -9 - 6 * -2
= -9 - -12
= 3
结果余数是3
Java 代入公式求余数:
r = a - b * q
= -9 - 6 * -1
= -9 - -6
= -3
结果余数是-3
- 计算 6 % -9
先算商:6/-9 = -0.666...,向负无穷取整 就是向下取整
注意 Python是向下取整q=-1
Java 是向0靠近取整q=0
代入公式求余数:
r = a - b * q
= 6 - -9 * 0
= 6 - 0
= 6
结果余数是6
好了这里就是第二个坑2Java和Python负数取的商值是不同的。
核心结论先讲清楚
Java 的 % 叫 取余(remainder)
规则:商向 0 截断,余数符号 = 被除数符号
Python 的 % 叫 取模(modulo)
规则:商向下取整 (floor),余数符号 = 除数符号
同样表达式正负混合时结果不同,是语言标准不同,不是 bug!
如果跨语言对接业务,不能直接两边写 a % b,必须统一一套算法。
问题Python项目和Java项目就不能统一取模吗?要求值要计算的一致。可以的有个公式方法原理就是使用
0 ≤ r < b 欧几里得标准带余除法 计算余数
方案 1:统一使用【欧几里得模】(推荐绝大多数业务)
Java代码示例
/**
* 统一得到非负余数(和业务标准欧几里得模对齐)
*/
public static int mod(int a, int b) {
int r = a % b;
return r < 0 ? r + Math.abs(b) : r;
}
Python代码示例
def mod(a, b):
r = a % b
abs_b = abs(b)
return r if r >= 0 else r + abs_b
方案 2:统一使用【Java 风格取余】(少数老系统兼容场景)
def java_remainder(a, b):
r = a - (a // b if (a * b >= 0) else (a // b + 1)) * b
return r
标题2 了解下 算法也有性能优化的
比如有这么一个问题:我们要求两个非负整数(18和12)的最大公约数(Greatest Common Divisor,简称GCD),你就得像下面这样大概描述一下怎么求解,注意,这里只能说是大概,因为没有考虑一些特殊情况:
- 最简单粗暴的,先看哪个小,然后看看最小的是不是他们的最大公约数,如果不是逐个向下找,也就是说先看12是不是他们的最大公约数,如果不是,那么11,10,9…一直找到6,所以结果是6。
- 如果数字稍微大一点的话,像上面那种做法会死人的,按照我们以前的做题方法,是从2,3这些数字先除,这些除数最终相乘就是结果,比如先让他们被2除,得到9和6,然后让他们再被3除,得到3和2,到此他们不能被除了1以后的其他数除了,这是我们把之前的除数2和3相乘,得到结果6。
- 假设两个非负整数p和q,若q为0,则最大公约数是p,否则将p除以q得到余数r,p和q的最大公约数就是q和r的最大公约数。
以上是文字语言的描述,我们下面用编程语言来描述。第二种算法总体而言会比第一种效率要慢一些,如果感兴趣可以自己实现,我们来看看第一种和第三种。第一种运行时间11743毫秒,第三种运行时间10798毫秒。也就是说,对于追求效率来讲,算法之间是存在优劣的。
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {
long start1 = System.currentTimeMillis();
for (int i = 0; i < 100000; i++){
gcd1(10240000,52000);
}
long end1 = System.currentTimeMillis();
System.out.println(end1 - start1);
long start2 = System.currentTimeMillis();
for (int i = 0; i < 100000; i++){
gcd3(10240000,52000);
}
long end2 = System.currentTimeMillis();
System.out.println((end2 - start2));
}
private static int gcd1(int num1, int num2){
int min = num1;
int max = num2;
if (num1 > num2){
min = num2;
max = num1;
}
if (min == 0){
return max;
}
int res = min;
for (int i = min; i > 0; i--){
if (num1 % i == 0 && num2 % i == 0){
res = i;
break;
}
}
return res;
}
private static int gcd3(int num1, int num2){
if (num2 == 0){
return num1;
}
int r = num1 % num2;
return gcd1(num2, r);
}
}
所以,学习算法的目的就是在解决问题的时候能找到那种效率最高的解题思路。但是,好的算法并不是谁都能设计出来的,大部分我们都是使用了现成的前人设计出来的算法。比如上面的求最大公约数的第三种算法,可能很多人都没听过,甚至即便现在看到了,也在怀疑这种算法到底正不正确。其实,这种算法是可以用数学证明的,详见欧几里得算法:从证明等式gcd(m, n) = gcd(n, m mod n)对每一对正整数m, n都成立说开去
注意一定要去验证一下 有可能这种加入负数会有问题,往往没经过验证就得不偿失了

浙公网安备 33010602011771号