CC++ & Algorithm

模运算与取余

中等0
语言版本:C++
概述:取余就是求除法后剩下的零头,像分糖果后手里还剩几颗。

模运算与取余:分糖果、算星期几,样样在行

你有没有遇到过这种情况:分糖果时,10颗糖分给3个小朋友,每人得3颗,还剩1颗——这个“还剩1颗”就是取余。在编程里,取余运算用百分号 % 表示,它能帮我们轻松处理“循环”“周期”“分组”之类的问题。比如7除以3得2余1,7 % 3 的结果就是1。今天我们就来聊聊取余的各种奇妙用法。

什么是取余?先来几个生活中的例子

取余其实就是求“除法剩下的零头”。下面几个例子让你秒懂:

  • 你有11块零花钱,每天花3块,能花3天,还剩2块。 → 11 % 3 = 2
  • 全班42人,每排坐8人,能坐满5排,还剩2人。 → 42 % 8 = 2
  • 现在是下午3点,过10小时是几点?(3 + 10) % 12 = 1,所以是凌晨1点。

这些例子背后全是取余运算。在Python里,写起来超级简单:

candy = 10        # 糖果数量
friends = 3       # 小朋友人数
remaining = candy % friends   # 剩下的糖果数
print("还剩", remaining, "颗")   # 输出:还剩 1 颗

模运算的三大妙用

1. 判断奇偶和倍数

判断一个数是不是偶数,就看它除以2的余数是不是0:n % 2 == 0。判断是不是3的倍数,就看 n % 3 == 0。反过来,如果 n % 2 == 1 就是奇数。

num = 17                 # 一个整数
if num % 2 == 0:        # 如果余数为0
    print(num, "是偶数")
else:
    print(num, "是奇数")  # 输出:17 是奇数

2. 让编号在固定范围内循环

就像星期几一样,数字总是在06之间循环。假如我们要给100个同学编号,每轮报数17,可以用取余把编号映射到1~7:

student_num = 55         # 第55个同学
group = student_num % 7 + 1  # 加1让结果变成1~7,而不是0~6
print("第", student_num, "个同学属于第", group, "组")

3. 模拟轮盘、环形跑道、游戏中的循环

比如一个圆形跑道上有4个标记点(编号0、1、2、3),小明从0出发,往前跑5步,位置就是 (0 + 5) % 4 = 1。取余让位置永远不超出范围。

position = 0            # 初始位置
steps = 5               # 向前走5步
new_pos = (position + steps) % 4   # 取余保证在0~3之间
print("新位置是", new_pos)   # 输出:新位置是 1

模运算的两个重要性质(防止大数溢出)

当数字很大时,直接计算可能会让计算机内存爆掉(比如算123456的平方再取模),但我们可以利用下面两个规律,先取模再运算,结果一模一样:

  • (a + b) % m = (a % m + b % m) % m
  • (a × b) % m = (a % m × b % m) % m

这两个性质非常有用,尤其在解数学题或编程竞赛时。比如计算 123456 × 123456 % 100,如果先算123456²,结果有十亿多,但你可以:

a = 123456               # 原始数
mod = 100                # 模数
a_mod = a % mod          # 先取余:56
result = (a_mod * a_mod) % mod   # 56²=3136,再取余得36
print(result)            # 输出:36

这样每一步的数字都很小,不会溢出。

注意:Python对负数取余的特别之处

很多刚学的同学会搞混负数取余的结果。在Python中,% 的结果总是与除数同号(即结果非负)。比如:

print(-7 % 3)   # 输出:2   (-7除以3得-3,余2,因为-3×3+2=-7)
print(7 % -3)   # 输出:-2  (7除以-3得-3,余-2,因为-3×(-3)+(-2)=7)

简单记:如果除数是正数,结果一定在0到除数减1之间。所以 -7 % 3 的结果是2,而不是-1。这一点和其他语言(如C语言)不同,要注意哦!

新手最容易犯的三个错误

  1. 混淆除法(/)和取余(%7 / 3 得到 2.333...,而 7 % 3 得到 1。一个是商(带小数),一个是余数。
  2. 忘记取余运算的优先级% 的优先级和 */ 一样,比加减高。比如 1 + 2 % 3 会先算 2 % 3 得2,再加1得3,而不是你想的 (1+2)%3=0。如果希望先加,记得加括号。
  3. 以为负数取余的结果和数学上一样:数学上“余数”一般是非负的,但编程中可能不同。Python的行为对正除数友好,但遇到负数除数时结果会是负数,容易出错。建议尽量只用正数做模数。

完整示例:模拟“分糖果”和“循环报数”

下面是一个完整的小程序,先模拟分糖果,再模拟循环报数:

# 模拟分糖果
total_candy = 50        # 总糖果数
children = 7            # 小朋友人数
each_get = total_candy // children   # 每人分得的整数颗(整除)
remain = total_candy % children      # 剩下的糖果
print("每人分", each_get, "颗,还剩", remain, "颗")

# 模拟循环报数(1到3循环)
team_size = 3           # 每队人数
for i in range(1, 11): # 报1到10号
    team_num = (i - 1) % team_size + 1  # 从1开始编号
    print("第", i, "号同学属于第", team_num, "队")

运行结果:

每人分 7 颗,还剩 1 颗
第 1 号同学属于第 1 队
第 2 号同学属于第 2 队
第 3 号同学属于第 3 队
第 4 号同学属于第 1 队
...
第 10 号同学属于第 1 队

相关指引

掌握模运算,就像拥有了一把处理周期、循环和余数的瑞士军刀。以后遇到“每隔几个”“还剩几个”“在N以内”的问题,第一个想到的就是它!

例题精讲

1单选题

在Python中,表达式 7 % 3 的结果是多少?

A1
B2
C3
D0
2判断题

在Python中,表达式 -7 % 3 的结果是 -1。

3填空题
判断一个整数 n 是否为偶数,可以使用 if ___ : print("偶数")。
4单选题

下列哪个表达式的结果为 0?

A5 % 5
B5 % 3
C3 % 5
D1 % 5
5填空题
用辗转相除法求两个整数的最大公约数,补全代码:\ndef gcd(a, b):\n    while b != 0:\n        a, b = b, ___\n    return a