Loại bỏ đệ quy - một cái nhìn vào lý thuyết đằng sau hậu trường


10

Tôi mới đến trang web này và câu hỏi này chắc chắn không phải là cấp độ nghiên cứu - nhưng tốt. Tôi có một chút nền tảng về công nghệ phần mềm và hầu như không có gì ở CSTheory, nhưng tôi thấy nó hấp dẫn. Để làm cho một câu chuyện dài ngắn, tôi muốn có một câu trả lời chi tiết hơn cho câu hỏi sau nếu câu hỏi này được chấp nhận trên trang web này.

Vì vậy, tôi biết rằng mọi chương trình đệ quy đều có một phép lặp tương tự và tôi hiểu được cách giải thích phổ biến được cung cấp cho nó bằng cách duy trì một cái gì đó tương tự như "ngăn xếp hệ thống" và đẩy các cài đặt môi trường như địa chỉ trả về, v.v. .

Cụ thể hơn một chút, tôi muốn (chính thức) xem làm thế nào để chứng minh tuyên bố này trong trường hợp bạn có chức năng gọi chuỗi . Hơn nữa, những gì nếu có một số báo cáo có điều kiện mà có thể dẫn một làm cho một cuộc gọi đến một số ? Đó là, biểu đồ gọi hàm tiềm năng có một số thành phần được kết nối mạnh mẽ.F iF0F1FiFi+1FnF0FiFj

Tôi muốn biết làm thế nào những tình huống này có thể được xử lý bằng cách cho chúng tôi nói một số đệ quy để chuyển đổi lặp. Và là mô tả bằng tay tôi đã đề cập trước đó, thực sự đủ cho vấn đề này? Ý tôi là tại sao tôi lại thấy việc loại bỏ đệ quy trong một số trường hợp dễ dàng. Đặc biệt loại bỏ đệ quy khỏi giao dịch đặt hàng trước của cây nhị phân thực sự dễ dàng - đó là một câu hỏi phỏng vấn tiêu chuẩn nhưng loại bỏ đệ quy trong trường hợp đặt hàng bài luôn là một cơn ác mộng đối với tôi.

Điều tôi thực sự hỏi là câu hỏi2

(1) Có thực sự có một bằng chứng chính thức (thuyết phục hơn không?) Rằng đệ quy có thể được chuyển đổi thành phép lặp?

(2) Nếu lý thuyết này thực sự nằm ngoài đó, thì tại sao tôi lại tìm thấy nó, ví dụ, lặp đi lặp lại việc đặt hàng trước dễ dàng hơn và đặt hàng rất khó? (khác với trí thông minh hạn chế của tôi)


1
như từ lặp đi lặp lại :)
Akash Kumar

Tôi không chắc chắn nếu tôi hoàn toàn hiểu, nhưng nếu đệ quy kết thúc ở đâu đó thì bạn thực sự có thể mô phỏng một ngăn xếp hệ thống bằng cách sử dụng ngăn xếp của riêng bạn. Đối với phần (2), các vấn đề không khác nhau về độ phức tạp tính toán.
singhsumit

Tôi nghĩ rằng câu hỏi này sẽ phù hợp nhất cho trang web Khoa học Máy tính chưa có. Đối với câu hỏi thứ hai của bạn, bạn có thể giải thích lý do tại sao bạn nghĩ nó khó hơn không? Quá trình nên gần như giống hệt nhau.
Raphael

cảm ơn mọi người vì ý kiến ​​của bạn - đoán tôi đã có khá nhiều việc phải đọc.
Uchiha Itachi

@Raphael - Một nhận xét về lý do tại sao tôi nghĩ rằng việc gửi bài lặp đi lặp lại là khó khăn (ngoài ra tôi không thể làm điều đó). Tôi đã đọc một vài bài viết về loại bỏ đệ quy và chạy vào một thứ gọi là hàm đệ quy đuôi. Hóa ra họ dễ lặp lại hơn. Tôi vẫn không chính thức hiểu tại sao điều này là đúng; nhưng có một điều tôi nên thêm vào. Tôi đã nghe nói rằng bưu điện lặp yêu cầu hai ngăn xếp chứ không phải một nhưng không biết chi tiết. Và bây giờ tôi bị lạc - tại sao sự khác biệt giữa hai chế độ truyền tải này? Và tại sao đệ quy đuôi dễ xử lý?
Uchiha Itachi

Câu trả lời:


6

Nếu tôi hiểu chính xác, bạn rõ ràng về việc chuyển đổi các hàm không chứa các lệnh gọi hàm nào khác ngoài chính chúng.

Vì vậy, giả sử chúng ta có một "chuỗi gọi là" . Nếu chúng ta hơn nữa cho rằng không đệ quy mình (bởi vì chúng tôi đã chuyển đổi chúng đã được), chúng ta có thể nội tuyến tất cả những cuộc gọi vào định nghĩa của mà thusly trở thành một chức năng trực tiếp đệ quy, chúng tôi đã có thể đối phó với.F 1 , ... , F n FFF1FnFF1,,FnF

Điều này không thành công nếu một số có chuỗi cuộc gọi đệ quy trong đó xảy ra, tức là . Trong trường hợp này, chúng tôi có đệ quy lẫn nhau đòi hỏi một thủ thuật khác để thoát khỏi. Ý tưởng là tính toán đồng thời cả hai chức năng. Ví dụ, trong trường hợp tầm thường: F F jF F jFjFFjFFj

f(0) = a
f(n) = f'(g(n-1))

g(0) = b
g(n) = g'(f(n-1))

với f'g'các hàm không đệ quy (hoặc ít nhất là độc lập với fg) trở thành

h(0) = (a,b)
h(n) = let (f,g) = h(n-1) in (f'(g), g'(f)) end

f(n) = let (f, _) = h(n) in f end
g(n) = let (_, g) = h(n) in g end

Điều này tự nhiên mở rộng đến nhiều chức năng liên quan và chức năng phức tạp hơn.


Rất vui vì tôi có thể giúp. Hãy nhớ chấp nhận câu trả lời yêu thích của bạn bằng cách nhấp vào dấu chọn bên cạnh nó.
Raphael

1
Raprc, thủ thuật của bạn chỉ hoạt động khi cả hai hàm đệ quy chấp nhận các đối số cùng loại. Nếu fgchấp nhận các loại khác nhau, một thủ thuật tổng quát hơn là cần thiết.
Andrej Bauer

@AndrejBauer quan sát tốt, tôi hoàn toàn bỏ lỡ điều đó. Tôi thực sự thích cách tiếp cận của raphael, nhưng giống như bạn quan sát trong các trường hợp chung, có lẽ chúng ta cần một số ý tưởng khác nhau. Bạn có thể đưa ra bất kỳ đề nghị khác?
Uchiha Itachi

@AndrejBauer Đúng. Tôi đã suy nghĩ từ một quan điểm lý thuyết đệ quy; ở đây chúng ta chỉ có số tự nhiên. Điều này là đủ bởi vì chúng ta có thể mã hóa mọi thứ theo cách phù hợp. Nhưng quan điểm của bạn là rất hợp lệ cho thực hành. Tôi đoán chúng ta sẽ phải viết lại fgcho đến khi họ chia sẻ sơ đồ mã hóa đệ quy đầu vào chung (chúng ta không thể có một cái bằng cách sử dụng và ). n - 2n1n2
Raphael

Vâng, xem câu trả lời của tôi về cách làm điều đó.
Andrej Bauer

8

Vâng, có những lý do thuyết phục để tin rằng đệ quy có thể được chuyển thành lặp đi lặp lại. Đây là những gì mọi trình biên dịch làm khi dịch mã nguồn sang ngôn ngữ máy. Đối với lý thuyết, bạn nên làm theo đề xuất của Dave Clarke. Nếu bạn muốn xem mã thực tế chuyển đổi đệ quy thành mã không đệ quy, hãy xem machine.mlngôn ngữ MiniML trong Sở thú PL của tôi (lưu ý rằng loophàm ở phía dưới, thực sự chạy mã, là đệ quy đuôi và vì vậy nó có thể được chuyển đổi tầm thường thành một vòng lặp thực tế).

Một điều nữa. MiniML không hỗ trợ các chức năng đệ quy lẫn nhau. Nhưng đây không phải là một vấn đề. Nếu bạn có đệ quy lẫn nhau giữa các chức năng

f 2 : Một 2B 2f n : Một nB n

f1:A1B1
f2:A2B2
fn:AnBn

đệ quy có thể được thể hiện dưới dạng bản đồ đệ quy đơn

f:A1++AnB1++Bn,

8

Bạn có thể muốn nhìn vào máy SECD . Một ngôn ngữ chức năng (mặc dù có thể là bất kỳ ngôn ngữ nào) được dịch thành một chuỗi các hướng dẫn quản lý những thứ như đặt các đối số của ngăn xếp, "gọi" các hàm mới, v.v., tất cả được quản lý bởi một vòng lặp đơn giản.
Các cuộc gọi đệ quy không bao giờ thực sự được gọi. Thay vào đó, các hướng dẫn của phần thân của hàm được gọi được đặt trên ngăn xếp để chạy.

Một cách tiếp cận liên quan là máy CEK .

Cả hai đã có từ lâu, vì vậy có rất nhiều công việc trên đó. Và tất nhiên, có bằng chứng cho thấy chúng hoạt động và quy trình "biên dịch" chương trình thành các hướng dẫn của SECD là tuyến tính theo kích thước của chương trình (không cần phải nghĩ về chương trình).

Quan điểm của câu trả lời của tôi là có một quy trình tự động để làm những gì bạn muốn. Thật không may, việc chuyển đổi sẽ không nhất thiết là về những điều mà lập trình viên dễ dàng giải thích ngay lập tức. Tôi nghĩ điều quan trọng là khi bạn muốn lặp lại một chương trình, bạn cần lưu trữ trên ngăn xếp những gì chương trình cần làm khi bạn quay lại từ một lệnh gọi hàm lặp (điều này được gọi là tiếp tục). Đối với một số chức năng (chẳng hạn như các hàm đệ quy đuôi), việc tiếp tục là không đáng kể. Đối với những người khác, việc tiếp tục có thể rất phức tạp, đặc biệt nếu bạn phải tự mã hóa nó.


tôi sẽ thành thật ở đây Tôi thực sự muốn hiểu tại sao (và làm thế nào) bạn có thể lặp lại mọi chương trình đệ quy. Nhưng tôi thấy thật khó khăn khi đọc qua một tờ giấy - chúng thường không thể truy cập được đối với tôi. ý tôi là tôi muốn một lý do sâu xa hơn mô tả "handw lượn sóng" mà tôi đã nói trong câu hỏi. nhưng tôi cũng hài lòng với một cái gì đó mang lại cho tôi một cái nhìn sâu sắc mới - nó không phải là toàn bộ bằng chứng trong các chi tiết nghiệt ngã của nó
Itachi Uchiha

[cntd] Ý tôi là tôi sẽ thích bằng chứng, nếu có một cái, để cho tôi biết tại sao việc lặp lại một chương trình dễ dàng hơn chương trình kia. Nhưng theo một cách nào đó, bộ chuyển đổi đệ quy thành công cụ lặp sẽ hoạt động bất kể chương trình đệ quy nào được sử dụng làm đầu vào. Nt chắc chắn, nhưng tôi đoán làm cho một công cụ chuyển đổi như vậy có thể khó khăn như vấn đề tạm dừng? Tôi chỉ đoán ở đây - nhưng tôi sẽ thích một trình chuyển đổi đệ quy để lặp và tồn tại nếu tôi muốn nó giải thích sự phức tạp vốn có của việc lặp lại các chương trình đệ quy khác nhau. không chắc chắn, nhưng tôi có nên chỉnh sửa câu hỏi không? Câu hỏi của tôi có rõ ràng không?
Uchiha Itachi

@ItachiUchiha - Tôi không nghĩ rằng vấn đề của bạn là không thể giải quyết được. Nhìn vào câu trả lời của Andrej Bauer. Ông lưu ý rằng mọi trình biên dịch sẽ làm điều đó khi nó dịch mã nguồn sang ngôn ngữ máy. Ngoài ra, ông cho biết thêm bạn có thể thấy mã thực tế chuyển đổi đệ quy thành không đệ quy trong ngôn ngữ MiniM (a) l. Điều này chỉ ra rõ ràng rằng có một thủ tục quyết định để "lặp lại" đệ quy. Tôi không chắc chắn về khó khăn / phức tạp vốn có (khái niệm) vốn có của việc loại bỏ đệ quy. Tôi không hiểu câu hỏi này rất rõ ràng nhưng có vẻ thú vị. Có lẽ bạn có thể chỉnh sửa câu hỏi của mình để nhận được câu trả lời tốt hơn
Akash Kumar

Quan điểm của câu trả lời của tôi là có một quy trình tự động để làm những gì bạn muốn. Thật không may, việc chuyển đổi sẽ không nhất thiết là về những điều mà lập trình viên dễ dàng giải thích ngay lập tức. Tôi nghĩ điều quan trọng là khi bạn muốn lặp lại một chương trình, bạn cần lưu trữ trên ngăn xếp những gì chương trình cần làm khi bạn quay lại từ một lệnh gọi hàm lặp (điều này được gọi là tiếp tục). Đối với một số chức năng (chẳng hạn như các hàm đệ quy đuôi), việc tiếp tục là không đáng kể. Đối với những người khác, việc tiếp tục có thể rất phức tạp, đặc biệt nếu bạn phải tự mã hóa nó.
Dave Clarke

6

Q : "Có thực sự có một bằng chứng chính thức (thuyết phục hơn không?) Rằng đệ quy có thể được chuyển đổi thành phép lặp?"

A : Tính đầy đủ của Turing của Máy Turing :-)

Nói đùa, mô hình máy chương trình lưu trữ truy cập ngẫu nhiên (RASP) tương đương Turing gần với cách thức hoạt động của bộ vi xử lý thực và tập lệnh của nó chỉ chứa một bước nhảy có điều kiện (không có đệ quy). Khả năng tự sửa đổi mã tự động làm cho nhiệm vụ thực hiện chương trình con và các cuộc gọi đệ quy dễ dàng hơn.

Tôi nghĩ rằng bạn có thể tìm thấy nhiều bài báo / bài viết về " chuyển đổi đệ quy " (xem câu trả lời của Dave hoặc chỉ Google từ khóa), nhưng có lẽ cách tiếp cận ít được biết đến (và thực tế ) là nghiên cứu mới nhất về triển khai phần cứng của thuật toán đệ quy ( sử dụng ngôn ngữ VHDL được "biên dịch" trực tiếp thành một phần cứng). Ví dụ, xem bài viết " Thực hiện thuật toán đệ quy dựa trên nền tảng " của V.Sklyarov ( Bài báo cho thấy một phương pháp mới để thực hiện các thuật toán đệ quy trong phần cứng. .... Hai ứng dụng thực tế của thuật toán đệ quy trong vùng sắp xếp và nén dữ liệu đã được nghiên cứu chi tiết .... ).


1

Nếu bạn quen thuộc với các ngôn ngữ hỗ trợ lambdas thì một con đường là xem xét chuyển đổi CPS. Loại bỏ việc sử dụng ngăn xếp cuộc gọi (và đặc biệt là đệ quy) là chính xác những gì chuyển đổi CPS thực hiện. Nó biến đổi một chương trình chứa các lệnh gọi thủ tục thành một chương trình chỉ có các lệnh gọi đuôi (bạn có thể nghĩ chúng là các gotos, là một cấu trúc lặp).

Chuyển đổi CPS có liên quan chặt chẽ đến việc giữ rõ ràng một ngăn xếp cuộc gọi trong ngăn xếp dựa trên mảng truyền thống, nhưng thay vì trong một mảng, ngăn xếp cuộc gọi được biểu diễn bằng các bao đóng được liên kết.


0

theo ý kiến ​​của tôi, câu hỏi này quay trở lại nguồn gốc của các định nghĩa tính toán và từ lâu đã được chứng minh nghiêm ngặt vào khoảng thời gian đó khi tính toán lambda của nhà thờ (vốn rất nắm bắt khái niệm đệ quy) được chứng minh là tương đương với máy Turing, và được chứa trong thuật ngữ vẫn được sử dụng "ngôn ngữ / hàm đệ quy". cũng có vẻ như một ref chính sau này dọc theo các dòng này như sau

Như được chỉ ra bởi bài viết năm 1969 của Peter Landin Một sự tương ứng giữa ALGOL 60 và ký hiệu Lambda của Church, các ngôn ngữ lập trình thủ tục tuần tự có thể được hiểu theo thuật toán lambda, cung cấp các cơ chế cơ bản cho ứng dụng trừu tượng hóa và thủ tục (chương trình con).

phần lớn bkd về điều này là trong trang wikipedia luận án nhà thờ này . không chắc chắn về các chi tiết cụ thể chính xác nhưng bài báo trên wikipedia dường như chỉ ra rằng đó là Rosser (1939), người đầu tiên đã chứng minh sự tương đương này giữa máy tính lambda và máy turing. có lẽ / có lẽ giấy của anh ta có một cơ chế giống như chồng để chuyển đổi các cuộc gọi lambda (có thể đệ quy) sang việc xây dựng tm?

Rosser, JB (1939). "Một triển lãm không chính thức về bằng chứng của Định lý Godel và Định lý của Giáo hội". Tạp chí Logic tượng trưng (Tạp chí Logic tượng trưng, ​​Tập 4, Số 2) 4 (2): 53 Điên60. doi: 10.2307 / 2269059. JSTOR 2269059.

lưu ý tất nhiên cho bất cứ ai quan tâm đến các nguyên tắc hiện đại ngôn ngữ Lisp và các biến thể Đề án cố ý có một sự tương đồng mạnh đến giải tích lambda. nghiên cứu mã trình thông dịch để đánh giá biểu thức dẫn đến các ý tưởng ban đầu được chứa trong các bài báo về tính hoàn chỉnh của tính toán lambda.


1
Bằng chứng tương đương Turing / lambda có trong phần phụ lục của bài viết này: www.cs.virginia.edu/~robins/Turing_Paper_1936.pdf
Radu GRIGore
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.