In một lần chuyển giữa hai lời gọi nhỏ
Hàm dưới dùng điều kiện n≥1 và ba tên cọc khác nhau:
```python
def Hanoi(n, i, j, k):
if n == 1:
print(f'Đĩa {n}: {i} -> {j}')
else:
Hanoi(n - 1, i, k, j)
print(f'Đĩa {n}: {i} -> {j}')
Hanoi(n - 1, k, j, i)
```
Lời gọi `Hanoi(2,1,3,2)` in ba dòng:
```text
Đĩa 1: 1 -> 2
Đĩa 2: 1 -> 3
Đĩa 1: 2 -> 3
```
Lần in ở giữa chỉ xảy ra sau khi lời gọi đầu hoàn tất. Hàm in thông tin, không trả một danh sách; nếu gán kết quả lời gọi thì nhận None.
Có thể chọn cơ sở không còn đĩa
Một bản rút gọn dùng n≥0 và chỉ làm việc khi n>0:
```python
def Hanoi_zero(n, i, j, k):
if n > 0:
Hanoi_zero(n - 1, i, k, j)
print(f'Đĩa {n}: {i} -> {j}')
Hanoi_zero(n - 1, k, j, i)
```
Khi n=0, hàm kết thúc mà không in. Với n≥1, bản này in cùng dãy lần chuyển như bản cơ sở n=1. Không gọi bản đầu với n=0 vì cơ sở của bản ấy chỉ kiểm n==1.
Lưu kết quả để dùng lại
Mỗi bước được lưu thành bộ ba (số đĩa, cọc nguồn, cọc đích). Bản sau trả danh sách thay vì in; cơ sở không đĩa trả danh sách rỗng:
```python
def Hanoi_list(n, i, j, k):
if n == 0:
return []
first = Hanoi_list(n - 1, i, k, j)
last = Hanoi_list(n - 1, k, j, i)
return first + [(n, i, j)] + last
```
Gọi với n≥0. Các danh sách được ghép theo đúng thứ tự: lời giải phần đầu, bước chuyển đĩa n, lời giải phần cuối. Ví dụ `Hanoi_list(2,1,3,2)` trả `[(1,1,2),(2,1,3),(1,2,3)]`; danh sách ghi ba lần chuyển, không ghi lại mọi trạng thái cọc.
Chạy và đối chiếu dãy lần chuyển
Tải mã, mở terminal tại thư mục chứa tệp và chạy bằng Python3:
```text
python3 g11-cs-thap-ha-noi.py 3
python3 g11-cs-thap-ha-noi.py 2 --names A C B
```
Lệnh đầu báo H(3)=7 rồi in bảy lần chuyển. Lần thứ tư phải là đĩa 3 từ cọc 1 sang cọc 3; sau lần thứ bảy, cả chồng ở cọc 3. Lệnh sau in đĩa 1 từ A sang B, đĩa 2 từ A sang C, đĩa 1 từ B sang C; thứ tự tên sau --names là nguồn, đích, trung gian.
Có thể nhập các lời gọi hàm trực tiếp trong Python để so sánh bản in với bản trả danh sách. Thử n=1,2,3,4; đối chiếu từng bước với quy tắc đĩa trên cùng và không đặt lớn lên nhỏ. Mẫu dòng lệnh chỉ liệt kê n từ 1 đến 10 để giữ bộ thử nhỏ; nhập 0, số âm hoặc ba tên cọc trùng nhau phải báo lỗi. Nếu máy gọi Python3 bằng lệnh python thì dùng tên lệnh tương ứng.
Bài luyện
Muốn dùng bản Hanoi in hai đĩa từ A sang C, lấy B làm trung gian, lời gọi nào đúng?
Bản đang xét yêu cầu n≥1, bài toán cần hai đĩa.
Thứ tự là số đĩa, nguồn, đích, trung gian.
Lời gọi đó chọn B làm đích.
Lời gọi đó chọn C làm nguồn và A làm đích.
Lời giải
Thay n=2, nguồn=A, đích=C, trung gian=B theo đúng thứ tự đối số được Hanoi(2,A,C,B). Đổi vị trí hai tên cọc sẽ đổi nhiệm vụ cần giải.
Bài luyện
Gán result=Hanoi(2,1,3,2) bằng bản in trong bài. Kết luận nào đúng?
print vẫn thực hiện dù không có return dữ liệu.
Số lần in không tự trở thành giá trị trả về.
print tạo đầu ra; hàm không trả dữ liệu bằng return.
Muốn danh sách phải dùng bản Hanoi_list.
Lời giải
Hàm gọi các lệnh print cho ba lần chuyển. Vì không có return trả dữ liệu, lời gọi kết thúc với giá trị None; nó không tự trả số bước hoặc danh sách.
Bài luyện
Với Hanoi_list(2,1,3,2), phần tử thứ hai của danh sách trả về là gì?
Đó là phần tử đầu của danh sách.
Đó là phần tử cuối của danh sách.
Đó không phải bước thứ hai của lời giải đang xét.
Bước giữa chuyển đĩa 2 từ nguồn 1 sang đích 3.
Lời giải
Danh sách là [(1,1,2),(2,1,3),(1,2,3)]. Mỗi bộ ba ghi (số đĩa,nguồn,đích), nên phần tử thứ hai là (2,1,3).
Bài luyện
Với n=0 theo điều kiện của hai bản Hanoi_zero và Hanoi_list, kết quả nào đúng?
Không có đĩa thì không có lần chuyển để in hoặc lưu.
Cơ sở không tạo một đĩa số 0.
Nhánh n>0 không chạy và bản danh sách có return [].
Danh sách rỗng biểu diễn không có bước.
Lời giải
Hanoi_zero chỉ thực hiện thân nhánh khi n>0 nên n=0 không in gì. Hanoi_list gặp cơ sở n==0 và trả [], tức không có lần chuyển nào.
Đang học