Theo Ronald Graham, Đệ quy là một phương pháp giải quyết vấn đề trong đó giải pháp phụ thuộc vào các giải pháp cho các trường hợp nhỏ hơn của cùng một vấn đề
Tuy nhiên, vì độ sâu của ngăn xếp bị hạn chế nên nó sẽ gây tràn cho
3. Chuyển đổi các hàm đệ quy đuôi
Trường hợp đơn giản nhất để xử lý là đệ quy đuôi. Các hàm như vậy hoàn thành tất cả công việc trong phần thân của chúng [nhánh không phải cơ sở] vào thời điểm cuộc gọi đệ quy kết thúc, do đó, không có gì khác để làm vào thời điểm đó ngoài việc trả về giá trị của nó. Nói chung, họ làm theo cùng một mô hình
Bộ tích lũy là biến chứa các giải pháp một phần trong quá trình thực hiện. Chúng đại diện cho các giải pháp cho một chuỗi các vấn đề phụ, được lồng vào nhau. Mỗi cuộc gọi cập nhật bộ tích lũy cho đến khi đệ quy đạt đến trường hợp cơ sở. Tại thời điểm đó, bộ tích lũy nắm giữ giải pháp cho toàn bộ vấn đề mà chúng tôi đã bắt đầu giải quyết ngay từ đầu
3. 1. Phiên bản lặp lại của đệ quy đuôi
Mẫu đệ quy đuôi ở trên có phiên bản lặp sau
Từ đó, chúng tôi xây dựng các quy tắc chung của chuyển đổi
- Khởi tạo bộ tích lũy trước vòng lặp while
- Sử dụng phủ định của điều kiện trường hợp cơ sở làm điều kiện của vòng lặp
- Sử dụng phần thân của hàm đệ quy [ngoại trừ lệnh gọi đệ quy] làm phần thân của vòng lặp while
- Sau vòng lặp, hãy áp dụng bản cập nhật trường hợp cơ sở của bộ tích lũy và trả về giá trị của nó
Thông thường, bản cập nhật trường hợp cơ sở không thay đổi giá trị của bộ tích lũy vì nó thường dẫn đến một hoạt động trung lập, chẳng hạn như cộng 0 hoặc nhân với 1 hoặc không có bản cập nhật nào được áp dụng. Vì vậy, chúng ta có thể bỏ qua phần này trong hầu hết các trường hợp. Tuy nhiên, chúng tôi giữ nó trong mã giả để giải thích cho các chức năng có cập nhật trường hợp cơ sở là không tầm thường. Ví dụ: đó là trường hợp nếu chúng ta có nhiều trường hợp cơ sở và mỗi trường hợp kết hợp một giá trị khác nhau với bộ tích lũy. Đó cũng là trường hợp nếu bản cập nhật trường hợp cơ sở phụ thuộc vào bộ tích lũy
3. 2. Ví dụ
Hãy sử dụng các quy tắc đó và chuyển đổi hàm đệ quy đuôi để tính giai thừa
Bây giờ chúng ta hãy xác định các phần tử của đệ quy đuôi này mà chúng ta sẽ sắp xếp lại trong biến thể lặp
- điều kiện trường hợp cơ sở.
- cập nhật bộ tích lũy trường hợp cơ sở. nhân với 1
- giá trị ban đầu của bộ tích lũy. 1
- cập nhật bộ tích lũy.
- giảm sự cố. từ đến
Với ý nghĩ đó, chúng ta có được hàm lặp sau
Mặc dù phủ định của là
4. Phương pháp chuyển đổi chung
Chúng tôi đã thấy cách chúng tôi có thể biến các hàm đệ quy đuôi thành tính lặp. Tuy nhiên, có những loại đệ quy khác. Ví dụ: một hàm đệ quy đầu đặt lệnh gọi đệ quy ở đầu phần thân của nó thay vì phần cuối của nó. Các loại khác bao quanh cuộc gọi với các khối mã để xử lý trước đầu vào và xử lý sau giá trị trả về của cuộc gọi. Hơn nữa, một hàm đệ quy có thể thực hiện bất kỳ số lần gọi đệ quy nào trong phần thân của nó, không chỉ một
Thật may mắn cho chúng ta, có một cách tổng quát để biến bất kỳ phép đệ quy nào thành một thuật toán lặp. Phương pháp này dựa trên quan sát rằng các hàm đệ quy được thực thi bằng cách đẩy các khung lên ngăn xếp cuộc gọi và bật chúng ra khỏi ngăn xếp đó. Do đó, nếu chúng ta mô phỏng ngăn xếp của mình, chúng ta có thể thực thi lặp đi lặp lại bất kỳ chức năng đệ quy nào trong một vòng lặp chính. Tuy nhiên, mã kết quả có thể không đẹp. Đó là lý do tại sao trước tiên chúng ta thường cố gắng làm cho hàm của mình có tính đệ quy đuôi. Nếu chúng tôi thành công, chúng tôi có thể nhận được mã khá dễ đọc bằng phương pháp từ Phần 3. Nếu không, chúng tôi sử dụng phương pháp chuyển đổi chung. Vì vậy, trước tiên hãy kiểm tra dạng tổng quát của các hàm đệ quy
4. 1. Hình thức tổng quát của đệ quy
Một hàm đệ quy có thể thực hiện số lượng lệnh gọi đệ quy tùy ý trong phần thân của nó
Mã giả này bao gồm các trường hợp trong đó số lần gọi đệ quy [
4. 2. Đồ thị thực hiện
Mỗi cuộc gọi đến
Cạnh có hướng từ
4. 3. Thực thi dưới dạng Depth-First Traversal
Vì vậy, chúng tôi bắt đầu trong nút khung của trình gọi ban đầu và thực thi khối mã không đệ quy trước lệnh gọi đệ quy đầu tiên. Tại thời điểm đó, chúng tôi đã tạo một nút con đại diện cho khung của cuộc gọi. Cách thức hoạt động của đệ quy ngụ ý rằng chúng ta nên di chuyển đến nút con và thăm các nút con của nó theo cách tương tự. Sau đó, chúng tôi thu thập giá trị trả về của khung, quay lại nút bắt đầu và chuyển sang nút con tiếp theo. Mục đích là truy cập tất cả các nút và quay lại khung ban đầu, tại thời điểm đó, chúng tôi kết hợp giá trị trả về của con và đưa ra giải pháp
Nhưng, làm thế nào để đồ thị này giúp chúng ta biến một hàm đệ quy thành một hàm lặp? . Phiên bản lặp của truyền tải theo chiều sâu sử dụng ngăn xếp để lưu trữ các nút được đánh dấu để truy cập. Nếu chúng ta triển khai các nút và cạnh theo cách tương ứng với các thành phần của hàm, chúng ta sẽ nhận được phiên bản lặp lại của nó. Ở đó ngăn chứa các nút đóng vai trò là ngăn gọi của một CPU
4. 4. Chi tiết triển khai
Biểu đồ thực thi là ẩn. Điều đó có nghĩa là chúng tôi tạo nó một cách nhanh chóng khi chúng tôi thực thi các khối mã không đệ quy và thực hiện các cuộc gọi đệ quy. Để thực hiện đúng, chúng ta nên triển khai các khung và cạnh theo cấu trúc của hàm đệ quy mà chúng ta đang chuyển đổi
Vì vậy,
4. 5. Ví dụ
Hãy chuyển đổi đệ quy này thành phép lặp
# Using for loopdef for_loop2[n]:1
x = n
for i in range[1,n,1]:
x = x*[n-i]
return x
x = for_loop2[26]
print[x]
# Using Recursiondef recursive2[n]:
### base case ###
if n
Chúng tôi sẽ cập nhật bộ đếm và thực thi NRCB tương ứng trong hàm trả về khung con tiếp theo
2
Chúng tôi kiểm tra xem khung có cạnh ra ngoài chưa được thăm hay không bằng cách xác minh rằng đó không phải là khung trường hợp cơ sở và kiểm tra xem khung có. nrcb_count < 2. Toàn bộ mã sẽ trông như thế này
3
4. 7. Khả năng đọc và ngữ nghĩa
Cung cấp thuật toán duyệt theo chiều sâu chung với các triển khai cụ thể theo đệ quy của các khung, cạnh và các hàm liên quan, chúng tôi nhận được một biến thể lặp của hàm đệ quy mà chúng tôi muốn chuyển đổi. Tuy nhiên, mã lặp kết quả sẽ không dễ đọc và dễ hiểu như mã đệ quy ban đầu
Tuy nhiên, chúng tôi giữ lại một số ngữ nghĩa trong biểu đồ. Bởi vì nó thể hiện cấu trúc đệ quy của vấn đề ban đầu, chúng ta vẫn có thể giải thích nó. Một nút có con của nó đại diện cho phần thân của đệ quy, trong khi trường hợp cơ sở có thể được nhận ra trong các nút không có con
4. 8. Tối ưu hóa và các loại đệ quy
Cuối cùng, chúng tôi lưu ý rằng chúng tôi có thể truy cập một số nút trong biểu đồ nhiều lần. Đó là trường hợp nếu các bài toán con chồng lên nhau, như trường hợp của các số Fibonacci. Cả
Hơn nữa, hình dạng của biểu đồ thực thi cho thấy sự khác biệt giữa các loại đệ quy khác nhau. Đồ thị của hàm đệ quy chỉ thực hiện một lệnh gọi đệ quy trong phần thân của nó là một đường dẫn. Nếu nó thực hiện hai lệnh gọi và các bài toán con không trùng nhau, biểu đồ kết quả sẽ là một cây nhị phân. Đệ quy đuôi là cụ thể vì giai đoạn tái tổ hợp của nó là một hoạt động nhận dạng. mỗi nút chỉ chuyển tiếp giá trị trả về của con. Đó là lý do tại sao chúng ta không cần ngăn xếp để lặp đi lặp lại
5. Phần kết luận
Trong bài viết này, chúng ta đã nói về việc chuyển đổi đệ quy thành phép lặp. Chúng tôi đã trình bày một cách chung để chuyển đổi bất kỳ hàm đệ quy nào thành hàm lặp. Ngoài ra, chúng tôi đã chỉ ra một phương pháp chỉ đệ quy đuôi. Mặc dù chuyển đổi có thể mang lại cho chúng tôi hiệu suất, mã sẽ mất khả năng đọc theo cách này
tác giả dưới cùng
Nếu bạn có một vài năm kinh nghiệm trong Khoa học máy tính hoặc nghiên cứu và bạn muốn chia sẻ kinh nghiệm đó với cộng đồng, hãy xem Nguyên tắc đóng góp của chúng tôi