Giữ ƯCLN khi thay cặp số
Cho hai số nguyên không âm a, b, không đồng thời bằng 0. Nếu b=0 thì ƯCLN(a,0)=a. Nếu b>0, đặt r=a%b; ta có a=q×b+r. Nếu d là ước chung của a và b thì d cũng là ước của r; ngược lại, nếu d là ước chung của b và r thì d cũng là ước của a. Vì vậy hai cặp có cùng các ước chung:
$$\operatorname{gcd}(a,b)=\operatorname{gcd}(b,a\bmod b).$$
Số dư luôn thỏa 0≤r<b, nên đối số thứ hai giảm tới 0.
Kiểm b bằng 0 trước khi lấy dư
```python
def gcd(a, b):
if b == 0:
return a
return gcd(b, a % b)
```
Cơ sở được đặt trước phép a%b để không chia cho 0. Với a=0, b>0, lời gọi tiếp là gcd(b,0) và trả b. Cặp (0,0) bị loại bởi điều kiện của bài toán, dù đoạn mã riêng lẻ vẫn có thể trả 0 khi bị gọi với cặp đó.
Theo dõi một cặp cụ thể
Với a=84, b=30:
| Lời gọi | Số dư | Tiếp theo |
|---|---|---|
| gcd(84,30) | 24 | gcd(30,24) |
| gcd(30,24) | 6 | gcd(24,6) |
| gcd(24,6) | 0 | gcd(6,0) |
| gcd(6,0) | Không tính | Trả 6 |
Các lời gọi đang chờ đều trả lại cùng kết quả 6. Không nhầm số dư 0 ở bước cuối với giá trị ƯCLN.
Cùng quy tắc có thể viết bằng vòng lặp
Bản lặp giữ cùng hai số a,b. Cần lưu số dư trước khi đổi a:
```python
def gcd_lap(a, b):
while b != 0:
r = a % b
a = b
b = r
return a
```
Một cách khác là trừ số nhỏ khỏi số lớn cho tới khi hai số bằng nhau. **Bản trừ dưới đây yêu cầu a,b đều dương**:
```python
def gcd_tru(a, b):
if a == b:
return a
if a > b:
return gcd_tru(a - b, b)
return gcd_tru(a, b - a)
```
Với hai số dương khác nhau, tổng a+b giảm sau mỗi bước mà ƯCLN không đổi. Nếu gọi bản này với (0,6), phép trừ vẫn cho (0,6), nên không tiến tới cơ sở. Không dùng điều kiện đầu vào của bản Euclid cho bản trừ khi chưa bổ sung xử lí số 0.
Bài luyện
Theo bảng, gcd(84,30) trả về bao nhiêu?
0 là số dư làm dừng, không phải ƯCLN.
30 là đối số thứ hai lúc đầu.
Lời gọi cơ sở là gcd(6,0), trả 6.
24 là số dư đầu tiên, chưa phải kết quả.
Lời giải
84%30=24, 30%24=6, 24%6=0. Do đó gcd(84,30)=gcd(30,24)=gcd(24,6)=gcd(6,0)=6.
Bài luyện
Trong hàm gcd dùng số dư, vì sao phải kiểm b==0 trước a%b?
Hàm trả a ở cơ sở, không bắt buộc là 0.
Trường hợp a<b vẫn được thuật toán xử lí.
a%b vẫn là phép lấy dư khi b khác 0.
Khi b=0, ƯCLN là a và không cần lấy dư.
Lời giải
Tại b=0, kết quả đã biết là a. Nếu tính a%b trước khi kiểm cơ sở thì Python gặp phép chia cho 0 và chương trình lỗi.
Bài luyện
Với điều kiện đầu vào của bản Euclid, cặp nào hợp lệ và cho kết quả 6?
Hai số không âm, không đồng thời bằng 0; gcd(0,6) gọi gcd(6,0).
Cặp này bị loại bởi yêu cầu không đồng thời bằng 0.
Số thứ nhất âm, ngoài điều kiện đang xét.
Số thứ hai âm, ngoài điều kiện đang xét.
Lời giải
Cặp (0,6) hợp lệ. Vì 0%6=0, hàm chuyển sang gcd(6,0) và trả 6; ba cặp còn lại vi phạm điều kiện đã nêu.
Bài luyện
Gọi bản gcd_tru trong bài với (0,6) sẽ vướng điều gì?
Phép trừ không có quy tắc như vậy.
Nhánh cuối gọi gcd_tru(0,6−0).
Bản đang xét kiểm a==b, không có nhánh ấy.
6−0 vẫn bằng 6, còn a vẫn 0.
Lời giải
Vì a=0<b=6, nhánh cuối gọi gcd_tru(0,6−0)=gcd_tru(0,6). Dữ liệu giữ nguyên nên lời gọi lặp lại; bản này chỉ được dùng với hai số dương.
Đang học