
Dãy Fibonacci là một trong những bài toán kinh điển nhất mà bất kỳ sinh viên CNTT nào cũng cần nắm vững khi học về thuật toán. Bài viết này sẽ hướng dẫn bạn cách viết hàm in dãy Fibonacci trong Python một cách chuẩn xác nhất. Chúng ta sẽ không dừng lại ở cách giải cơ bản mà còn đi sâu vào kỹ thuật tối ưu bộ nhớ chuyên nghiệp, giúp code của bạn ghi điểm tuyệt đối trong các bài tập lớn.
💡 Trả lời nhanh: Để in dãy Fibonacci trong Python hiệu quả, hãy khởi tạo hai biến a, b = 0, 1. Thay vì dùng đệ quy gây chậm, bạn nên dùng vòng lặp for để tính a, b = b, a + b và nối vào một List. Nếu số lượng phần tử cần in quá lớn, hãy sử dụng Generator với từ khóa yield để giảm độ phức tạp không gian xuống O(1), tránh tràn bộ nhớ (RAM).
Đề bài
Viết một hàm trong Python để tạo và in ra màn hình n số đầu tiên của dãy Fibonacci.
Input: Một số nguyên dương n đại diện cho số lượng phần tử của dãy cần in ra. Ví dụ: n = 5.
Output: Một chuỗi hoặc danh sách (List) chứa n số Fibonacci đầu tiên. Ví dụ: 0, 1, 1, 2, 3.
Ràng buộc: Cần xử lý các trường hợp đầu vào không hợp lệ (như n <= 0). Hàm phải hoạt động hiệu quả ngay cả khi n lớn (ví dụ n = 10000).
Phân tích
Dãy Fibonacci trong Python là một chuỗi số nguyên bắt đầu bằng 0 và 1, trong đó mỗi số tiếp theo được tạo ra bằng tổng của hai số liền trước nó. Công thức toán học cơ bản là F(n) = F(n-1) + F(n-2) với F(0) = 0 và F(1) = 1. Để giải quyết bài toán in dãy Fibonacci, chúng ta cần một cơ chế để lưu giữ hai giá trị gần nhất và liên tục cập nhật chúng qua mỗi vòng lặp.
📌 Góc nhìn thực tế: Trong thực tế chấm bài trên giảng đường, tôi thấy sinh viên năm 2 thường lao ngay vào sử dụng đệ quy (recursion) vì nó nhìn giống hệt công thức toán học. Tuy nhiên, đệ quy thuần túy sẽ sinh ra hàng loạt phép tính trùng lặp, khiến chương trình treo cứng chỉ với n = 40. Do đó, tư duy đúng ở trình độ trung cấp là phải dùng vòng lặp (Iteration) hoặc sinh tự động (Generation).
Giả định
Trong toán học máy tính hiện đại, dãy Fibonacci thường được mặc định bắt đầu bằng số 0 rồi đến 1 (tức là 0, 1, 1, 2, 3...). Một số tài liệu cũ có thể bắt đầu bằng 1, 1, nhưng trong khuôn khổ bài viết này, chúng ta sẽ áp dụng quy chuẩn phổ biến nhất là 0, 1. Yêu cầu “in dãy” sẽ được tách biệt thành hai phần: logic tạo dãy trả về một mảng/đối tượng, và logic in (print) ra màn hình. Việc này tuân thủ nguyên tắc Clean Code thay vì trộn lẫn lệnh print vào thẳng trong hàm tính toán.
Dựa trên phân tích này, bài viết sẽ giới thiệu hai hướng tiếp cận: dùng List truyền thống để thao tác dữ liệu dễ dàng, và dùng Generator để tối ưu tài nguyên tối đa cho hệ thống.
Cách giải 1: In dãy Fibonacci bằng vòng lặp và List (Truyền thống)
Sử dụng vòng lặp kết hợp cấu trúc dữ liệu List là cách tiếp cận an toàn, trực quan và dễ hiểu nhất để in dãy Fibonacci trong Python. Phương pháp này lưu trữ toàn bộ chuỗi số vào bộ nhớ trước khi trả về kết quả.
Ý tưởng cốt lõi
Chúng ta sẽ khởi tạo một danh sách (List) chứa sẵn hai giá trị ban đầu là 0 và 1. Tiếp theo, sử dụng một vòng lặp for chạy n - 2 lần. Trong mỗi lần lặp, ta lấy hai phần tử cuối cùng của mảng cộng lại với nhau và nối (append) kết quả mới đó vào cuối mảng. Cuối cùng, hàm sẽ trả về toàn bộ danh sách này.
Các bước thực hiện
-
Kiểm tra tính hợp lệ của input: nếu
n <= 0, ném ra lỗiValueError. Nếun == 1, chỉ trả về mảng[0]. -
Khởi tạo mảng
result = [0, 1]. -
Chạy vòng lặp từ
2đếnn - 1. -
Tính giá trị mới:
next_val = result[-1] + result[-2]. -
Đẩy
next_valvàoresult. -
Kết thúc vòng lặp, trả về
result.
Minh họa tay (Trace)
Giả sử người dùng nhập n = 5:
-
Bước 1:
n = 5hợp lệ. Mảng khởi tạoresult = [0, 1]. -
Bước 2: Lặp lần 1 (i = 2).
next_val = result[-1] + result[-2]tương đương1 + 0 = 1. Mảng thành[0, 1, 1]. -
Bước 3: Lặp lần 2 (i = 3).
next_val = 1 + 1 = 2. Mảng thành[0, 1, 1, 2]. -
Bước 4: Lặp lần 3 (i = 4).
next_val = 2 + 1 = 3. Mảng thành[0, 1, 1, 2, 3]. -
Bước 5: Vòng lặp kết thúc. Kết quả trả về
[0, 1, 1, 2, 3].
Đánh giá phương pháp List
-
Phù hợp người mới vì: Thuật toán rất sát với cách con người cộng nhẩm trên giấy. Việc dùng List cho phép bạn dễ dàng truy xuất lại bất kỳ phần tử nào sau khi hàm chạy xong.
-
Ưu điểm: Tốc độ tính toán nhanh, dễ dàng chuyển đổi mảng thành chuỗi (string) để in ra màn hình.
-
Nhược điểm: Tiêu tốn dung lượng RAM tỷ lệ thuận với
n. Nếu bạn muốn in 1 triệu số, RAM máy tính của bạn có thể bị lấp đầy bởi một List khổng lồ. -
Độ phức tạp:
O(n) thời gian/O(n) bộ nhớ.
Cách giải 2: Tối ưu bộ nhớ bằng Generator và từ khóa yield (Khuyên dùng)
Khác với Cách 1, cách in dãy Fibonacci bằng Generator không lưu trữ toàn bộ mảng vào RAM mà sẽ “sản xuất” ra từng số một ngay tại thời điểm bạn cần in chúng ra màn hình. Đây là dấu hiệu nhận biết một lập trình viên Python đã đạt trình độ trung cấp.
Ý tưởng tối ưu bộ nhớ
Thay vì dùng một List chứa hàng vạn phần tử, ta chỉ cần duy nhất hai biến a và b để giữ giá trị của hai số Fibonacci gần nhất. Mỗi vòng lặp, ta sẽ “tạm dừng” hàm và trả về giá trị a hiện tại bằng từ khóa yield. Sau khi bên ngoài nhận được số đó, hàm tiếp tục chạy, cập nhật biến a thành b và biến b thành a + b. Quá trình này lặp lại liên tục mà không sinh ra một List nào cả.
Các bước thực hiện
-
Kiểm tra input
n <= 0. Ném ra ngoại lệ tương ứng. -
Khởi tạo hai biến
a = 0vàb = 1. -
Khởi tạo vòng lặp
forchạy đúngnlần. -
Tại mỗi lần lặp, trả về giá trị của
athông quayield a. -
Cập nhật đồng thời hai biến bằng Tuple Unpacking:
a, b = b, a + b.
Minh họa tay (Trace)
Giả sử n = 5:
-
Hàm được gọi, trả về một đối tượng Generator (chưa tính toán ngay).
-
Vòng lặp bên ngoài gọi hàm lần 1: biến
a=0, b=1. Hàmyield 0và tạm dừng. Cập nhậta=1, b=1. -
Lần 2: Hàm tiếp tục.
yield 1. Cập nhậta=1, b=2. -
Lần 3: Hàm tiếp tục.
yield 1. Cập nhậta=2, b=3. -
Lần 4: Hàm tiếp tục.
yield 2. Cập nhậta=3, b=5. -
Lần 5: Hàm tiếp tục.
yield 3. Cập nhậta=5, b=8. -
Kết thúc
nlần, Generator cạn kiệt và dừng lại.
Khi nào nên dùng Cách 2?
Phương pháp Generator là lựa chọn bắt buộc khi bạn phải làm việc với n cực lớn (hàng triệu, hàng tỷ) hoặc môi trường thực thi có dung lượng RAM eo hẹp (ví dụ lập trình nhúng IoT bằng MicroPython). Nó cũng phù hợp khi bạn xử lý luồng dữ liệu (data streaming).
Đánh giá phương pháp Generator
-
Ưu điểm: Bộ nhớ tiêu thụ cực thấp và luôn cố định bất kể
nbằng 10 hay 10.000. Đoạn code vô cùng thanh lịch và chuẩn phong cách “Pythonic”. -
Nhược điểm: Do không lưu lại mảng, bạn không thể truy cập ngẫu nhiên (ví dụ không thể gọi
ket_qua[50]). Nếu muốn duyệt lại dãy, bạn phải sinh lại từ đầu. -
Độ phức tạp:
O(n) thời gian/O(1) bộ nhớ.
So sánh nhanh 2 cách
Bảng này giúp bạn quyết định nên dùng cách nào để tạo và in dãy Fibonacci mà không cần đọc lại toàn bài. Nhìn chung, Cách 2 luôn được đánh giá cao hơn trong thực tế.
| Tiêu chí | Cách 1: In bằng vòng lặp và List | Cách 2: Tối ưu bằng Generator (yield) |
| Ý tưởng cốt lõi | Lưu dồn phần tử vào mảng result.append() |
Tính và trả về từng số một qua yield |
| Độ phức tạp | O(n) thời gian / O(n) bộ nhớ | O(n) thời gian / O(1) bộ nhớ |
| Dễ đọc / dễ hiểu | ★★★★★ | ★★★☆☆ |
| Hiệu năng (RAM) | ★★★☆☆ | ★★★★★ |
| Phù hợp khi | Cần lưu lại toàn bộ dãy số để tính toán tiếp | Cần in dãy số cực lớn, tối ưu RAM tuyệt đối |
| Không phù hợp khi | Dung lượng RAM có hạn, hệ thống nhúng | Cần lấy một phần tử ngẫu nhiên ở giữa dãy |
Code Python đầy đủ
Cách 1 — In dãy Fibonacci bằng vòng lặp và List (Truyền thống):
# Cố định kiểu dữ liệu bằng Type Hinting giúp code chuyên nghiệp hơn
def fibonacci_list(n: int) -> list[int]:
"""
Hàm sinh dãy Fibonacci bằng vòng lặp và List.
Nhận vào số nguyên dương n, trả về list chứa n phần tử.
"""
if n <= 0:
raise ValueError("Vui lòng nhập số nguyên dương lớn hơn 0.")
if n == 1:
return [0]
# Khởi tạo mảng với 2 giá trị đầu tiên
result = [0, 1]
# Lặp để cộng dồn các phần tử tiếp theo
for i in range(2, n):
next_val = result[-1] + result[-2]
result.append(next_val)
return result
# --- TEST NHANH ---
# n_input = int(input("Nhập n: "))
# print("Dãy Fibonacci (List):", fibonacci_list(n_input))
Cách 2 — Tối ưu bộ nhớ bằng Generator và từ khóa yield (Khuyên dùng):
# Điểm khác biệt: Dùng `yield` thay vì `return` để tạo Generator object
from typing import Generator
def fibonacci_gen(n: int) -> Generator[int, None, None]:
"""
Hàm sinh dãy Fibonacci tối ưu bộ nhớ bằng Generator.
Không lưu mảng, trả về từng số một.
"""
if n <= 0:
raise ValueError("Vui lòng nhập số nguyên dương lớn hơn 0.")
a, b = 0, 1
for _ in range(n):
yield a
# Cập nhật song song a và b bằng Tuple Unpacking
a, b = b, a + b
# --- TEST NHANH ---
# n_input = int(input("Nhập n: "))
# print("Dãy Fibonacci (Generator):", end=" ")
# for so in fibonacci_gen(n_input):
# print(so, end=", ")
# print()
Ví dụ chạy thử
| STT | Input (n) | Output thực tế | Giải thích / Ngữ cảnh |
| 1 | 5 |
[0, 1, 1, 2, 3] |
Trường hợp thông thường. 5 phần tử đầu tiên của dãy. |
| 2 | 10 |
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34] |
Dãy tăng trưởng rất nhanh nhờ bản chất cộng dồn. |
| 3 | 1 |
[0] |
Edge case (Trường hợp biên). Chỉ yêu cầu in 1 số đầu tiên là số 0. |
| 4 | -3 |
ValueError |
Số âm không hợp lệ. Hàm bắt lỗi và chặn thực thi an toàn. |
Lỗi thường gặp khi in dãy Fibonacci
Lỗi 1: Cập nhật biến tuần tự thay vì song song (Tuple Unpacking)
Hiện tượng: Output in ra các số nhảy vọt sai logic, ví dụ 0, 1, 2, 4, 8 thay vì 0, 1, 1, 2, 3.
Nguyên nhân: Vì bạn gán giá trị mới cho biến a trước, sau đó biến b lại sử dụng ngay cái a mới đó để tính toán. Ở bước tính b, lẽ ra phải dùng a cũ. Python hỗ trợ gán song song (Tuple Unpacking) để giải quyết triệt để lỗi này.
Code sai:
a = b
b = a + b # Sai vì biến a ở đây đã mang giá trị mới của b
Code đúng:
a, b = b, a + b # Đúng: b và a+b được tính toán trước, sau đó mới gán ngược lại cho a và b
Lỗi 2: Dùng đệ quy thuần gây Time Limit Exceeded
Hiện tượng: Khi gọi hàm với n = 40, chương trình chạy mãi không ra kết quả, quạt tản nhiệt máy tính quay mạnh.
Nguyên nhân: Vì đệ quy thuần túy F(n) = F(n-1) + F(n-2) sẽ tính đi tính lại một bài toán con hàng triệu lần (độ phức tạp hàm mũ O(2^n)). Dù mã nguồn nhìn rất ngắn gọn nhưng đây là cấm kỵ trong môi trường thực tế.
Code sai:
def fib_bad(n):
if n <= 1: return n
return fib_bad(n-1) + fib_bad(n-2) # Hàm bị phân nhánh khổng lồ
Code đúng:
Hãy áp dụng Cách 1 (List) hoặc Cách 2 (Generator) đã hướng dẫn ở trên với độ phức tạp chỉ O(n).
Lỗi 3: Không xử lý trường hợp biên (n <= 0)
Hiện tượng: Người dùng nhập n = 0, chương trình in ra [0, 1] hoặc báo lỗi IndexError văng đỏ màn hình.
Nguyên nhân: Vì logic khởi tạo mảng result = [0, 1] mặc định độ dài đã là 2. Nếu không có lệnh if chặn lại các số n < 2 ngay từ đầu, vòng lặp phía sau sẽ hoạt động sai lệch hoặc truy xuất mảng vượt quá giới hạn.
Code sai:
def fib(n):
result = [0, 1]
# Lỗi nếu n = 0, hàm vẫn trả về mảng 2 phần tử
return result
Code đúng:
def fib(n):
if n <= 0: raise ValueError("N không hợp lệ")
if n == 1: return [0]
# ... tiếp tục khởi tạo mảng
Lỗi 4: Hàm in dãy Fibonacci trả về giá trị None
Hiện tượng: Bạn gọi print(fibonacci(5)) và kết quả trên màn hình là 0 1 1 2 3 None. Thừa một chữ None vô duyên ở cuối.
Nguyên nhân: Vì bên trong hàm bạn đã dùng lệnh print() để in trực tiếp, nhưng hàm lại không có câu lệnh return nào. Mặc định Python sẽ trả về None khi kết thúc một hàm không có return. Khi bạn lại print cái kết quả trả về đó ở ngoài, chữ None sẽ xuất hiện.
Code sai:
def fib(n):
a, b = 0, 1
for _ in range(n):
print(a)
a, b = b, a + b
# Thiếu return
Code đúng:
Thay vì dùng print bên trong, hãy nối vào mảng và return list (Cách 1) hoặc dùng yield (Cách 2).
Lỗi 5: Khởi tạo giá trị ban đầu sai chuẩn toán học
Hiện tượng: Dãy số in ra là 1, 1, 2, 3, 5... thay vì 0, 1, 1, 2, 3....
Nguyên nhân: Vì bạn khởi tạo hai giá trị đầu tiên là 1 và 1. Mặc dù trong một số ngữ cảnh cũ điều này được chấp nhận, nhưng trong khoa học máy tính và lập trình hiện đại (bao gồm các hệ thống test tự động của HackerRank, LeetCode), chuỗi Fibonacci tiêu chuẩn luôn bắt đầu bằng 0.
Code sai:
a, b = 1, 1 # Hoặc result = [1, 1]
Code đúng:
a, b = 0, 1 # Hoặc result = [0, 1]
Câu hỏi thường gặp
Dãy Fibonacci là gì?
Dãy Fibonacci trong Python là một chuỗi số nguyên bắt đầu bằng 0 và 1, theo quy luật số đứng sau luôn bằng tổng của hai số đứng ngay trước nó. Đây là chuỗi số thường xuyên xuất hiện trong cấu trúc dữ liệu, thuật toán và cả các quy luật tự nhiên như tỷ lệ vàng.
Làm sao để tìm số Fibonacci thứ N tối ưu nhất?
Để tìm số Fibonacci thứ N (chỉ lấy 1 số thay vì in cả dãy), bạn có thể dùng công thức Binet hoặc nhân ma trận (Matrix Exponentiation) để đạt độ phức tạp O(log n). Tuy nhiên, với các bài tập thông thường, dùng vòng lặp O(n) như trên là hoàn toàn đủ đáp ứng tốc độ.
Làm thế nào để in dãy Fibonacci trên cùng một dòng trong Python?
Nếu bạn đang dùng vòng lặp, thay vì lệnh print(so) mặc định sẽ xuống dòng, hãy thêm tham số end. Cú pháp chính xác là print(so, end=", "). Điều này sẽ nối các số trên một hàng ngang, cách nhau bằng dấu phẩy.
Từ khóa yield trong hàm in dãy Fibonacci hoạt động ra sao?
Từ khóa yield biến một hàm Python thông thường thành một Generator. Thay vì thoát khỏi hàm và xóa biến (như return), yield sẽ trả kết quả ra ngoài và “đóng băng” trạng thái hiện tại của biến a, b. Khi hàm được gọi ở vòng lặp tiếp theo, nó sẽ tiếp tục chạy từ vị trí bị đóng băng.
Nên dùng đệ quy hay vòng lặp for để in dãy Fibonacci?
Tuyệt đối nên dùng vòng lặp for (Iteration) thay vì đệ quy (Recursion) thuần túy. Đệ quy thuần sẽ lặp lại hàng nghìn phép tính dư thừa khiến hệ thống quá tải. Vòng lặp for có độ phức tạp thời gian tĩnh O(n), hoạt động nhanh và ổn định hơn rất nhiều.
Sự khác biệt giữa List và Generator khi lưu trữ chuỗi Fibonacci?
List lưu trữ toàn bộ các số Fibonacci vào bộ nhớ vật lý (RAM), khiến bộ nhớ tăng tuyến tính theo số lượng phần tử. Ngược lại, Generator không lưu trữ bất kỳ số nào; nó chỉ giữ công thức và sinh ra số tiếp theo khi được yêu cầu, giúp bộ nhớ luôn ở mức xấp xỉ 0 MB.
Tại sao code in dãy Fibonacci của tôi báo lỗi “RecursionError: maximum recursion depth exceeded”?
Lỗi này xảy ra khi bạn dùng phương pháp đệ quy và giá trị n đầu vào quá lớn (thường là lớn hơn 1000). Python có cơ chế bảo vệ ngăn chặn việc gọi hàm đệ quy quá sâu để chống tràn bộ nhớ (Stack Overflow). Cách khắc phục nhanh nhất là chuyển sang dùng vòng lặp for như Cách 1 trong bài.
Kết luận
Bài viết đã hướng dẫn bạn chi tiết hai phương pháp chuẩn xác để tạo và in dãy Fibonacci trong Python. Nếu bạn cần lưu trữ dữ liệu để vẽ biểu đồ hoặc thao tác sau đó, hãy chọn Cách 1 (Vòng lặp & List). Ngược lại, nếu mục tiêu là in ra hàng vạn con số với hiệu năng cao nhất, Cách 2 (Generator với yield) chính là chiếc chìa khóa thể hiện sự chuyên nghiệp của bạn. Hãy tự mình gõ lại đoạn code và xem máy tính của bạn chạy nhanh đến mức nào nhé!
Các khóa học liên quan:
Một số sản phẩm từ Python:
Một số sách lập trình Python bạn hãy tham khảo