Mỗi đệ quy có thể được chuyển đổi thành lặp không?


181

Một chủ đề reddit đưa ra một câu hỏi rõ ràng thú vị:

Các hàm đệ quy đuôi có thể được chuyển đổi thành các hàm lặp. Những cái khác, có thể được chuyển đổi bằng cách sử dụng một ngăn xếp rõ ràng. Có thể mỗi đệ quy được chuyển đổi thành lặp?

Ví dụ (bộ đếm?) Trong bài viết là cặp:

(define (num-ways x y)
  (case ((= x 0) 1)
        ((= y 0) 1)
        (num-ways2 x y) ))

(define (num-ways2 x y)
  (+ (num-ways (- x 1) y)
     (num-ways x (- y 1))

3
Tôi không thấy đây là một ví dụ ngược lại. Kỹ thuật ngăn xếp sẽ hoạt động. Nó sẽ không đẹp, và tôi sẽ không viết nó, nhưng điều đó là có thể. Nó xuất hiện akdas thừa nhận rằng trong liên kết của bạn.
Matthew Flaschen

(Num-cách xy) của bạn chỉ là (x + y) choosex = (x + y)! / (X! Y!), Không cần đệ quy.
ShreevatsaR


Tôi muốn nói rằng đệ quy chỉ là một sự tiện lợi.
e2-e4

Câu trả lời:


181

Bạn có thể luôn luôn biến một hàm đệ quy thành một hàm lặp không? Vâng, hoàn toàn, và luận điểm Church-Turing chứng minh điều đó nếu bộ nhớ phục vụ. Nói một cách dễ hiểu, nó nói rằng những gì có thể tính toán được bằng các hàm đệ quy có thể tính toán được bằng một mô hình lặp (như máy Turing) và ngược lại. Luận án không cho bạn biết chính xác cách thực hiện chuyển đổi, nhưng nó nói rằng nó chắc chắn có thể.

Trong nhiều trường hợp, chuyển đổi một hàm đệ quy là dễ dàng. Knuth cung cấp một số kỹ thuật trong "Nghệ thuật lập trình máy tính". Và thông thường, một thứ được tính toán đệ quy có thể được tính bằng một cách tiếp cận hoàn toàn khác trong ít thời gian và không gian hơn. Ví dụ kinh điển về điều này là các số Fibonacci hoặc các chuỗi của chúng. Bạn chắc chắn đã gặp vấn đề này trong kế hoạch bằng cấp của bạn.

Mặt trái của đồng tiền này, chúng ta chắc chắn có thể tưởng tượng một hệ thống lập trình tiên tiến đến mức coi định nghĩa đệ quy của công thức như một lời mời để ghi nhớ các kết quả trước đó, do đó mang lại lợi ích tốc độ mà không gặp rắc rối khi nói với máy tính chính xác các bước theo dõi tính toán của một công thức với định nghĩa đệ quy. Dijkstra gần như chắc chắn đã tưởng tượng một hệ thống như vậy. Ông đã dành một thời gian dài để cố gắng tách việc thực hiện khỏi ngữ nghĩa của một ngôn ngữ lập trình. Sau đó, một lần nữa, ngôn ngữ lập trình không xác định và đa xử lý của anh ta nằm trong một giải đấu trên các lập trình viên chuyên nghiệp thực hành.

Trong phân tích cuối cùng, nhiều hàm chỉ đơn giản là dễ hiểu, đọc và viết ở dạng đệ quy. Trừ khi có một lý do thuyết phục, có lẽ bạn không nên (thủ công) chuyển đổi các hàm này thành một thuật toán lặp rõ ràng. Máy tính của bạn sẽ xử lý công việc đó một cách chính xác.

Tôi có thể thấy một lý do thuyết phục. Giả sử bạn có một hệ thống nguyên mẫu bằng ngôn ngữ siêu cao cấp như [ tặng đồ lót amiăng ] Scheme, Lisp, Haskell, OCaml, Perl hoặc Pascal. Giả sử các điều kiện sao cho bạn cần triển khai bằng C hoặc Java. (Có lẽ đó là chính trị.) Sau đó, bạn chắc chắn có thể có một số chức năng được viết đệ quy nhưng được dịch theo nghĩa đen sẽ làm nổ tung hệ thống thời gian chạy của bạn. Ví dụ, đệ quy đuôi vô hạn có thể có trong Lược đồ, nhưng thành ngữ tương tự gây ra vấn đề cho các môi trường C hiện có. Một ví dụ khác là việc sử dụng các hàm lồng nhau và phạm vi tĩnh, mà Pascal hỗ trợ nhưng C thì không.

Trong những trường hợp này, bạn có thể cố gắng vượt qua sự kháng cự chính trị với ngôn ngữ gốc. Bạn có thể thấy mình đang thực hiện lại Lisp một cách tồi tệ, như trong luật thứ mười (nói bằng lưỡi) của Greensasta. Hoặc bạn có thể tìm thấy một cách tiếp cận hoàn toàn khác với giải pháp. Nhưng trong bất kỳ sự kiện, chắc chắn có một cách.


10
Không phải Church-Turing chưa được chứng minh?
Liran Orevi

15
@eyelidlessness: Nếu bạn có thể thực hiện A trong B, điều đó có nghĩa là B có ít nhất sức mạnh bằng A. Nếu bạn không thể thực hiện một số tuyên bố của A trong A-exec-of-B, thì đó không phải là triển khai. Nếu A có thể được thực hiện trong B và B có thể được thực hiện trong A, power (A)> = power (B) và power (B)> = power (A). Giải pháp duy nhất là power (A) == power (B).
Tordek

6
re: Đoạn 1: Bạn đang nói về sự tương đương của các mô hình tính toán, chứ không phải luận án Church-Turing. Sự tương đương đã được AFAIR chứng minh bởi Church và / hoặc Turing, nhưng nó không phải là luận án. Luận án là một thực tế thực nghiệm rằng mọi thứ có thể tính toán bằng trực giác đều có thể tính toán được theo nghĩa toán học nghiêm ngặt (bằng máy Turing / các hàm đệ quy, v.v.). Nó có thể bị từ chối nếu sử dụng các định luật vật lý, chúng ta có thể xây dựng một số máy tính không phân lớp tính toán một cái gì đó mà máy Turing không thể làm được (ví dụ như dừng vấn đề). Trong khi đó, sự tương đương là một định lý toán học, và nó sẽ không bị từ chối.
sdcvvc

7
Làm thế quái nào câu trả lời này nhận được bất kỳ phiếu bầu tích cực? Đầu tiên, nó kết hợp tính hoàn chỉnh của Turing với luận điểm Church-Turing, sau đó nó tạo ra một loạt các thao tác không chính xác, đề cập đến các hệ thống "tiên tiến" và bỏ đệ quy đuôi vô hạn lười biếng (mà bạn có thể làm trong C hoặc bất kỳ ngôn ngữ hoàn chỉnh Turing nào vì .. uh. Có ai biết Turing hoàn thành nghĩa là gì không?). Sau đó, một kết luận nắm tay đầy hy vọng, như đây là một câu hỏi về Oprah và tất cả những gì bạn cần là phải tích cực và nâng cao tinh thần? Câu trả lời kinh khủng!
ex0du5

8
Và bs về ngữ nghĩa ??? Có thật không? Đây là một câu hỏi về chuyển đổi cú pháp, và bằng cách nào đó nó đã trở thành một cách tuyệt vời để đặt tên cho Dijkstra và ngụ ý bạn biết gì về phép tính pi? Hãy để tôi nói rõ điều này: liệu người ta nhìn vào ngữ nghĩa học của ngôn ngữ hay một mô hình nào khác sẽ không ảnh hưởng đến câu trả lời cho câu hỏi này. Cho dù ngôn ngữ là lắp ráp hay một ngôn ngữ mô hình hóa miền chung có nghĩa là không có gì. Nó chỉ nói về tính đầy đủ của Turing và chuyển đổi "biến stack" thành "stack stack".
ex0du5

43

Có phải luôn luôn có thể viết một hình thức không đệ quy cho mọi hàm đệ quy không?

Đúng. Một bằng chứng chính thức đơn giản là chỉ ra rằng cả đệ quy và một phép tính không đệ quy như GOTO đều hoàn thành Turing. Vì tất cả các phép tính hoàn chỉnh Turing đều tương đương hoàn toàn với sức mạnh biểu cảm của chúng, tất cả các hàm đệ quy có thể được thực hiện bằng phép tính Turing không hoàn thành đệ quy.

Thật không may, tôi không thể tìm thấy một định nghĩa chính thức, tốt về GOTO trực tuyến nên đây là một:

Chương trình GOTO là một chuỗi các lệnh P được thực thi trên máy đăng ký sao cho P là một trong những điều sau đây:

  • HALT, mà dừng thực thi
  • r = r + 1nơi rbất kỳ đăng ký
  • r = r – 1nơi rbất kỳ đăng ký
  • GOTO xnơi xlà một nhãn
  • IF r ≠ 0 GOTO xnơi rbất kỳ đăng ký và xlà một nhãn
  • Một nhãn, theo sau là bất kỳ lệnh nào ở trên.

Tuy nhiên, việc chuyển đổi giữa các hàm đệ quy và không đệ quy không phải lúc nào cũng tầm thường (ngoại trừ việc thực hiện lại thủ công ngăn xếp cuộc gọi).

Để biết thêm thông tin xem câu trả lời này .


Câu trả lời chính xác! Tuy nhiên, trong thực tế, tôi gặp khó khăn rất lớn trong việc điều chỉnh các thuật toán đệ quy thành các phép lặp. Ví dụ, cho đến nay tôi không thể biến ông trùm đơn hình được trình bày ở đây Community.topcoder.com/, thành một thuật toán lặp
Nils

31

Đệ quy được thực hiện như các ngăn xếp hoặc các cấu trúc tương tự trong các trình thông dịch hoặc trình biên dịch thực tế. Vì vậy, bạn chắc chắn có thể chuyển đổi một hàm đệ quy thành một đối tác lặp vì đó là cách nó luôn được thực hiện (nếu tự động) . Bạn sẽ chỉ sao chép công việc của nhà soạn nhạc theo cách đặc biệt và có thể theo cách rất xấu và không hiệu quả.


13

Về cơ bản là có, về bản chất, những gì bạn phải làm là thay thế các cuộc gọi phương thức (mà hoàn toàn đẩy trạng thái lên ngăn xếp) thành ngăn xếp rõ ràng để ghi nhớ nơi 'cuộc gọi trước' đã thực hiện, và sau đó thực hiện 'phương thức được gọi' thay thế.

Tôi tưởng tượng rằng sự kết hợp của một vòng lặp, một ngăn xếp và một máy trạng thái có thể được sử dụng cho tất cả các kịch bản bằng cách mô phỏng cơ bản các cuộc gọi phương thức. Cho dù điều này sẽ trở nên 'tốt hơn' (nhanh hơn hay hiệu quả hơn trong một số trường hợp) không thực sự có thể nói chung.


9
  • Luồng thực thi hàm đệ quy có thể được biểu diễn dưới dạng cây.

  • Logic tương tự có thể được thực hiện bởi một vòng lặp, sử dụng cấu trúc dữ liệu để duyệt qua cây đó.

  • Truyền tải chiều sâu đầu tiên có thể được thực hiện bằng cách sử dụng ngăn xếp, di chuyển ngang đầu tiên có thể được thực hiện bằng cách sử dụng hàng đợi.

Vì vậy, câu trả lời là có. Tại sao: https://stackoverflow.com/a/531721/2128327 .

Bất kỳ đệ quy có thể được thực hiện trong một vòng lặp duy nhất? Vâng, bởi vì

một máy Turing thực hiện mọi thứ nó làm bằng cách thực hiện một vòng lặp duy nhất:

  1. lấy một hướng dẫn,
  2. đánh giá nó
  3. goto 1.

7

Có, sử dụng rõ ràng một ngăn xếp (nhưng đệ quy dễ đọc hơn nhiều, IMHO).


17
Tôi sẽ không nói rằng nó luôn luôn dễ chịu hơn để đọc. Cả lặp và đệ quy đều có vị trí của chúng.
Matthew Flaschen

6

Có, luôn luôn có thể viết một phiên bản không đệ quy. Giải pháp tầm thường là sử dụng cấu trúc dữ liệu ngăn xếp và mô phỏng thực thi đệ quy.


Cái nào sẽ đánh bại mục đích nếu cấu trúc dữ liệu ngăn xếp của bạn được phân bổ trên ngăn xếp, hoặc mất nhiều thời gian hơn nếu nó được phân bổ trên heap, không? Điều đó nghe có vẻ tầm thường nhưng không hiệu quả với tôi.
conradkleinespel

1
@conradk Trong một số trường hợp, đó là điều thực tế cần làm nếu bạn phải thực hiện một số thao tác đệ quy cây trên một vấn đề đủ lớn để làm cạn kiệt ngăn xếp cuộc gọi; bộ nhớ heap thường phong phú hơn nhiều.
jamesdlin

4

Về nguyên tắc, luôn luôn có thể loại bỏ đệ quy và thay thế nó bằng phép lặp trong một ngôn ngữ có trạng thái vô hạn cả cho cấu trúc dữ liệu và cho ngăn xếp cuộc gọi. Đây là một kết quả cơ bản của luận án Church-Turing.

Cho một ngôn ngữ lập trình thực tế, câu trả lời là không rõ ràng. Vấn đề là hoàn toàn có thể có một ngôn ngữ trong đó số lượng bộ nhớ có thể được phân bổ trong chương trình bị giới hạn nhưng trong đó số lượng ngăn xếp cuộc gọi có thể được sử dụng là không giới hạn (32 bit C trong đó địa chỉ của các biến ngăn xếp không truy cập được). Trong trường hợp này, đệ quy mạnh hơn đơn giản vì nó có nhiều bộ nhớ hơn mà nó có thể sử dụng; không có đủ bộ nhớ phân bổ rõ ràng để mô phỏng ngăn xếp cuộc gọi. Đối với một cuộc thảo luận chi tiết về điều này, xem cuộc thảo luận này .


2

Tất cả các chức năng tính toán có thể được tính toán bởi Turing Machines và do đó các hệ thống đệ quy và máy Turing (hệ thống lặp) là tương đương.


1

Đôi khi thay thế đệ quy dễ dàng hơn nhiều. Đệ quy từng là điều thời thượng được dạy trong CS vào những năm 1990, và vì vậy, rất nhiều nhà phát triển trung bình từ thời đó đã nhận ra rằng nếu bạn giải quyết được điều gì đó bằng đệ quy thì đó là một giải pháp tốt hơn. Vì vậy, họ sẽ sử dụng đệ quy thay vì lặp ngược lại để đảo ngược thứ tự, hoặc những thứ ngớ ngẩn như thế. Vì vậy, đôi khi loại bỏ đệ quy là một loại bài tập "duh, điều đó rõ ràng".

Đây không phải là một vấn đề bây giờ, vì thời trang đã chuyển sang các công nghệ khác.



0

Hãy từ ngăn xếp rõ ràng, một mô hình khác để chuyển đổi đệ quy thành phép lặp là sử dụng tấm bạt lò xo.

Ở đây, các hàm hoặc trả về kết quả cuối cùng hoặc đóng lệnh gọi hàm mà nó sẽ thực hiện. Sau đó, hàm khởi tạo (trampolining) tiếp tục gọi các bao đóng được trả về cho đến khi đạt được kết quả cuối cùng.

Cách tiếp cận này hoạt động cho các chức năng đệ quy lẫn nhau, nhưng tôi e rằng nó chỉ hoạt động cho các cuộc gọi đuôi.

http://en.wikipedia.org/wiki/Trampoline_(computing)


0

Tôi muốn nói có - một lệnh gọi hàm không là gì ngoài thao tác goto và stack (nói đại khái). Tất cả những gì bạn cần làm là bắt chước ngăn xếp được xây dựng trong khi gọi các hàm và làm một cái gì đó tương tự như một goto (bạn có thể bắt chước gotos với các ngôn ngữ không có từ khóa này rõ ràng).


1
Tôi nghĩ rằng OP đang tìm kiếm một bằng chứng hoặc một cái gì đó thực chất
Tim

0

Hãy xem các mục sau trên wikipedia, bạn có thể sử dụng chúng làm điểm bắt đầu để tìm câu trả lời hoàn chỉnh cho câu hỏi của mình.

Theo một đoạn có thể cung cấp cho bạn một số gợi ý về nơi bắt đầu:

Giải một mối quan hệ lặp lại có nghĩa là có được một giải pháp dạng đóng : một hàm không đệ quy của n.

Cũng có một cái nhìn vào đoạn cuối của mục này .



-1

tazzego, đệ quy có nghĩa là một hàm sẽ tự gọi mình dù bạn có thích hay không. Khi mọi người đang nói về việc liệu mọi thứ có thể được thực hiện mà không cần đệ quy hay không, họ có nghĩa là điều này và bạn không thể nói "không, điều đó không đúng, vì tôi không đồng ý với định nghĩa đệ quy" là một tuyên bố hợp lệ.

Với ý nghĩ đó, tất cả những gì bạn nói là vô nghĩa. Điều khác duy nhất mà bạn nói không vô nghĩa là ý tưởng mà bạn không thể tưởng tượng được lập trình mà không có một cuộc gọi. Đó là điều đã được thực hiện trong nhiều thập kỷ cho đến khi sử dụng một cách gọi trở nên phổ biến. Các phiên bản cũ của FORTRAN thiếu một callstack và chúng hoạt động tốt.

Nhân tiện, tồn tại các ngôn ngữ hoàn chỉnh Turing chỉ thực hiện đệ quy (ví dụ SML) như một phương tiện lặp. Ngoài ra còn tồn tại các ngôn ngữ hoàn chỉnh Turing chỉ thực hiện phép lặp như một phương tiện lặp (ví dụ FORTRAN IV). Luận án Church-Turing chứng minh rằng bất kỳ điều gì có thể trong một ngôn ngữ chỉ đệ quy đều có thể được thực hiện bằng ngôn ngữ không đệ quy và vica-Versa bởi thực tế là cả hai đều có đặc tính của sự hoàn thiện.


-3

Đây là một thuật toán lặp:

def howmany(x,y)
  a = {}
  for n in (0..x+y)
    for m in (0..n)
      a[[m,n-m]] = if m==0 or n-m==0 then 1 else a[[m-1,n-m]] + a[[m,n-m-1]] end
    end
  end
  return a[[x,y]]
end
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.