Đệ quy là gì: cách lập trình bằng cách gọi lại chính hàm

Đệ quy là gì: cách lập trình bằng cách gọi lại chính hàm

Đệ quy (recursion) là kỹ thuật lập trình trong đó một hàm tự gọi lại chính nó để giải quyết bài toán nhỏ hơn, lặp lại cho tới khi đạt được điều kiện dừng (base case). Kỹ thuật này đặc biệt hiệu quả cho các bài toán có cấu trúc phân rã tự nhiên: duyệt cây, quy hoạch động, chia để trị và backtracking.

Đệ quy xuất hiện xuyên suốt lịch sử lập trình máy tính, kể cả trong khái niệm lập trình hàm mà Alan Turing và Alonzo Church phát triển. Một cách nói kinh điển về sức mạnh của nó là: nếu một tác vụ chỉ gọi lại chính nó với các tham số nhỏ hơn, thì máy tính có thể tự giải quyết tác vụ đó mà không cần bất kỳ công thức nào khác.

Mẫu tổ ong được tạo bằng thuật toán đệ quy, mỗi ô được vẽ lại theo cùng quy tắc từ các ô nhỏ hơn bên trong

Điều kiện cần có của một hàm đệ quy

Một hàm đệ quy đúng đắn luôn có hai phần không thể thiếu:

  • Điều kiện dừng (base case) – trường hợp mà hàm trả về kết quả mà không gọi lại chính nó.
  • Bước giảm (recursive step) – trường hợp gọi lại hàm với tham số nhỏ hơn, tiến dần về điều kiện dừng.

Thiếu điều kiện dừng sẽ dẫn tới vòng lặp vô hạn (infinite recursion) và làm tràn ngăn xếp lời gọi. Thiếu bước giảm thì hàm không bao giờ tiến tới điều kiện dừng. Đây là hai lỗi phổ biến nhất khi mới học đệ quy, và cả hai đều dễ phát hiện khi chạy thử chương trình.

Ví dụ kinh điển – tính giai thừa:

DE QUY:
GIAI THUA(n):
    neu n <= 1: tra ve 1
    tra ve n x GIAI THUA(n - 1)

LAP (tuong duong):
GIAI_THUA_ITER(n):
    kq = 1
    cho i tu 2 den n: kq = kq x i
    tra ve kq

Cơ chế bên trong: ngăn xếp lời gọi (call stack)

Mỗi lần hàm đệ quy được gọi, hệ thống tạo một khung ngăn xếp mới (stack frame) chứa các biến cục bộ và địa chỉ trả về. Khi hàm kết thúc, khung đó bị giải phóng và điều khiển trở về khung bên dưới. Đó là lý do các hàm đệ quy viết bằng ngôn ngữ C (không có quản lý bộ nhớ tự động) dễ gặp lỗi stack overflow khi chiều sâu vượt quá kích thước ngăn xếp.

Python quản lý điều này bằng giới hạn đệ quy mặc định 1000. Vượt quá ngưỡng này sẽ sinh ra ngoại lệ RecursionError, vì lời gọi quá sâu có thể làm tràn ngăn xếp C bên dưới và gây crash toàn bộ interpreter. Hàm sys.setrecursionlimit() cho phép điều chỉnh ngưỡng này.

Quy trình mở khung và giải phóng khung diễn ra theo nguyên tắc vòng sau cùng vào trước ra (LIFO). Nhờ đó một bài toán đệ quy luôn phải chạy ngược lại từ điều kiện dừng về điểm xuất phát, và thứ tự hoàn thành các lời gọi chính là thứ tự ngăn xếp được giải phóng.

Sơ đồ thứ tự thực thi hàm đệ quy in số 0 đến 4 trên ngăn xếp lời gọi, các lời gọi lồng nhau rồi giải phóng ngược từ trong ra ngoài

Đệ quy trực tiếp và đệ quy tương tác

Hai hàm gọi lẫn nhau trực tiếp tạo thành mutual recursion (đệ quy tương tác), dùng phổ biến khi phân tích biểu thức: isExpression() gọi isTerm() và ngược lại. Loại này không thể gom về một hàm duy nhất mà không làm mất tính tự nhiên của thuật phân tích.

Tháp Hà Nội (Tower of Hanoi) là ví dụ kinh điển cho đệ quy: với n đĩa, số bước tối thiểu là 2^n – 1. Với 3 đĩa cần 7 bước, với 10 đĩa là 1023 bước – tăng theo cấp số nhân. Bài toán này không có lời giải vòng lặp nào đơn giản bằng, minh hoạ rõ sức mạnh biểu đạt của đệ quy.

Lời giải đệ quy của Tháp Hà Nội dựa trên ba bước: chuyển n-1 đĩa sang cọc trung gian, chuyển đĩa lớn nhất sang cọc đích, rồi chuyển n-1 đĩa còn lại sang cọc đích. Cùng một khuôn mẫu gọi lại chính nó với n-1 đĩa, lặp lại cho tới khi n bằng 1.

Đệ quy đuôi (tail recursion)

Đệ quy đuôi là trường hợp lời gọi đệ quy là thao tác cuối cùng của hàm, nên kết quả của nó được trả thẳng về nơi gọi mà không cần chờ khung hàm hiện tại hoàn tất. Các ngôn ngữ như Scheme, Haskell hay OCaml tối ưu trường hợp này thành vòng lặp thật, nên đệ quy sâu không làm tràn ngăn xếp.

Python không thực hiện tối ưu đệ quy đuôi, nên vẫn phải dựa vào ngăn xếp lời gọi. Nếu gặp trường hợp đệ quy đuôi rất sâu, hãy viết lại thành vòng lặp hoặc dùng kỹ thuật trampoline.

Độ phức tạp và cây đệ quy

Naive recursive Fibonacci có độ phức tạp thời gian O(2^n) vì cây đệ quy tính lại nhiều giá trị trùng lặp. Có thể tối ưu bằng memoization (lưu kết quả đã tính) hoặc chuyển sang dynamic programming, đưa độ phức tạp xuống O(n). Đây chính là bài học phổ quát: đệ quy rõ ràng về mặt triết lý nhưng thường tốn bộ nhớ và thời gian hơn nếu không cẩn thận.

Master theorem là công cụ phân tích độ phức tạp cho các thuật toán chia để trị có dạng T(n) = aT(n/b) + O(n^c), cho biết kết quả là O(n^c log n) khi a = b^c, hay O(n^log_b a) khi a lớn hơn.

Hình tam giác Sierpinski sau tám vòng lặp đệ quy, mỗi vòng chia đôi tam giác thành ba và lặp lại trên các tam giác nhỏ

Khi nào nên dùng và khi nào nên tránh

Nên dùng đệ quy Nên tránh, dùng vòng lặp
Duyệt cây (thư mục, JSON, DOM) Duyệt mảng tuyến tính
Chia để trị (quicksort, merge sort) Tính giai thừa, tổng các số
Backtracking (sudoku, n-queens) Xử lý chuỗi đơn giản
Xử lý file phân cấp (thư mục) Vòng lặp vô tận hàng triệu lần

Nguyên tắc thực hành: nếu bài toán có cấu trúc phân rã tự nhiên thành các bài toán con cùng dạng, hãy dùng đệ quy. Nếu đệ quy chỉ là cách viết vòng lặp một cách phức tạp hơn mà không mang lại lợi ích biểu đạt, hãy dùng vòng lặp – vừa dễ đọc vừa tránh rủi ro tràn ngăn xếp.

Trước khi viết một hàm đệ quy, hãy tự hỏi chiều sâu tối đa của nó là bao nhiêu. Nếu con số đó không xác định theo dữ liệu đầu vào, đệ quy là lựa chọn rủi ro vì một tập dữ liệu bất thường có thể làm tràn ngăn xếp mà không có dấu hiệu báo trước.

Những nguồn thông tin chính

Tôi là một lập trình viên IOS. Code chính là IOS nhưng thỉnnh thoảng vẫn đá sang Android hoặc web. Mặc dù không quá thông thạo nhưng tôi sẽ chia sẻ những kiến thức mà mình đã tìm hiểu, áp dụng qua.

Bài viết liên quan

Debugging là gì: quy trình, kỹ thuật và công cụ tìm lỗi

Debugging là gì: quy trình, kỹ thuật và công cụ tìm lỗi phần mềm Debugging là quá trình phát hiện, cô lập và khắc phục lỗi trong phần mềm. Từ…

Xem thêm

Terraform là gì: công cụ Infrastructure as Code của HashiCorp

Terraform là gì: công cụ Infrastructure as Code của HashiCorp Terraform là một công cụ Infrastructure as Code (IaC) do HashiCorp phát triển, cho phép người dùng khai báo và…

Xem thêm

Fortran là gì: ngôn ngữ lập trình cho tính toán khoa học

Fortran là gì: ngôn ngữ lập trình cho tính toán khoa học Fortran là gì? Đây là ngôn ngữ lập trình cao cấp được thiết kế riêng cho các bài…

Xem thêm
0 0 đánh giá
Article Rating
Theo dõi
Thông báo của
guest
0 Comments
Cũ nhất
Mới nhất Được bỏ phiếu nhiều nhất