Có sự khác biệt nào giữa đệ quy cấu trúc và đệ quy đuôi hay cả hai đều giống nhau không? Tôi thấy rằng trong cả hai lần truy xuất này, hàm đệ quy được gọi trên tập hợp con của các mục gốc.
Có sự khác biệt nào giữa đệ quy cấu trúc và đệ quy đuôi hay cả hai đều giống nhau không? Tôi thấy rằng trong cả hai lần truy xuất này, hàm đệ quy được gọi trên tập hợp con của các mục gốc.
Câu trả lời:
Đệ quy cấu trúc: các cuộc gọi đệ quy được thực hiện trên các đối số có cấu trúc nhỏ hơn .
Đệ quy đuôi: cuộc gọi đệ quy là điều cuối cùng xảy ra.
Không có yêu cầu rằng đệ quy đuôi nên được gọi trên một đối số nhỏ hơn. Trong thực tế, khá thường xuyên các hàm đệ quy được thiết kế để lặp lại mãi mãi. Ví dụ: đây là một đệ quy đuôi tầm thường (không hữu ích lắm, nhưng đó là đệ quy đuôi):
def f(x):
return f(x+1)
Chúng tôi thực sự phải cẩn thận hơn một chút. Có thể có một số lệnh gọi đệ quy trong một hàm và không phải tất cả chúng đều cần được đệ quy đuôi:
def g(x):
if x < 0:
return 42 # no recursive call
elif x < 20:
return 2 + g(x - 2) # not tail recursive (must add 2 after the call)
else:
return g(x - 3) # tail recursive
Một người nói về các cuộc gọi đệ quy đuôi . Một chức năng mà các cuộc gọi đệ quy được tất cả các đuôi-đệ quy sau đó được gọi là một hàm đuôi-đệ quy.
Đệ quy đuôi là một trường hợp rất đơn giản của đệ quy cấu trúc, trong đó cấu trúc được đề cập là một danh sách liên kết . Trong ngôn ngữ mà bạn có thể đang sử dụng chủ yếu, danh sách này có thể không có nghĩa đen trong mã; đúng hơn, đó là một "danh sách các cuộc gọi đến hàm" theo khái niệm, một khái niệm có thể không thể diễn đạt bằng văn bản sử dụng ngôn ngữ đó. Trong Haskell (ngôn ngữ của tôi), bất kỳ lệnh gọi hàm đệ quy nào thực sự có thể được thay thế bằng các hành động tuần tự trên một danh sách theo nghĩa đen có các phần tử theo nghĩa đen là "gọi hàm", nhưng đây có lẽ là một ngôn ngữ chức năng.
Đệ quy cấu trúc là một cách vận hành trên một đối tượng được định nghĩa là một hỗn hợp của các đối tượng khác (có thể là hỗn hợp). Ví dụ, cây nhị phân là một đối tượng chứa các tham chiếu đến hai cây nhị phân hoặc trống (do đó, nó là một đối tượng được định nghĩa đệ quy ). Ít tự tham chiếu hơn, một cặp (t1, t2) chứa hai giá trị của một số loại t1 và t2 thừa nhận đệ quy cấu trúc, mặc dù t1 và t2 không cần phải là cặp. Đệ quy này có dạng
hành động trên cặp = kết hợp kết quả của các hành động khác trên mỗi yếu tố
Điều đó không có vẻ rất sâu sắc.
Thông thường, đệ quy cấu trúc không thể được đệ quy đuôi, mặc dù bất kỳ loại đệ quy nào cũng có thể được viết lại dưới dạng đệ quy đuôi (bằng chứng: nếu bạn chạy đệ quy gốc, các hành động được hoàn thành theo một thứ tự nhất định; tương đương với việc thực hiện chuỗi hành động cụ thể đó, như tôi đã thảo luận trước đó, là đệ quy đuôi).
Cây nhị phân hoặc ví dụ cặp ở trên chứng minh điều này: tuy nhiên bạn sắp xếp các cuộc gọi đệ quy trên các tiểu dự án, chỉ một trong số chúng có thể là hành động cuối cùng; có lẽ không ai, nếu kết quả của họ được kết hợp theo một cách nào đó (giả sử, bổ sung). Như Andrej Bauer nói trong câu trả lời của mình, điều này có thể xảy ra ngay cả khi chỉ có một cuộc gọi đệ quy, miễn là kết quả được sửa đổi. Nói cách khác, đối với mọi loại đối tượng không phải là những danh sách được liên kết hiệu quả (chỉ có một tiểu dự án hoàn toàn), đệ quy cấu trúc không phải là đệ quy đuôi.
f x = (stuff defining x'); f x'
giống như trình tự các nút trong danh sách được liên kết được xác định như l = modify f : l
(theo kiểu Haskell-monad). Nó không chỉ là sự tương đồng về mặt thuật ngữ đối với tôi. Đối với đệ quy đuôi trên cây nhị phân, bạn có thể giải thích? Tôi chỉ có thể nghĩ về thực tế tuyến tính hóa từ đoạn thứ hai của tôi.
f (f x)
cuộc gọi bên ngoài f
là đệ quy đuôi. Làm thế nào mà nó phù hợp với quan điểm rằng đó là tất cả về danh sách? Đây là một ví dụ khác: f : (Int -> Int) -> (Int -> Int)
với f g 0 = g 42
và f g (n + 1) = f (f . g) n
. Các khả năng là vô tận, và một số là hữu ích.
f (f x)
áp dụng: trong đánh giá của f bên ngoài, bên trong không phải là một cuộc gọi đuôi (trừ khi f là danh tính). Các câu lệnh if có thể được viết lại một cách tầm thường không phân nhánh trong lệnh gọi đuôi : if (c) then f a else f b == let x = if (c) then a else b in f x
. Ví dụ cuối cùng không hợp lệ vì f . g
không đánh máy; ngay cả như vậy, nó vẫn sẽ không phải là đệ quy đuôi: f g = \n -> if n == 0 then g 42 else f (f . g) (n - 1)
không phải là một lời kêu gọi f
, mà là một lambda thực sự khác biệt. (tiếp theo)
f g = h where { h 0 = g 42; h n = f (f . g) (n - 1) }
, nhưng nếu bạn đưa nó vào cuộc thảo luận, thì bất kỳ hàm đệ quy nào , đuôi hay không, đều được chấp nhận và thuật ngữ này trở nên vô nghĩa.