แนวคิดของการเรียกซ้ำไม่เป็นเรื่องธรรมดาในโลกแห่งความเป็นจริง ดังนั้นดูเหมือนว่าสับสนกับโปรแกรมเมอร์สามเณร แม้ว่าฉันคิดว่าพวกเขาคุ้นเคยกับแนวคิดทีละน้อย ดังนั้นอะไรคือคำอธิบายที่ดีสำหรับพวกเขาที่จะเข้าใจความคิดได้อย่างง่ายดาย
แนวคิดของการเรียกซ้ำไม่เป็นเรื่องธรรมดาในโลกแห่งความเป็นจริง ดังนั้นดูเหมือนว่าสับสนกับโปรแกรมเมอร์สามเณร แม้ว่าฉันคิดว่าพวกเขาคุ้นเคยกับแนวคิดทีละน้อย ดังนั้นอะไรคือคำอธิบายที่ดีสำหรับพวกเขาที่จะเข้าใจความคิดได้อย่างง่ายดาย
คำตอบ:
เพื่ออธิบายการเรียกซ้ำฉันใช้การผสมผสานของคำอธิบายที่ต่างกันโดยปกติแล้วทั้งสองพยายาม:
สำหรับผู้เริ่มต้นWolfram | Alphaให้นิยามมันง่ายกว่าWikipedia :
นิพจน์เช่นนั้นแต่ละคำถูกสร้างขึ้นโดยการทำซ้ำการดำเนินการทางคณิตศาสตร์ที่เฉพาะเจาะจง
ถ้านักเรียนของคุณ (หรือคนที่คุณอธิบายเกินไปจากนี้ไปฉันจะบอกนักเรียน) มีอย่างน้อยบางส่วนพื้นหลังทางคณิตศาสตร์ที่พวกเขาได้อย่างเห็นได้ชัดอยู่แล้วพบ recursion โดยการศึกษาและชุดความคิดของพวกเขาrecursivityของพวกเขาและความสัมพันธ์เวียนเกิด
วิธีที่ดีในการเริ่มต้นคือการสาธิตด้วยชุดและบอกว่ามันค่อนข้างง่ายที่การเรียกซ้ำเป็นเรื่อง:
โดยปกติแล้วคุณจะได้รับ "huh huh, whatev" "ที่ดีที่สุดเพราะพวกเขายังไม่ได้ใช้หรืออาจเป็นเพียงกรนที่ลึกมาก
สำหรับส่วนที่เหลือจริง ๆ แล้วมันเป็นรุ่นที่มีรายละเอียดของสิ่งที่ฉันนำเสนอในภาคผนวกของคำตอบของฉันสำหรับคำถามที่คุณชี้ไปที่เกี่ยวกับพอยน์เตอร์ (ปุนแย่)
ในขั้นตอนนี้นักเรียนของฉันมักจะรู้วิธีพิมพ์บางอย่างบนหน้าจอ สมมติว่าเราจะใช้ C พวกเขารู้วิธีที่จะพิมพ์ถ่านเดียวโดยใช้หรือwrite
printf
พวกเขายังรู้เกี่ยวกับลูปควบคุม
ฉันมักจะใช้ปัญหาการเขียนโปรแกรมซ้ำ ๆ และเรียบง่ายไม่กี่จนกว่าพวกเขาจะได้รับ:
factorial
แฟคทอเรียลเป็นแนวคิดทางคณิตศาสตร์ที่เข้าใจง่ายและการใช้งานนั้นใกล้เคียงกับการแสดงทางคณิตศาสตร์มาก อย่างไรก็ตามพวกเขาอาจไม่ได้รับมันในตอนแรก
ตัวอักษร
เวอร์ชั่นตัวอักษรนั้นน่าสนใจที่จะสอนให้พวกเขาคิดเกี่ยวกับการเรียงลำดับประโยคซ้ำ เช่นเดียวกับพอยน์เตอร์พวกเขาก็จะโยนเส้นสุ่มที่คุณ ประเด็นก็คือนำพวกเขาไปสู่การตระหนักว่าการวนซ้ำสามารถย้อนกลับได้โดยการแก้ไขเงื่อนไขหรือเพียงแค่กลับคำสั่งของคำสั่งในฟังก์ชั่นของคุณ นั่นคือสิ่งที่การพิมพ์ตัวอักษรช่วยเพราะเป็นสิ่งที่มองเห็นได้สำหรับพวกเขา เพียงแค่ให้พวกเขาเขียนฟังก์ชั่นที่จะพิมพ์อักขระหนึ่งตัวสำหรับการโทรแต่ละครั้งและเรียกตัวเองซ้ำ ๆ เพื่อเขียนหนึ่งถัดไป (หรือก่อนหน้า)
แฟน ๆ ของ FP ข้ามความจริงที่ว่าสิ่งที่พิมพ์ไปยังสตรีมเอาท์พุทเป็นผลข้างเคียงสำหรับตอนนี้ ... อย่ามัว แต่รำคาญกับหน้า FP (แต่ถ้าคุณใช้ภาษาที่มีการสนับสนุนรายการอย่าลังเลที่จะต่อเชื่อมกับรายการในแต่ละการวนซ้ำและเพียงพิมพ์ผลลัพธ์สุดท้าย แต่โดยปกติฉันจะเริ่มด้วย C ซึ่งน่าเสียดายที่ไม่ใช่ปัญหาที่ดีที่สุดสำหรับปัญหาและแนวคิดนี้) .
ยกกำลัง
ปัญหาการยกกำลังยากขึ้นเล็กน้อย ( ในขั้นตอนของการเรียนรู้) เห็นได้ชัดว่าแนวคิดนั้นเหมือนกับแฟคทอเรียลและไม่มีการเพิ่มความซับซ้อน ... ยกเว้นว่าคุณมีพารามิเตอร์หลายตัว และนั่นก็เพียงพอแล้วที่จะทำให้ผู้คนสับสนและทิ้งพวกเขาในตอนแรก
รูปแบบที่เรียบง่าย:
สามารถแสดงเช่นนี้โดยการเกิดซ้ำ:
ยาก
เมื่อปัญหาง่าย ๆ เหล่านี้ได้รับการแสดงและนำไปใช้ใหม่ในบทเรียนแล้วคุณสามารถให้แบบฝึกหัดที่ยากขึ้น (แต่คลาสสิกมาก) เล็กน้อย:
หมายเหตุ: อีกครั้งสิ่งเหล่านี้ไม่ยากจริง ๆ ... พวกเขาเข้าหาปัญหาจากมุมเดียวกันหรืออีกมุมหนึ่งเล็กน้อย แต่การฝึกฝนทำให้สมบูรณ์แบบ
เอกสารอ้างอิง
การอ่านบางคนไม่เคยเจ็บปวด ในตอนแรกมันจะดีและพวกเขาจะรู้สึกหลงทางมากขึ้น มันเป็นสิ่งที่เติบโตขึ้นกับคุณและอยู่ด้านหลังศีรษะของคุณจนกระทั่งวันหนึ่งคุณรู้ว่าคุณได้รับมันในที่สุด จากนั้นคุณคิดถึงสิ่งเหล่านี้ที่คุณอ่าน recursion , การเรียกซ้ำในสาขาวิทยาศาสตร์คอมพิวเตอร์และความสัมพันธ์เวียนเกิดหน้าในวิกิพีเดียจะทำตอนนี้
ระดับ / ลึก
สมมติว่านักเรียนของคุณไม่มีประสบการณ์ในการเขียนโค้ดมากนักให้โค้ดสมบูรณ์ หลังจากความพยายามครั้งแรกให้ฟังก์ชั่นการพิมพ์ที่สามารถแสดงระดับการเรียกซ้ำได้ การพิมพ์ค่าตัวเลขของระดับช่วย
แผนภาพสแต็ก - เป็น - ลิ้นชัก
การเยื้องผลลัพธ์ที่พิมพ์ (หรือระดับของเอาต์พุต) ช่วยได้เช่นกันเพราะจะให้ภาพที่เป็นตัวแทนของสิ่งที่โปรแกรมของคุณกำลังทำอยู่การเปิดและปิดบริบทสแต็กเช่นลิ้นชักหรือโฟลเดอร์ใน explorer ระบบไฟล์
ตัวย่อแบบเรียกซ้ำ
หากนักเรียนของคุณมีอยู่แล้วบิตประสบการณ์ในวัฒนธรรมคอมพิวเตอร์แล้วพวกเขาอาจใช้บางโครงการ / โปรแกรมที่มีชื่อโดยใช้คำย่อแบบกล่าวซ้ำ มันเป็นประเพณีที่เกิดขึ้นมาระยะหนึ่งโดยเฉพาะในโครงการ GNU ตัวอย่างบางส่วน ได้แก่ :
ซ้ำ:
ซ้ำกัน:
ให้พวกเขาลองคิดด้วยตัวเอง
ในทำนองเดียวกันมีอารมณ์ขันซ้ำหลายครั้งเช่นการแก้ไขการค้นหาซ้ำของ Google สำหรับข้อมูลเพิ่มเติมเกี่ยวกับการเรียกซ้ำอ่านคำตอบนี้
ปัญหาบางอย่างที่คนมักจะต่อสู้ด้วยและที่คุณต้องรู้คำตอบ
ทำไมโอ้พระเจ้าทำไม ???
ทำไมคุณจะทำเช่นนั้น? เหตุผลที่ดี แต่ไม่ชัดเจนคือบ่อยครั้งที่มันจะแสดงปัญหาได้ง่ายกว่า เหตุผลที่ไม่ดี แต่เห็นได้ชัดคือบ่อยครั้งที่พิมพ์น้อยลง (อย่าทำให้พวกเขารู้สึกเหมือน soooo l33t เพียงแค่ใช้การเรียกซ้ำแม้ว่า ... )
ปัญหาบางอย่างง่ายกว่าในการแก้เมื่อใช้วิธีเรียกซ้ำ โดยทั่วไปปัญหาใด ๆ ที่คุณสามารถแก้ไขได้โดยใช้กระบวนทัศน์การแบ่งและการพิชิตจะเหมาะกับอัลกอริทึมการเรียกซ้ำแบบหลายสาขา
มีอะไรอีก
ทำไมn
หรือ (ชื่อตัวแปรของคุณ) แตกต่างกันทุกครั้ง ผู้เริ่มต้นมักจะมีปัญหาในการทำความเข้าใจว่าตัวแปรและพารามิเตอร์คืออะไรและวิธีการตั้งชื่อสิ่งต่าง ๆn
ในโปรแกรมของคุณสามารถมีค่าต่างกันได้ ตอนนี้ถ้าค่านี้อยู่ในลูปควบคุมหรือเรียกซ้ำนั่นก็ยิ่งแย่ลง! จะดีและไม่ได้ใช้ชื่อตัวแปรเหมือนกันทุกที่และทำให้มันชัดเจนว่าพารามิเตอร์เป็นเพียงตัวแปร
สิ้นสุดเงื่อนไข
ฉันจะกำหนดเงื่อนไขสิ้นสุดได้อย่างไร ง่ายมากเพียงแค่ให้พวกเขาพูดเสียงดังออกมา ตัวอย่างเช่นสำหรับแฟคทอเรียลเริ่มจาก 5 จากนั้น 4 จากนั้น ... จนถึง 0
ปีศาจอยู่ในรายละเอียด
ไม่ได้พูดคุยถึงต้นจดสิ่งที่ต้องการเพิ่มประสิทธิภาพสายหาง ฉันรู้ฉันรู้ว่า TCO เป็นคนดี แต่พวกเขาไม่สนใจในตอนแรก ให้เวลาพวกเขาห่อหัวของพวกเขารอบกระบวนการในวิธีที่เหมาะกับพวกเขา รู้สึกอิสระที่จะทำลายโลกของพวกเขาอีกครั้งในภายหลัง แต่ให้พวกเขาหยุดพัก
ในทำนองเดียวกันไม่ได้พูดคุยตรงจากบรรยายครั้งแรกเกี่ยวกับสแต็คโทรและการใช้หน่วยความจำและ ... ดีที่ ... กองล้น ฉันมักจะสอนนักเรียนแบบส่วนตัวที่แสดงการบรรยายให้กับพวกเขาซึ่งมี 50 สไลด์เกี่ยวกับทุกสิ่งที่ต้องรู้เกี่ยวกับการเรียกซ้ำเมื่อพวกเขาแทบจะไม่สามารถเขียนวนซ้ำได้อย่างถูกต้องในขั้นตอนนี้ นี่เป็นตัวอย่างที่ดีของวิธีการอ้างอิงที่จะช่วยในภายหลังแต่ตอนนี้เพียงแค่ทำให้คุณสับสน
แต่โปรดในเวลาที่กำหนดให้ชัดเจนว่ามีเหตุผลที่จะไปเส้นทางซ้ำหรือเรียกซ้ำ
เรียกซ้ำกัน
เราได้เห็นแล้วว่าฟังก์ชั่นสามารถเรียกซ้ำได้และแม้กระทั่งว่าพวกเขาสามารถมีจุดโทรหลายจุด (8-ราชินี, ฮานอย, ฟีโบนักชีหรือแม้แต่อัลกอริธึมสำรวจสำหรับเรือกวาดทุ่นระเบิด) แต่สิ่งที่เกี่ยวกับการโทรซ้ำร่วมกัน ? เริ่มด้วยคณิตศาสตร์ที่นี่เช่นกัน f(x) = g(x) + h(x)
ที่ไหนg(x) = f(x) + l(x)
และh
และl
เพียงแค่ทำสิ่งต่าง ๆ
การเริ่มต้นด้วยชุดคณิตศาสตร์เพียงอย่างเดียวทำให้การเขียนและนำไปปฏิบัติง่ายขึ้นเนื่องจากสัญญาถูกกำหนดไว้อย่างชัดเจนโดยนิพจน์ ตัวอย่างเช่นลำดับของHofstadter ตัวเมียและตัวผู้ :
อย่างไรก็ตามในแง่ของรหัสก็เป็นที่น่าสังเกตว่าการดำเนินการของการแก้ปัญหาร่วมกัน recursive มักจะนำไปสู่รหัสซ้ำซ้อนและค่อนข้างควรจะคล่องตัวในรูปแบบ recursive เดียว (ดูปีเตอร์นอร์วิก 's แก้ปริศนาทุกซูโดกุ
static unsigned int vote = 1;
จากฉัน ให้อภัยอารมณ์ขันแบบคงที่ถ้าคุณจะ :) นี่คือคำตอบที่ดีที่สุดเพื่อให้ห่างไกล
การเรียกใช้ฟังก์ชันจากภายในฟังก์ชันเดียวกันนั้น
วิธีใช้งานเมื่อใช้งานและวิธีหลีกเลี่ยงการออกแบบที่ไม่ดีเป็นสิ่งสำคัญที่ต้องรู้ซึ่งคุณจะต้องลองใช้ด้วยตนเองและเข้าใจว่าจะเกิดอะไรขึ้น
สิ่งที่สำคัญที่สุดที่คุณต้องรู้ก็คือระวังให้มากเพื่อไม่ให้วนซ้ำที่ไม่สิ้นสุด คำตอบจาก pramodc84สำหรับคำถามของคุณมีข้อผิดพลาดนี้: มันไม่สิ้นสุด ...
ฟังก์ชั่นวนซ้ำจะต้องตรวจสอบเงื่อนไขเพื่อพิจารณาว่าควรเรียกตัวเองอีกครั้งหรือไม่
ตัวอย่างคลาสสิกที่สุดในการใช้การเรียกซ้ำคือทำงานกับต้นไม้ที่ไม่มีข้อ จำกัด เชิงลึก นี่เป็นงานที่คุณต้องใช้การสอบถามซ้ำ
a
ยังคงเรียกตัวเองเพียงทางอ้อม (โดยการโทรb
)
การเขียนโปรแกรมแบบเรียกซ้ำเป็นกระบวนการของการลดปัญหาอย่างต่อเนื่องเพื่อให้ง่ายต่อการแก้ไขรุ่นของตัวเอง
ทุกฟังก์ชั่นวนซ้ำมีแนวโน้มที่:
เมื่อขั้นตอนที่ 2 อยู่ก่อน 3 และเมื่อขั้นตอนที่ 4 เป็นเรื่องเล็กน้อย (การต่อเชื่อมผลรวมหรือไม่มีอะไร) สิ่งนี้จะช่วยให้การเรียกซ้ำหางเป็นไปได้ ขั้นตอนที่ 2 มักจะต้องมาหลังขั้นตอนที่ 3 เนื่องจากอาจต้องใช้ผลลัพธ์จากโดเมนย่อยของปัญหาเพื่อให้ขั้นตอนปัจจุบันเสร็จสมบูรณ์
ใช้การสำรวจเส้นทางของต้นไม้ไบนารีไปข้างหน้าตรงๆ การสำรวจเส้นทางสามารถทำได้ในการสั่งซื้อล่วงหน้า, ในการสั่งซื้อหรือโพสต์ขึ้นอยู่กับสิ่งที่จำเป็น
B
A C
สั่งซื้อล่วงหน้า: BAC
traverse(tree):
visit the node
traverse(left)
traverse(right)
ตามลำดับ: ABC
traverse(tree):
traverse(left)
visit the node
traverse(right)
การสั่งซื้อภายหลัง: ACB
traverse(tree):
traverse(left)
traverse(right)
visit the node
ปัญหาการเรียกซ้ำจำนวนมากเป็นกรณีเฉพาะของการดำเนินการแผนที่หรือการทำความเข้าใจแบบพับได้การดำเนินการทั้งสองนี้สามารถนำไปสู่ความเข้าใจอย่างมีนัยสำคัญเกี่ยวกับกรณีการใช้งานที่ดีสำหรับการเรียกซ้ำ
OP กล่าวว่าการเรียกซ้ำไม่มีอยู่ในโลกจริง แต่ฉันขอแตกต่างกัน
ลอง 'การทำงาน' ในโลกแห่งความเป็นจริงในการตัดพิซซ่า คุณได้นำพิซซ่าออกจากเตาอบและเพื่อให้บริการคุณต้องตัดมันครึ่งหนึ่งแล้วตัดครึ่งเหล่านั้นครึ่งแล้วตัดอีกครึ่งหนึ่งผลลัพธ์
การทำงานของการตัดพิซซ่าที่คุณแสดงซ้ำแล้วซ้ำอีกจนกว่าคุณจะได้ผลลัพธ์ตามที่คุณต้องการ (จำนวนชิ้น) และสำหรับข้อโต้แย้งสมมติว่าพิซซ่าที่ไม่ได้เจียระไนเป็นชิ้นเอง
นี่คือตัวอย่างใน Ruby:
def cut_pizza (existing_slices, ที่ต้องการ_slices) ถ้า existing_slices! = ต้องการ_slices # เรามีชิ้นไม่เพียงพอที่จะเลี้ยงทุกคนดังนั้น # เราตัดพิซซ่าเป็นส่วน ๆ ดังนั้นเพิ่มจำนวนของพวกเขาเป็นสองเท่า new_slices = existing_slices * 2 # และนี่คือการเรียกซ้ำ cut_pizza (new_slices, ที่ต้องการ_slices) อื่น # เรามีจำนวนชิ้นที่ต้องการดังนั้นเราจึงกลับมา # ที่นี่แทนที่จะเรียกเก็บเงินต่อไป ส่งคืนค่าที่มีอยู่ ปลาย ปลาย pizza = 1 # a พิซซ่าทั้งหมด 'one slice' cut_pizza (pizza, 8) # => เราจะได้ 8
ดังนั้นการทำงานในโลกแห่งความเป็นจริงก็คือการตัดพิซซ่าและการเรียกซ้ำก็ทำสิ่งเดียวกันซ้ำแล้วซ้ำอีกจนกว่าคุณจะมีสิ่งที่คุณต้องการ
การดำเนินการคุณจะพบว่าการครอบตัดที่คุณสามารถนำไปใช้กับฟังก์ชันแบบเรียกซ้ำคือ:
ฉันขอแนะนำให้เขียนโปรแกรมเพื่อค้นหาไฟล์ตามชื่อไฟล์และลองเขียนฟังก์ชั่นที่เรียกตัวเองจนกระทั่งพบว่าลายเซ็นจะมีลักษณะดังนี้:
find_file_by_name(file_name_we_are_looking_for, path_to_look_in)
ดังนั้นคุณสามารถเรียกมันว่า:
find_file_by_name('httpd.conf', '/etc') # damn it i can never find apache's conf
เป็นเพียงกลไกการเขียนโปรแกรมในความคิดของฉันวิธีการลบการทำซ้ำอย่างชาญฉลาด คุณสามารถเขียนซ้ำได้โดยใช้ตัวแปร แต่นี่เป็นวิธีการแก้ปัญหา 'ดีกว่า' ไม่มีอะไรลึกลับหรือยากเกี่ยวกับเรื่องนี้ คุณจะเขียนฟังก์ชันแบบเรียกซ้ำสองสามครั้งมันจะคลิกและhuzzahกลอีกกลในกล่องเครื่องมือการเขียนโปรแกรมของคุณ
เครดิตเสริมcut_pizza
ตัวอย่างข้างต้นจะทำให้คุณมีระดับสแต็คข้อผิดพลาดลึกเกินไปถ้าคุณถามมันสำหรับจำนวนของชิ้นที่ไม่ได้เป็นอำนาจของ 2 (เช่น 2 หรือ 4 หรือ 8 หรือ 16) คุณสามารถปรับเปลี่ยนมันได้ไหมถ้ามีคนขอ 10 แผ่นมันจะไม่ทำงานตลอดไปหรือไม่?
โอเคฉันจะพยายามทำให้เรื่องนี้ง่ายและกระชับ
ฟังก์ชั่นวนซ้ำเป็นฟังก์ชั่นที่เรียกตัวเองว่า ฟังก์ชั่นวนซ้ำประกอบด้วยสามสิ่ง:
วิธีที่ดีที่สุดในการเขียนวิธีเรียกซ้ำคือคิดถึงวิธีที่คุณพยายามเขียนเป็นตัวอย่างง่ายๆเพียงจัดการหนึ่งวนวนของกระบวนการที่คุณต้องการวนซ้ำแล้วเพิ่มการเรียกไปยังวิธีนั้นและเพิ่มเมื่อคุณต้องการ ยุติ วิธีที่ดีที่สุดในการเรียนรู้คือฝึกฝนเหมือนทุกสิ่ง
ตั้งแต่นี้เป็นเว็บไซต์โปรแกรมเมอร์ฉันจะละเว้นจากการเขียนรหัส แต่นี่คือลิงค์ที่ดี
หากคุณได้รับเรื่องตลกที่คุณได้รับหมายถึงการเรียกซ้ำ
การเรียกซ้ำเป็นเครื่องมือที่โปรแกรมเมอร์สามารถใช้เพื่อเรียกใช้การเรียกฟังก์ชันบนตัวมันเอง ลำดับฟีโบนักชีเป็นตัวอย่างของวิธีการใช้การสอบถามซ้ำ
รหัสซ้ำส่วนใหญ่หากไม่ทั้งหมดสามารถแสดงเป็นฟังก์ชั่นซ้ำ แต่มักจะยุ่ง ตัวอย่างที่ดีของโปรแกรมแบบเรียกซ้ำอื่น ๆ คือโครงสร้างข้อมูลเช่นแผนผังแผนภูมิการค้นหาแบบทวิภาคและแม้แต่แบบรวดเร็ว
การเรียกซ้ำใช้เพื่อทำให้โค้ดมีความเลอะเทอะน้อยลงโปรดจำไว้ว่าโดยทั่วไปจะช้ากว่าและต้องใช้หน่วยความจำมากขึ้น
ฉันชอบที่จะใช้อันนี้:
หากคุณอยู่ที่ทางเข้าร้านเพียงผ่านไป ไม่งั้นใช้ขั้นตอนเดียวแล้วเดินไปจนสุดทางเพื่อไปร้าน
มันเป็นสิ่งสำคัญที่จะรวมสามด้าน:
เราใช้การเรียกซ้ำหลายครั้งในชีวิตประจำวัน เราแค่ไม่คิดอย่างนั้น
for
ลูปที่เขียนไว้เป็นฟังก์ชั่นวนซ้ำแบบไม่มีจุดหมาย
ตัวอย่างที่ดีที่สุดที่ฉันจะชี้ให้คุณคือภาษาการเขียนโปรแกรม C โดย K & R ในหนังสือเล่มนั้น (และฉันอ้างจากหน่วยความจำ) รายการในหน้าดัชนีสำหรับการสอบถามซ้ำ (คนเดียว) แสดงรายการหน้าจริงที่พวกเขาพูดถึงการเรียกซ้ำ หน้าดัชนีเช่นกัน
Josh K พูดถึงตุ๊กตาMatroshkaแล้ว สมมติว่าคุณต้องการเรียนรู้สิ่งที่มีเพียงตุ๊กตาสั้นที่สุดเท่านั้นที่รู้ ปัญหาคือคุณไม่สามารถพูดคุยกับเธอได้โดยตรงเพราะเดิมทีเธออาศัยอยู่ในตุ๊กตาสูงที่วางรูปซ้ายไว้ โครงสร้างนี้เป็นเช่นนั้น (ตุ๊กตาอาศัยอยู่ในตุ๊กตาสูง) จนกว่าจะจบลงด้วยตุ๊กตาที่สูงที่สุดเท่านั้น
ดังนั้นสิ่งเดียวที่คุณทำได้คือถามคำถามกับตุ๊กตาที่สูงที่สุด ตุ๊กตาที่สูงที่สุด (ที่ไม่ทราบคำตอบ) จะต้องส่งคำถามของคุณไปยังตุ๊กตาที่สั้นกว่า (ซึ่งในภาพแรกอยู่ทางขวา) เนื่องจากเธอยังไม่มีคำตอบเธอจึงต้องถามตุ๊กตาตัวสั้นตัวต่อไป สิ่งนี้จะเป็นเช่นนั้นจนกว่าข้อความจะถึงตุ๊กตาที่สั้นที่สุด ตุ๊กตาที่สั้นที่สุด (ซึ่งเป็นเพียงคนเดียวที่รู้คำตอบลับ) จะผ่านคำตอบไปยังตุ๊กตาสูงถัดไป (พบทางซ้ายของเธอ) ซึ่งจะผ่านมันไปยังตุ๊กตาสูงถัดไป ... และจะดำเนินต่อไปจนกว่าคำตอบ มาถึงปลายทางสุดท้ายซึ่งเป็นตุ๊กตาที่สูงที่สุดและในที่สุด ... คุณ :)
นี่คือสิ่งที่เรียกซ้ำการทำจริงๆ ฟังก์ชั่น / วิธีการเรียกตัวเองจนกว่าจะได้คำตอบที่คาดหวัง นั่นเป็นเหตุผลที่เมื่อคุณเขียนโค้ดแบบเรียกซ้ำมันสำคัญมากที่จะต้องตัดสินใจว่าเมื่อไรที่การเรียกซ้ำจะยุติ
ไม่ใช่คำอธิบายที่ดีที่สุด แต่หวังว่าจะช่วยได้
การ สอบถามซ้ำ - รูปแบบของการออกแบบอัลกอริทึมที่การดำเนินการถูกกำหนดในแง่ของตัวเอง
ตัวอย่างคลาสสิกคือการหาแฟกทอเรียลของตัวเลข, n! 0! = 1 และสำหรับหมายเลขธรรมชาติอื่น ๆ N แฟคทอเรียลของ N คือผลผลิตของจำนวนธรรมชาติทั้งหมดน้อยกว่าหรือเท่ากับ N ดังนั้น, 6! = 6 * 5 * 4 * 3 * 2 * 1 = 720 คำจำกัดความพื้นฐานนี้จะช่วยให้คุณสร้างโซลูชันที่วนซ้ำง่าย ๆ :
int Fact(int degree)
{
int result = 1;
for(int i=degree; i>1; i--)
result *= i;
return result;
}
อย่างไรก็ตามตรวจสอบการดำเนินการอีกครั้ง 6! = 6 * 5 * 4 * 3 * 2 * 1 ตามคำนิยามเดียวกัน 5! = 5 * 4 * 3 * 2 * 1 หมายความว่าเราสามารถพูดได้ 6! = 6 * (5!) ในทางกลับกัน 5! = 5 * (4!) และอื่น ๆ โดยการทำเช่นนี้เราลดปัญหาให้กับการดำเนินการที่ทำกับผลลัพธ์ของการดำเนินการก่อนหน้านี้ทั้งหมด ในที่สุดสิ่งนี้จะลดลงเป็นจุดที่เรียกว่าเคสฐานซึ่งผลลัพธ์เป็นที่รู้จักกันโดยนิยาม ในกรณีของเรา 0! = 1 (ในกรณีส่วนใหญ่เราสามารถพูดได้ว่า 1! = 1) ในการคำนวณเรามักได้รับอนุญาตให้กำหนดอัลกอริธึมในลักษณะที่คล้ายกันมากโดยให้วิธีการเรียกตัวเองและผ่านอินพุตขนาดเล็กซึ่งช่วยลดปัญหาผ่านการเรียกซ้ำหลายครั้งไปยังกรณีพื้นฐาน:
int Fact(int degree)
{
if(degree==0) return 1; //the base case; 0! = 1 by definition
else return degree * Fact(degree -1); //the recursive case; N! = N*(N-1)!
}
ในหลายภาษาสามารถทำได้ง่ายขึ้นโดยใช้โอเปอร์เรเตอร์ ternary (บางครั้งถูกมองว่าเป็นฟังก์ชัน Iif ในภาษาที่ไม่ได้ให้โอเปอเรเตอร์):
int Fact(int degree)
{
//reads equivalently to the above, but is concise and often optimizable
return degree==0 ? 1: degree * Fact(degree -1);
}
ข้อดี:
ข้อเสีย:
ตัวอย่างที่ฉันใช้เป็นปัญหาที่ฉันต้องเผชิญในชีวิตจริง คุณมีภาชนะ (เช่นกระเป๋าเป้สะพายหลังขนาดใหญ่ที่คุณตั้งใจจะไปเที่ยว) และคุณต้องการทราบน้ำหนักรวม คุณมีภาชนะที่หลวมสองหรือสามชิ้นและภาชนะอื่น ๆ (เช่นกระสอบสิ่งของ) น้ำหนักของภาชนะทั้งหมดเห็นได้ชัดว่าน้ำหนักของภาชนะเปล่าบวกกับน้ำหนักของทุกอย่างที่อยู่ในภาชนะ สำหรับสิ่งของที่หลวมคุณก็สามารถชั่งน้ำหนักมันได้และสำหรับกระสอบที่คุณสามารถชั่งน้ำหนักได้หรือคุณอาจพูดว่า "น้ำหนักของกระเป๋าแต่ละใบก็คือน้ำหนักของภาชนะเปล่าบวกกับน้ำหนักของทุกอย่างในนั้น" จากนั้นคุณก็เข้าไปในภาชนะบรรจุเข้าไปเรื่อย ๆ จนกว่าคุณจะไปถึงจุดที่มีเพียงสิ่งของที่หลวมในภาชนะ นั่นคือการสอบถามซ้ำ
คุณอาจคิดว่าไม่เคยเกิดขึ้นในชีวิตจริง แต่ลองจินตนาการถึงการนับหรือเพิ่มเงินเดือนของคนใน บริษัท หรือแผนกใดแผนกหนึ่งซึ่งมีส่วนผสมของคนที่เพิ่งทำงานให้กับ บริษัท คนในแผนกแล้วใน หน่วยงานที่มีแผนกและอื่น ๆ หรือการขายในประเทศที่มีภูมิภาคบางแห่งมีภูมิภาคย่อย ฯลฯ ฯลฯ ปัญหาเหล่านี้เกิดขึ้นตลอดเวลาในธุรกิจ
การเรียกซ้ำสามารถใช้เพื่อแก้ปัญหาการนับจำนวนมาก ตัวอย่างเช่นสมมติว่าคุณมีกลุ่มคน n คนในงานปาร์ตี้ (n> 1) และทุกคนก็จับมือของคนอื่นเพียงครั้งเดียว มีการจับมือกันกี่ครั้ง คุณอาจรู้ว่าวิธีแก้ปัญหาคือ C (n, 2) = n (n-1) / 2 แต่คุณสามารถแก้ปัญหาแบบวนซ้ำดังนี้:
สมมติว่ามีเพียงสองคน จากนั้น (โดยการตรวจสอบ) คำตอบนั้นชัดเจน 1
สมมติว่าคุณมีสามคน แยกคนคนหนึ่งออกจากกันและสังเกตว่าเขา / เธอจับมือกับอีกสองคน หลังจากนั้นคุณต้องนับแค่การจับมือกันระหว่างคนอีกสองคน เราทำไปแล้วตอนนี้และก็คือ 1 ดังนั้นคำตอบคือ 2 + 1 = 3
สมมติว่าคุณมี n คน ทำตามตรรกะเดียวกับก่อนหน้านี้คือ (n-1) + (จำนวนการจับมือกันระหว่าง n-1 คน) กำลังขยายเราจะได้รับ (n-1) + (n-2) + ... + 1
แสดงว่าเป็นฟังก์ชันแบบเรียกซ้ำ
f (2) = 1
f (n) = n-1 + f (n-1), n> 2
ในชีวิต (เมื่อเทียบกับในโปรแกรมคอมพิวเตอร์) การเรียกซ้ำเกิดขึ้นบ่อยครั้งภายใต้การควบคุมโดยตรงของเราเพราะมันอาจสร้างความสับสนให้เกิดขึ้น นอกจากนี้การรับรู้มีแนวโน้มที่จะเกี่ยวกับผลข้างเคียงมากกว่าการบริสุทธิ์ตามหน้าที่ดังนั้นหากการเรียกซ้ำเกิดขึ้นคุณอาจไม่สังเกตเห็น
การเรียกซ้ำจะเกิดขึ้นที่นี่ในโลกแม้ว่า มาก.
ตัวอย่างที่ดีคือ (เวอร์ชั่นย่อของ) วัฏจักรของน้ำ:
นี่คือวงจรที่ทำให้ตัวเองเกิดขึ้นอีกครั้ง มันเกิดซ้ำ
อีกที่หนึ่งที่คุณสามารถเรียกซ้ำได้คือภาษาอังกฤษ (และภาษาทั่วไป) คุณอาจไม่รู้จักในตอนแรก แต่วิธีที่เราสามารถสร้างประโยคนั้นวนซ้ำได้เนื่องจากกฎอนุญาตให้เราฝังหนึ่งอินสแตนซ์ของสัญลักษณ์ในอีกอินสแตนซ์ของสัญลักษณ์เดียวกัน
จากสัญชาตญาณภาษาของสตีเวนพินเจอร์:
ถ้าเด็กผู้หญิงคนนั้นกินไอศครีมหรือเด็กผู้หญิงกินขนมแล้วเด็กก็กินฮอทดอก
นั่นคือประโยคทั้งหมดที่มีประโยคอื่นทั้งหมด:
หญิงสาวกินไอศกรีม
หญิงสาวกินขนม
เด็กชายกินฮอทดอก
การทำความเข้าใจประโยคเต็มรูปแบบนั้นเกี่ยวข้องกับการทำความเข้าใจประโยคที่เล็กกว่าซึ่งใช้กลอุบายทางจิตใจชุดเดียวกันเพื่อให้เข้าใจเป็นประโยคเต็ม
เพื่อให้เข้าใจถึงการเรียกซ้ำจากมุมมองการเขียนโปรแกรมง่ายที่สุดในการดูปัญหาที่สามารถแก้ไขได้ด้วยการเรียกซ้ำและเข้าใจว่าเหตุใดจึงควรเป็นและสิ่งที่คุณต้องทำ
สำหรับตัวอย่างฉันจะใช้ฟังก์ชันตัวหารร่วมมากหรือ gcd สั้น ๆ
คุณมีหมายเลขสองของคุณและa
b
ในการค้นหา gcd ของพวกเขา (สมมติว่าไม่ใช่ 0) คุณต้องตรวจสอบว่าa
สามารถแบ่งได้เท่า ๆ กันb
หรือไม่ ถ้ามันเป็นแล้วb
เป็น GCD มิฉะนั้นคุณต้องตรวจสอบ GCD ของและส่วนที่เหลือของb
a/b
คุณควรจะเห็นว่านี่เป็นฟังก์ชันแบบเรียกซ้ำเนื่องจากคุณมีฟังก์ชัน gcd ที่เรียกใช้ฟังก์ชัน gcd เพียงแค่ใช้ค้อนที่บ้านนี่คือใน c # (อีกครั้งโดยสมมติว่า 0 ไม่เคยผ่านเป็นพารามิเตอร์):
int gcd(int a, int b)
{
if (a % b == 0) //this is a stopping condition
{
return b;
}
return (gcd(b, a % b)); //the call to gcd here makes this function recursive
}
ในโปรแกรมมันเป็นสิ่งสำคัญที่จะต้องมีเงื่อนไขการหยุดมิฉะนั้นคุณจะทำงานซ้ำอีกครั้งซึ่งในที่สุดก็จะทำให้กองล้น!
เหตุผลในการใช้การเรียกซ้ำที่นี่แทนที่จะเป็น while loop หรือโครงสร้างวนซ้ำอื่น ๆ ก็คือเมื่อคุณอ่านโค้ดมันจะบอกคุณว่ามันกำลังทำอะไรอยู่และจะเกิดอะไรขึ้นต่อไป .
นี่คือตัวอย่างโลกแห่งความเป็นจริงสำหรับการเรียกซ้ำ
ให้พวกเขาจินตนาการว่าพวกเขามีคอลเล็กชั่นการ์ตูนและคุณจะผสมมันทั้งหมดให้เป็นกองใหญ่ ระวัง - หากพวกเขามีคอลเล็กชันจริง ๆ พวกเขาอาจฆ่าคุณทันทีเมื่อคุณพูดถึงแนวคิดที่จะทำเช่นนั้น
ตอนนี้ให้พวกเขาจัดเรียงการ์ตูนกองใหญ่ที่ยังไม่เรียงด้วยความช่วยเหลือของคู่มือนี้:
Manual: How to sort a pile of comics
Check the pile if it is already sorted. If it is, then done.
As long as there are comics in the pile, put each one on another pile,
ordered from left to right in ascending order:
If your current pile contains different comics, pile them by comic.
If not and your current pile contains different years, pile them by year.
If not and your current pile contains different tenth digits, pile them
by this digit: Issue 1 to 9, 10 to 19, and so on.
If not then "pile" them by issue number.
Refer to the "Manual: How to sort a pile of comics" to separately sort each
of the new piles.
Collect the piles back to a big pile from left to right.
Done.
สิ่งที่ดีที่นี่คือ: เมื่อพวกเขาลงไปที่ประเด็นเดียวพวกเขามี "กรอบสแต็ค" เต็มรูปแบบที่มีกองท้องถิ่นมองเห็นได้ก่อนที่พวกเขาอยู่บนพื้นดิน แจกคู่มือให้พวกเขาหลายฉบับและแยกแต่ละระดับด้วยเครื่องหมายที่คุณอยู่ในระดับนี้ (เช่นสถานะของตัวแปรท้องถิ่น) เพื่อให้คุณสามารถดำเนินการต่อได้ในแต่ละเสร็จ
นั่นคือสิ่งที่เกิดขึ้นโดยทั่วไปเกี่ยวกับการเรียกซ้ำ: การดำเนินการกระบวนการเดียวกันมากขึ้นเพียงแค่ในระดับรายละเอียดที่ละเอียดยิ่ง
การเรียกซ้ำเป็นวิธีที่รัดกุมมากในการแสดงสิ่งที่ต้องทำซ้ำจนกว่าจะถึงบางสิ่ง
ไม่ใช่ภาษาอังกฤษธรรมดาไม่ใช่ตัวอย่างชีวิตจริง แต่เป็นการเรียนรู้การเรียกซ้ำโดยใช้สองวิธี:
คำอธิบายที่ดีของการเรียกซ้ำคือการกระทำที่เกิดขึ้นเองจากภายใน "
พิจารณาจิตรกรวาดภาพผนังมันซ้ำเพราะการกระทำคือ "ทาสีแถบจากเพดานจรดพื้นกว่าวิ่งหนีไปทางขวาเล็กน้อยและ (ทาสีแถบจากเพดานจรดพื้นกว่าวิ่งหนีไปทางขวาเล็กน้อยและ (ทาสี ดึงจากพื้นจรดเพดานกว่าวิ่งหนีไปทางขวาและ (ฯลฯ ))) ".
ฟังก์ชัน paint () ของเขาเรียกตัวเองซ้ำแล้วซ้ำอีกเพื่อสร้างฟังก์ชัน paint_wall () ที่ใหญ่กว่าของเขา
หวังว่าจิตรกรผู้น่าสงสารคนนี้จะมีเงื่อนไขหยุดอยู่บ้าง :)