Lặp lại có thể thay thế đệ quy?


42

Tôi đã nhìn thấy khắp nơi trên ngăn xếp tràn, ví dụ ở đây , ở đây , ở đây , ở đây , ở đây và một số người khác tôi không quan tâm đề cập đến, đó là "bất kỳ chương trình mà sử dụng đệ quy có thể được chuyển đổi sang một chương trình chỉ sử dụng lặp đi lặp lại".

Thậm chí còn có một chủ đề được đánh giá cao với một câu trả lời được đánh giá cao nói rằng có thể.

Bây giờ tôi không nói họ sai. Chỉ là câu trả lời đó phản ánh kiến ​​thức và hiểu biết ít ỏi của tôi về điện toán.

Tôi tin rằng mọi chức năng lặp có thể được biểu thị dưới dạng đệ quy và wikipedia có một tuyên bố về hiệu ứng đó. Tuy nhiên, tôi nghi ngờ điều ngược lại là đúng. Đối với một, tôi nghi ngờ các hàm đệ quy không nguyên thủy có thể được biểu diễn lặp đi lặp lại.

Tôi cũng nghi ngờ siêu hoạt động có thể được thể hiện lặp đi lặp lại.

Trong câu trả lời của anh ấy (mà tôi không hiểu theo cách này) cho câu hỏi của tôi @YuvalFIlmus nói rằng không thể chuyển đổi bất kỳ chuỗi các phép toán nào thành một chuỗi bổ sung.

Nếu câu trả lời của YF thực sự đúng (tôi đoán là vậy, nhưng lý luận của anh ta ở trên đầu tôi) thì điều đó có nghĩa là không phải mọi đệ quy đều có thể được chuyển đổi thành lặp đi lặp lại? Bởi vì nếu có thể chuyển đổi mọi đệ quy thành phép lặp, tôi có thể diễn đạt tất cả các hoạt động như một chuỗi bổ sung.

Câu hỏi của tôi là:

Mỗi đệ quy có thể được chuyển đổi thành lặp và tại sao?

Xin vui lòng cho một câu trả lời một học sinh trung học sáng hoặc một sinh viên năm thứ nhất sẽ hiểu. Cảm ơn bạn.

Tái bút Tôi không biết đệ quy nguyên thủy là gì (tôi biết về hàm Ackermann và nó không phải là đệ quy nguyên thủy, nhưng vẫn có thể tính toán được.

PPS: Nếu câu trả lời là có, ví dụ bạn có thể viết một phiên bản lặp của hàm không đệ quy nguyên thủy không. Ví dụ Ackermann trong câu trả lời. Nó sẽ giúp tôi hiểu.


21
Tất cả mọi thứ bạn chạy trên CPU là lặp đi lặp lại. Bạn có thể viết nó một cách đệ quy bằng ngôn ngữ bậc cao hơn, nhưng dù sao nó cũng được biên dịch thành một nhóm các trình biên dịch trình biên dịch lặp. Vì vậy, theo đúng nghĩa đen, trình biên dịch thực hiện chính xác những gì bạn đang hỏi, nó chuyển đổi tất cả đệ quy ưa thích của bạn thành một loạt các hướng dẫn lặp cho CPU.
Davor

6
Ở mức độ thấp, hầu hết chúng ta đều biết rằng đệ quy bằng với phép lặp cộng với stack. Đệ quy mô hình ngữ pháp không ngữ cảnh, trong khi tự động đẩy xuống là "chương trình" lặp với bộ nhớ ngăn xếp.
Hendrik

2
Chỉ cần chỉ ra rằng nói chung nên đợi ít nhất 24 giờ để xem các câu trả lời khác có bật lên không. Câu hỏi được chấp nhận có vẻ khá dài và phức tạp với tôi, thẳng thắn, và dường như cố tình làm phức tạp vấn đề hơn mức cần thiết. Câu trả lời cơ bản cho câu hỏi của bạn là "ngăn xếp được sử dụng cho đệ quy có thể được thực hiện rõ ràng theo cách lặp" và nó không cần phức tạp hơn thế nhiều. Cân nhắc về việc bộ nhớ không bị ràng buộc hoặc không phát huy tác dụng vì tính năng đó là cần thiết cho các ngăn xếp đệ quy, dù sao đi nữa.
AnoE

có thể thực hiện trình giả lập máy Turing chỉ với lần lặp
Sarge Borsch

Trong một lớp ngôn ngữ so sánh tôi đã mất cách đây rất lâu, chúng tôi đã phải viết chức năng của Ackermann trong tập hợp, trong FORTRAN (không phải Fortran hiện đại), và bằng ngôn ngữ đệ quy của sự lựa chọn. Vì vậy, có, nó có thể trong thực tế. Và tất nhiên là có thể trong lý thuyết. Xem, ví dụ, câu hỏi chứng minh rằng máy Turing và phép tính lambda là tương đương .
David Hammen

Câu trả lời:


52

Có thể thay thế đệ quy bằng phép lặp cộng với bộ nhớ không giới hạn .

Nếu bạn chỉ có vòng lặp (giả sử, trong khi các vòng lặp) và một lượng bộ nhớ hữu hạn, thì tất cả những gì bạn có là một máy tự động hữu hạn. Với số lượng bộ nhớ hữu hạn, tính toán có số bước hữu hạn có thể, do đó có thể mô phỏng tất cả chúng bằng một máy tự động hữu hạn.

Có bộ nhớ không giới hạn thay đổi thỏa thuận. Bộ nhớ không giới hạn này có thể có nhiều dạng hóa ra có sức mạnh biểu cảm tương đương. Ví dụ, máy Turing giữ cho nó đơn giản: có một băng duy nhất và máy tính chỉ có thể di chuyển tiến hoặc lùi trên băng một bước một lần - nhưng điều đó đủ để làm bất cứ điều gì bạn có thể làm với các chức năng đệ quy.

Một máy Turing có thể được xem là một mô hình lý tưởng hóa của một máy tính (máy trạng thái hữu hạn) với một số lưu trữ bổ sung phát triển theo yêu cầu. Lưu ý rằng điều quan trọng là không chỉ không có giới hạn hữu hạn trên băng, mà ngay cả khi được cung cấp đầu vào, bạn không thể dự đoán một cách đáng tin cậy bao nhiêu băng sẽ cần. Nếu bạn có thể dự đoán (tức là tính toán) cần bao nhiêu băng từ đầu vào, thì bạn có thể quyết định liệu tính toán có dừng lại hay không bằng cách tính kích thước băng tối đa và sau đó xử lý toàn bộ hệ thống, bao gồm cả băng hiện hữu hạn, như một máy trạng thái hữu hạn .

Một cách khác để mô phỏng máy Turing với máy tính như sau. Mô phỏng máy Turing với chương trình máy tính lưu trữ phần đầu của băng trong bộ nhớ. Nếu tính toán đạt đến cuối phần băng phù hợp với bộ nhớ, hãy thay thế máy tính bằng một máy tính lớn hơn và chạy lại tính toán.

Bây giờ giả sử rằng bạn muốn mô phỏng một tính toán đệ quy với máy tính. Các kỹ thuật để thực hiện các hàm đệ quy đã được biết đến: mỗi lệnh gọi hàm có một phần bộ nhớ, được gọi là khung stack . Điều quan trọng, các hàm đệ quy có thể truyền thông tin qua nhiều cuộc gọi bằng cách chuyển các biến xung quanh. Về mặt triển khai trên máy tính, điều đó có nghĩa là một lệnh gọi hàm có thể truy cập vào khung ngăn xếp của một cuộc gọi cha (grand-) * .

Máy tính là bộ xử lý - một máy trạng thái hữu hạn (với số lượng trạng thái rất lớn, nhưng chúng tôi đang thực hiện lý thuyết tính toán ở đây, vì vậy tất cả vấn đề là nó hữu hạn) - kết hợp với bộ nhớ hữu hạn. Bộ vi xử lý chạy một vòng lặp khổng lồ: trong khi bật nguồn, đọc một lệnh từ bộ nhớ và thực hiện nó. (Bộ xử lý thực phức tạp hơn thế nhiều, nhưng nó không ảnh hưởng đến những gì họ có thể tính toán, chỉ là họ làm việc đó nhanh và tiện lợi như thế nào.) Một máy tính có thể thực hiện các chức năng đệ quy chỉ bằng vòng lặp này để cung cấp phép lặp, cộng với cơ chế để truy cập bộ nhớ, bao gồm khả năng tăng kích thước của bộ nhớ theo ý muốn.

Nếu bạn hạn chế đệ quy thành đệ quy nguyên thủy, thì bạn có thể hạn chế phép lặp đối với phép lặp bị chặn . Đó là, thay vì sử dụng các vòng lặp while với thời gian chạy không thể đoán trước, bạn có thể sử dụng cho các vòng lặp trong đó số lần lặp được biết ở đầu vòng lặp¹. Số lần lặp có thể không được biết ở đầu chương trình: bản thân nó có thể được tính bằng các vòng lặp trước đó.

Tôi thậm chí sẽ không phác thảo một bằng chứng ở đây, nhưng có một mối quan hệ trực quan giữa việc chuyển từ đệ quy nguyên thủy sang đệ quy đầy đủ và từ các vòng lặp đến các vòng lặp: trong cả hai trường hợp, nó liên quan đến việc không biết trước khi nào bạn sẽ dừng lại. Với đệ quy đầy đủ, điều này được thực hiện với toán tử thu nhỏ, nơi bạn tiếp tục cho đến khi bạn tìm thấy một tham số thỏa mãn điều kiện. Với các vòng lặp while, điều này được thực hiện bằng cách tiếp tục cho đến khi điều kiện vòng lặp được thỏa mãn.

forwhilen


Chấp nhận giải thích chi tiết, giữ ở mức yêu cầu.
Tobi Alafin

12
Tôi nghĩ rằng nó đáng chú ý hơn trong các máy tính thực, đệ quy cũng sử dụng hết bộ nhớ (tăng số lượng cuộc gọi). Vì vậy, trong các ứng dụng thực tế, không có yêu cầu về bộ nhớ không giới hạn (vì mọi thứ đều bị giới hạn bởi nó). Và đệ quy thường cần nhiều bộ nhớ hơn lặp lại.
Đặc vụ_L

@Agent_L Có, đệ quy cần bộ nhớ không giới hạn, để lưu trữ tất cả các khung ngăn xếp. Với cách tiếp cận đệ quy, có một yêu cầu về bộ nhớ không giới hạn, nhưng nó không thể tách rời bằng trực giác với trình tự các thao tác như với các lần lặp.
Gilles 'SO- ngừng trở nên xấu xa'

1
Có lẽ quan tâm sẽ là luận án Church-Turing. Máy Turing là máy lặp có tính lặp lại cao (không có khái niệm đệ quy). Phép tính Lambda là một ngôn ngữ được tạo ra để diễn đạt các phép tính theo cách đệ quy. Luận án Church-Turing đã chỉ ra rằng tất cả các biểu thức lambda có thể được đánh giá trên máy Turing, liên kết đệ quy và lặp lại.
Cort Ammon

1
@BlackV ăn được Nếu một phương thức đệ quy không có biến nội bộ và bộ nhớ duy nhất mà nó sử dụng là ngăn xếp cuộc gọi (TCO có thể được tối ưu hóa), thì phiên bản lặp đó rất có thể sẽ không có biến nội bộ và chỉ sử dụng lượng bộ nhớ không đổi ( các biến) được chia sẻ trên tất cả các lần lặp, như bộ đếm.
Đặc vụ_L

33

Mọi đệ quy có thể được chuyển đổi thành phép lặp, như được chứng kiến ​​bởi CPU của bạn, thực thi các chương trình tùy ý bằng cách sử dụng phép lặp vô hạn thực hiện tìm nạp. Đây là một dạng của định lý Böhm-Jacopini . Hơn nữa, nhiều mô hình tính toán hoàn chỉnh Turing không có đệ quy, ví dụ máy Turingmáy đếm .

Các hàm đệ quy nguyên thủy tương ứng với các chương trình sử dụng phép lặp có giới hạn , nghĩa là bạn phải chỉ định số lần lặp mà vòng lặp được thực hiện trước. Lặp đi lặp lại không thể mô phỏng đệ quy nói chung, vì hàm Ackermann không phải là đệ quy nguyên thủy. Nhưng phép lặp không giới hạn có thể mô phỏng bất kỳ chức năng tính toán một phần nào.


25

a(n,m)

s

push(s,x)xxpop(s)empty(s)

Ackermann(n0,m0):

  • s= (khởi tạo ngăn xếp trống)
  • push(s,n0)
  • push(s,m0)
  • while(true):
    • mpop(s)
    • if(empty(s)):
      • return m (điều này kết thúc vòng lặp)
    • npop(s)
    • if(n=0):
      • push(s,m+1)
    • else if(m=0):
      • push(s,n1)
      • push(s,1)
    • else:
      • push(s,n1)
      • push(s,n)
      • push(s,m1)

Tôi đã thực hiện điều này trong Ceylon, bạn có thể chạy nó trong WebIDE , nếu bạn muốn. (Nó xuất ra ngăn xếp khi bắt đầu mỗi lần lặp của vòng lặp.)

Tất nhiên, điều này chỉ chuyển ngăn xếp cuộc gọi ngầm từ đệ quy thành ngăn xếp rõ ràng với các tham số và giá trị trả về.


15
Tôi nghĩ rằng đây là một điểm quan trọng. Bạn đã thể hiện khá rõ ràng rằng đệ quy không có gì đặc biệt và nó có thể được loại bỏ chỉ bằng cách tự theo dõi ngăn xếp cuộc gọi, thay vì để trình biên dịch thực hiện. Recursion chỉ là một tính năng biên dịch giúp cho việc viết loại chương trình này dễ dàng hơn.
David Richerby

4
Cảm ơn đã dành nỗ lực để viết chương trình yêu cầu.
Tobi Alafin

16

Đã có một số câu trả lời tuyệt vời (mà tôi thậm chí không thể hy vọng cạnh tranh được), nhưng tôi muốn đưa ra lời giải thích đơn giản này.

Đệ quy chỉ là thao tác của ngăn xếp thời gian chạy. Đệ quy thêm một khung ngăn xếp mới (cho lệnh gọi mới của hàm đệ quy) và trả về sẽ loại bỏ khung ngăn xếp (đối với sự đổi mới vừa hoàn thành của hàm đệ quy). Đệ quy sẽ làm cho một số số khung ngăn xếp được thêm / xóa, cho đến khi tất cả chúng thoát ra (hy vọng!) Và kết quả được trả về cho người gọi.

Bây giờ, điều gì sẽ xảy ra nếu bạn tạo ngăn xếp của riêng mình, thay thế các lệnh gọi hàm đệ quy bằng cách đẩy vào ngăn xếp, thay thế trả về từ các hàm đệ quy bằng cách bật ngăn xếp và có một vòng lặp while chạy cho đến khi ngăn xếp trống?


2

Theo như tôi có thể nói, và theo kinh nghiệm của riêng tôi, bạn có thể thực hiện bất kỳ đệ quy nào dưới dạng lặp. Như đã đề cập ở trên, đệ quy sử dụng ngăn xếp, về mặt khái niệm là không giới hạn, nhưng thực tế bị giới hạn (bạn đã bao giờ nhận được thông báo tràn ngăn xếp chưa?). Trong những ngày đầu lập trình (vào quý ba của thế kỷ trước của thiên niên kỷ trước) tôi đã sử dụng các ngôn ngữ không đệ quy thực hiện các thuật toán đệ quy và không gặp vấn đề gì. Tôi không chắc chắn làm thế nào một người sẽ chứng minh điều đó, mặc dù.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.