การเรียกซ้ำทุกครั้งสามารถเปลี่ยนเป็นการทำซ้ำได้หรือไม่


181

ด้าย Redditนำขึ้นคำถามที่น่าสนใจเห็นได้ชัดว่า:

ฟังก์ชันเรียกซ้ำแบบหางสามารถแปลงเป็นฟังก์ชั่นวนซ้ำได้เล็กน้อย ส่วนอื่นสามารถเปลี่ยนได้โดยใช้สแต็กที่ชัดเจน การเรียกซ้ำทุกครั้งสามารถเปลี่ยนเป็นการวนซ้ำได้หรือไม่

ตัวอย่าง (ตัวนับ?) ในโพสต์คือคู่:

(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
ฉันไม่เห็นว่านี่เป็นตัวอย่างที่เคาน์เตอร์ได้อย่างไร เทคนิคสแต็กจะทำงาน มันจะไม่สวยและฉันจะไม่เขียนมัน แต่ทำได้ ดูเหมือนว่า akdas ยอมรับว่าในลิงก์ของคุณ
Matthew Flaschen

(num-ways xy) ของคุณคือ (x + y) choosex = (x + y)! / (x! y!) ซึ่งไม่ต้องการการเรียกซ้ำ
ShreevatsaR

3
รายการซ้ำซ้อนของ: stackoverflow.com/questions/531668
Henk Holterman

ฉันจะบอกว่าการเรียกซ้ำเป็นเพียงความสะดวกสบาย
e2-e4

คำตอบ:


181

คุณสามารถเปลี่ยนฟังก์ชั่นวนซ้ำเป็นฟังก์ชันซ้ำได้หรือไม่? ใช่แน่นอนและการทำวิทยานิพนธ์ของทัวริสต์ทัวริสพิสูจน์ได้ถ้าความจำทำหน้าที่ ในแง่ของการวางมันระบุว่าสิ่งที่คำนวณได้โดยฟังก์ชั่นซ้ำคือคำนวณโดยแบบจำลองซ้ำ (เช่นเครื่องทัวริง) และในทางกลับกัน วิทยานิพนธ์ไม่ได้บอกคุณอย่างชัดเจนถึงวิธีการแปลง แต่ก็บอกว่าเป็นไปได้แน่นอน

ในหลายกรณีการแปลงฟังก์ชั่นวนซ้ำเป็นเรื่องง่าย Knuth เสนอเทคนิคต่าง ๆ ใน "ศิลปะแห่งการเขียนโปรแกรมคอมพิวเตอร์" และบ่อยครั้งที่สิ่งที่คำนวณซ้ำสามารถคำนวณได้ด้วยวิธีการที่แตกต่างกันโดยสิ้นเชิงในเวลาและพื้นที่ที่น้อยลง ตัวอย่างคลาสสิกของสิ่งนี้คือหมายเลขฟีโบนักชีหรือลำดับของมัน คุณได้พบปัญหานี้อย่างแน่นอนในแผนปริญญาของคุณ

ในอีกด้านหนึ่งของเหรียญนี้เราสามารถจินตนาการถึงระบบการเขียนโปรแกรมขั้นสูงเพื่อรักษานิยามการเรียกซ้ำของสูตรเพื่อเชิญชวนให้บันทึกผลลัพธ์ก่อนหน้าดังนั้นจึงให้ประโยชน์ด้านความเร็วโดยไม่ต้องวุ่นวายกับการบอกคอมพิวเตอร์ว่าขั้นตอนใด ติดตามการคำนวณสูตรด้วยคำจำกัดความแบบเรียกซ้ำ Dijkstra เกือบจะจินตนาการระบบดังกล่าว เขาใช้เวลานานในการพยายามแยกการใช้งานจากความหมายของภาษาโปรแกรม จากนั้นอีกครั้งภาษาการเขียนโปรแกรมแบบไม่ จำกัด และหลายตัวประมวลผลของเขาอยู่ในลีกเหนือโปรแกรมเมอร์มืออาชีพฝึก

ในการวิเคราะห์ขั้นสุดท้ายฟังก์ชั่นจำนวนมากนั้นง่ายต่อการเข้าใจอ่านและเขียนในแบบเรียกซ้ำ หากไม่มีเหตุผลที่น่าสนใจคุณอาจไม่ควรแปลงฟังก์ชันเหล่านี้ด้วยตนเองเป็นอัลกอริทึมการวนซ้ำอย่างชัดเจน คอมพิวเตอร์ของคุณจะจัดการกับงานนั้นอย่างถูกต้อง

ฉันเห็นเหตุผลที่น่าสนใจหนึ่งข้อ สมมติว่าคุณมีระบบต้นแบบในภาษาระดับสูงพิเศษเช่น [ donning asbestos underwear ] Scheme, Lisp, Haskell, OCaml, Perl หรือ Pascal สมมติว่าเงื่อนไขเป็นเช่นนั้นคุณต้องมีการนำไปใช้ใน C หรือ Java (อาจเป็นเรื่องการเมือง) จากนั้นคุณอาจมีฟังก์ชั่นบางอย่างที่เขียนซ้ำ ๆ แต่ที่แปลตามตัวอักษรแล้วจะระเบิดระบบรันไทม์ของคุณ ยกตัวอย่างเช่น infinite tail recursion เป็นไปได้ใน Scheme แต่สำนวนเดียวกันทำให้เกิดปัญหาสำหรับสภาพแวดล้อม C ที่มีอยู่ อีกตัวอย่างหนึ่งคือการใช้ฟังก์ชั่นที่ซ้อนกันของคำศัพท์และขอบเขตคงที่ซึ่ง Pascal รองรับ แต่ C ไม่ได้

ในสถานการณ์เหล่านี้คุณอาจพยายามเอาชนะการต่อต้านทางการเมืองเป็นภาษาดั้งเดิม คุณอาจพบว่าตัวเองปรับใช้ Lisp ไม่ดีเช่นเดียวกับในกฎหมายที่สิบของ Greenspun (ลิ้นที่แก้ม) หรือคุณอาจพบวิธีแก้ไขปัญหาที่แตกต่างอย่างสิ้นเชิง แต่ในกรณีใด ๆ ก็มีวิธี


10
ทัวริงยังไม่ได้รับการพิสูจน์หรือไม่
Liran Orevi

15
@eyelidlessness: หากคุณสามารถใช้ A ใน B ก็หมายความว่า B มีพลังงานอย่างน้อยเท่ากับ A. หากคุณไม่สามารถดำเนินการคำสั่งของ A ใน A-Implement-of-B ได้นั่นไม่ใช่การนำไปปฏิบัติ หากสามารถนำ A มาใช้ใน B และ B ได้ใน A, power (A)> = power (B) และ power (B)> = power (A) ทางออกเดียวคือพลังงาน (A) == พลังงาน (B)
Tordek

6
Re: ย่อหน้าที่ 1: คุณกำลังพูดถึงความเท่าเทียมกันของแบบจำลองการคำนวณไม่ใช่วิทยานิพนธ์ทัวริสต์ของโบสถ์ ความเท่าเทียมกันคือ AFAIR พิสูจน์โดยคริสตจักรและ / หรือทัวริง แต่ไม่ใช่วิทยานิพนธ์ วิทยานิพนธ์เป็นความจริงเชิงทดลองว่าทุกสิ่งที่คำนวณได้อย่างสังหรณ์ใจนั้นคำนวณได้ในความหมายทางคณิตศาสตร์ที่เข้มงวด (โดยเครื่องจักรทัวริง / ฟังก์ชั่นวนซ้ำ ฯลฯ ) มันอาจหักล้างได้หากใช้กฎของฟิสิกส์เราสามารถสร้างคอมพิวเตอร์ที่ไม่ใช่แบบคลาสสิกโดยใช้บางสิ่งบางอย่างที่เครื่องทัวริงไม่สามารถทำได้ (เช่นปัญหาการหยุดชะงัก) ในขณะที่ความเท่าเทียมเป็นทฤษฎีบททางคณิตศาสตร์และมันจะไม่หักล้าง
sdcvvc

7
คำตอบนี้ได้รับการโหวตอย่างไร ก่อนอื่นมันเป็นการผสมผสานของทัวริงที่สมบูรณ์กับวิทยานิพนธ์ของทัวริสต์ของทัวริสต์จากนั้นก็จะทำการเขียนด้วยมือที่ไม่ถูกต้องกล่าวถึงระบบ "ขั้นสูง" และวางหางหางที่ไม่มีที่สิ้นสุด ไม่มีใครรู้ว่าทัวริงสมบูรณ์หมายถึงอะไร) จากนั้นข้อสรุปที่ถือด้วยมือที่มีความหวังเช่นนี้เป็นคำถามเกี่ยวกับโอปราห์และสิ่งที่คุณต้องการคือการเป็นบวกและยกระดับ? คำตอบที่น่าสยดสยอง!
ex0du5

8
และ bs เกี่ยวกับความหมาย ??? จริงๆ? นี่เป็นคำถามเกี่ยวกับการแปลงทางวากยสัมพันธ์และอย่างใดมันก็กลายเป็นวิธีที่ดีในการตั้งชื่อ Dijkstra และบอกเป็นนัยว่าคุณรู้อะไรเกี่ยวกับไพแคลคูลัสไหม ให้ฉันทำให้ชัดเจน: ไม่ว่าคนดูที่ความหมายเชิงลบของภาษาหรือรุ่นอื่น ๆ จะไม่มีผลต่อคำตอบสำหรับคำถามนี้ ไม่ว่าภาษานั้นจะเป็นภาษาแอสเซมบลีหรือภาษาที่ใช้ในการสร้างโมเดลโดเมนไม่มีความหมายอะไรเลย มันเกี่ยวกับทัวริงครบถ้วนและเปลี่ยน "ตัวแปรสแต็ก" เป็น "สแต็คของตัวแปร"
ex0du5

43

เป็นไปได้หรือไม่ที่จะเขียนรูปแบบที่ไม่ใช่แบบเรียกซ้ำสำหรับฟังก์ชันแบบเรียกซ้ำทุกครั้ง

ใช่. หลักฐานที่เป็นทางการง่าย ๆ คือการแสดงให้เห็นว่าทั้งµ recursionและแคลคูลัสที่ไม่ใช่แบบเรียกซ้ำเช่น GOTO นั้นทั้งทัวริงสมบูรณ์ เนื่องจากแคลคูลัสทัวริงที่สมบูรณ์ทั้งหมดนั้นมีความเทียบเท่าในพลังการแสดงออกของพวกเขาอย่างสมบูรณ์ฟังก์ชัน recursive ทั้งหมดจึงสามารถใช้งานได้โดยแคลคูลัสทัวริงที่ไม่เรียกซ้ำ

น่าเสียดายที่ฉันไม่สามารถหาคำจำกัดความที่ดีและเป็นทางการของ GOTO ทางออนไลน์ได้

โปรแกรม GOTO เป็นลำดับของคำสั่งP ที่ดำเนินการบนเครื่องลงทะเบียนซึ่งPเป็นหนึ่งในรายการต่อไปนี้:

  • HALTซึ่งหยุดการทำงาน
  • r = r + 1การrลงทะเบียนใด ๆ
  • r = r – 1การrลงทะเบียนใด ๆ
  • GOTO xxป้ายกำกับอยู่ที่ไหน
  • IF r ≠ 0 GOTO xที่rใดก็ได้ที่ลงทะเบียนและxเป็นฉลาก
  • ป้ายกำกับตามด้วยคำสั่งใด ๆ ข้างต้น

อย่างไรก็ตามการแปลงระหว่างฟังก์ชั่นแบบเรียกซ้ำและที่ไม่เกิดซ้ำนั้นไม่ได้เป็นเรื่องเล็กน้อยเสมอไป (ยกเว้นโดยการใช้งานสแต็กการโทรซ้ำด้วยตนเองแบบไม่สนใจอีกครั้ง)

สำหรับข้อมูลเพิ่มเติมดูคำตอบนี้


คำตอบที่ดี! อย่างไรก็ตามในทางปฏิบัติฉันมีความยากลำบากอย่างมากในการทำแอลกอแบบเรียกซ้ำให้เป็นตัววนซ้ำ ตัวอย่างเช่นฉันไม่สามารถเปลี่ยน monomorphic typer ที่นำเสนอที่นี่community.topcoder.com/…เป็นอัลกอริทึมซ้ำได้
Nils

31

การเรียกซ้ำถูกใช้เป็นกองซ้อนหรือโครงสร้างที่คล้ายกันในล่ามหรือผู้แปลที่แท้จริง ดังนั้นแน่นอนคุณสามารถแปลงฟังก์ชันเวียนไปยังคู่ซ้ำเพราะนั่นคือวิธีการที่จะทำเสมอ (ถ้าโดยอัตโนมัติ) คุณเพียงแค่ทำซ้ำงานของคอมไพเลอร์ในโฆษณาเฉพาะกิจและอาจเป็นไปอย่างน่าเกลียดและไม่มีประสิทธิภาพ


13

โดยพื้นฐานแล้วใช่แล้วสิ่งสำคัญที่คุณต้องทำคือแทนที่การเรียกใช้เมธอด (โดยปริยายผลักสถานะลงบนสแต็ก) ลงในสแต็กที่ชัดเจนเพื่อให้จำได้ว่า 'การโทรก่อนหน้า' ได้รับมาแล้ว แทน.

ฉันคิดว่าการรวมกันของวน, สแต็คและเครื่องรัฐสามารถใช้สำหรับทุกสถานการณ์โดยจำลองการเรียกเมธอด ไม่ว่าสิ่งนี้จะเป็น 'ดีกว่า' (อย่างใดอย่างหนึ่งได้เร็วขึ้นหรือมีประสิทธิภาพมากขึ้นในบางแง่มุม) เป็นไปไม่ได้ที่จะพูดโดยทั่วไป


9
  • การไหลเวียนของการดำเนินการของฟังก์ชันแบบเรียกซ้ำสามารถถูกแสดงเป็นทรี

  • ตรรกะเดียวกันสามารถทำได้โดยการวนรอบซึ่งใช้โครงสร้างข้อมูลเพื่อสำรวจทรีนั้น

  • การสำรวจเส้นทางแรกที่ลึกสามารถทำได้โดยใช้กองซ้อนการสำรวจเส้นทางแรกที่กว้างสามารถทำได้โดยใช้คิว

ดังนั้นคำตอบคือ: ใช่ ทำไมhttps://stackoverflow.com/a/531721/2128327

การเรียกซ้ำใด ๆ สามารถทำได้ในวงเดียว? ใช่เป็นเพราะ

เครื่องทัวริงทำทุกอย่างที่ทำได้ด้วยการทำลูปเดี่ยว:

  1. เอาคำสั่ง
  2. ประเมินมัน
  3. ข้ามไป 1

7

ใช่ใช้สแต็กอย่างชัดเจน (แต่การเรียกซ้ำเป็นสิ่งที่น่าอ่านมากกว่า IMHO)


17
ฉันจะไม่พูดว่ามันน่าอ่านเสมอ ทั้งการวนซ้ำและการเรียกซ้ำมีที่ของมัน
Matthew Flaschen

6

ใช่เป็นไปได้เสมอที่จะเขียนเวอร์ชันที่ไม่ต้องเรียกซ้ำ วิธีแก้ปัญหาเล็กน้อยคือการใช้โครงสร้างข้อมูลสแต็กและจำลองการดำเนินการแบบเรียกซ้ำ


ข้อใดที่เอาชนะวัตถุประสงค์หากโครงสร้างข้อมูลสแต็กของคุณถูกจัดสรรในสแต็กหรือใช้เวลานานกว่านี้หากถูกจัดสรรบนฮีป นั่นฟังดูเล็กน้อย แต่ไร้ประสิทธิภาพสำหรับฉัน
conradkleinespel

1
@conradk ในบางกรณีมันเป็นเรื่องจริงที่ต้องทำหากคุณต้องดำเนินการแบบเรียกซ้ำแบบต้นไม้ในปัญหาที่มีขนาดใหญ่พอที่จะทำให้ call stack หมดไป; หน่วยความจำฮีปโดยทั่วไปมีอยู่มากมาย
jamesdlin

4

โดยหลักการแล้วเป็นไปได้ที่จะลบการเรียกซ้ำและแทนที่ด้วยการวนซ้ำในภาษาที่มีสถานะไม่สิ้นสุดทั้งโครงสร้างข้อมูลและการเรียกสแต็ก นี่เป็นผลลัพธ์พื้นฐานของวิทยานิพนธ์ทัวริสต์

เนื่องจากภาษาการเขียนโปรแกรมจริงคำตอบนั้นไม่ชัดเจน ปัญหาคือมีความเป็นไปได้ค่อนข้างมากที่จะมีภาษาที่จำนวนหน่วยความจำที่สามารถจัดสรรในโปรแกรมนั้นมี จำกัด แต่จำนวนของ call stack ที่สามารถใช้ได้นั้นไม่ จำกัด (32- บิต C ที่อยู่ของตัวแปร stack ไม่สามารถเข้าถึงได้). ในกรณีนี้การเรียกซ้ำมีประสิทธิภาพมากขึ้นเพียงเพราะมีหน่วยความจำมากกว่าที่สามารถใช้ได้ มีหน่วยความจำไม่สามารถจัดสรรได้อย่างชัดเจนเพียงพอที่จะเลียนแบบ call stack สำหรับการสนทนาโดยละเอียดเกี่ยวกับสิ่งนี้โปรดดูการสนทนานี้


2

ฟังก์ชันที่คำนวณได้ทั้งหมดสามารถคำนวณได้โดยเครื่องทัวริงด้วยเหตุนี้ระบบซ้ำและเครื่องทัวริง (ระบบซ้ำ) จึงเทียบเท่า


1

บางครั้งการแทนที่การเรียกซ้ำนั้นง่ายกว่านั้นมาก การเรียกซ้ำเคยเป็นสิ่งที่ทันสมัยที่สอนใน CS ในปี 1990 และนักพัฒนาทั่วไปจำนวนมากจากเวลานั้นคิดว่าถ้าคุณแก้ไขบางสิ่งด้วยการเรียกซ้ำมันเป็นวิธีที่ดีกว่า ดังนั้นพวกเขาจะใช้การเรียกซ้ำแทนที่จะวนกลับไปข้างหลังเพื่อย้อนกลับลำดับหรือสิ่งที่งี่เง่าเช่นนั้น ดังนั้นบางครั้งการลบการสอบถามซ้ำจึงเป็นประเภท "การออกกำลังกายที่ชัดเจน"

นี่เป็นปัญหาที่น้อยลงเนื่องจากแฟชั่นเปลี่ยนไปใช้เทคโนโลยีอื่น


0

การลบการเรียกซ้ำเป็นปัญหาที่ซับซ้อนและทำได้ภายใต้สถานการณ์ที่กำหนดไว้อย่างดี

กรณีด้านล่างเป็นเรื่องง่าย:


0

Appart จาก stack stack ที่ชัดเจนอีกรูปแบบหนึ่งสำหรับการแปลงการเรียกซ้ำไปเป็นการวนซ้ำคือการใช้แทรมโพลีน

ที่นี่ฟังก์ชั่นทั้งคืนผลสุดท้ายหรือปิดการเรียกใช้ฟังก์ชั่นว่ามันจะได้ทำอย่างอื่น จากนั้นฟังก์ชั่นเริ่มต้น (trampolining) จะเรียกใช้การปิดที่ส่งคืนจนกว่าจะถึงผลลัพธ์สุดท้าย

วิธีนี้ใช้ได้กับฟังก์ชั่นแบบเรียกซ้ำ แต่ฉันเกรงว่าจะใช้ได้กับการโทรแบบหางเท่านั้น

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


0

ฉันจะบอกว่าใช่ - การเรียกใช้ฟังก์ชั่นคืออะไรนอกจากการดำเนินการข้ามไปและการซ้อน (พูดคร่าว ๆ ) สิ่งที่คุณต้องทำคือเลียนแบบสแต็คที่สร้างขึ้นในขณะที่เรียกใช้ฟังก์ชันและทำสิ่งที่คล้ายกับ goto (คุณอาจเลียนแบบ gotos ด้วยภาษาที่ไม่มีคำหลักนี้อย่างชัดเจน)


1
ผมคิดว่า OP ที่กำลังมองหาหลักฐานหรือสิ่งอื่นที่สำคัญ
ทิม

0

ดูรายการต่อไปนี้บนวิกิพีเดียคุณสามารถใช้เป็นจุดเริ่มต้นเพื่อค้นหาคำตอบสำหรับคำถามของคุณได้อย่างสมบูรณ์

ปฏิบัติตามย่อหน้าที่อาจให้คำแนะนำแก่คุณในการเริ่มต้น:

การแก้ไขความสัมพันธ์ที่เกิดซ้ำหมายถึงการได้รับโซลูชั่นแบบปิด : ฟังก์ชันที่ไม่ใช่แบบเรียกซ้ำของ n

ดูที่ย่อหน้าสุดท้ายของรายการนี้ด้วย


0

เป็นไปได้ที่จะแปลงอัลกอริทึมแบบเรียกซ้ำใด ๆ ไปเป็นแบบไม่เรียกซ้ำ แต่บ่อยครั้งที่ตรรกะมีความซับซ้อนมากและการทำเช่นนั้นจำเป็นต้องใช้สแต็ก ในความเป็นจริงการเรียกซ้ำตัวเองใช้ stack: stack function

รายละเอียดเพิ่มเติม: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Functions


-1

tazzego การเรียกซ้ำหมายถึงฟังก์ชั่นจะเรียกตัวเองว่าคุณชอบหรือไม่ เมื่อมีคนพูดถึงว่าสิ่งต่าง ๆ สามารถทำได้โดยไม่ต้องเรียกซ้ำพวกเขาหมายถึงสิ่งนี้และคุณไม่สามารถพูดว่า "ไม่นั่นไม่เป็นความจริงเพราะฉันไม่เห็นด้วยกับคำนิยามของการเรียกซ้ำ" เป็นคำสั่งที่ถูกต้อง

ในใจทุกสิ่งที่คุณพูดนั้นเป็นเรื่องไร้สาระ อีกสิ่งหนึ่งที่คุณบอกว่าไม่ใช่เรื่องไร้สาระคือความคิดที่ว่าคุณไม่สามารถจินตนาการการเขียนโปรแกรมโดยไม่มี callstack นั่นคือสิ่งที่ทำมานานหลายทศวรรษจนกระทั่งการใช้ callstack ได้รับความนิยม FORTRAN เวอร์ชันเก่าขาด callstack และทำงานได้ดี

โดยวิธีการมีภาษาทัวริงที่มีอยู่ที่ใช้เฉพาะการเรียกซ้ำ (เช่น SML) เป็นวิธีการวนลูป นอกจากนี้ยังมีภาษาทัวริงที่สมบูรณ์ซึ่งใช้การวนซ้ำเป็นวิธีการวนซ้ำ (เช่น FORTRAN IV) วิทยานิพนธ์ของคริสตจักรทัวริงพิสูจน์ให้เห็นว่าสิ่งที่เป็นไปได้ในภาษาแบบเรียกซ้ำเท่านั้นสามารถทำได้ในภาษาที่ไม่ใช่แบบเรียกซ้ำและ vica-versa โดยความจริงที่ว่าพวกเขาทั้งสองมีคุณสมบัติของความสมบูรณ์แบบ


-3

นี่คืออัลกอริทึมซ้ำ:

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
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.