เพื่อไม่ให้เขียนบทความใหญ่ๆ สักบทความ ซึ่งจะมีตัวอย่างมากมายของการเรียกซ้ำในภาษา C++ ฉันจะเขียนอีกตัวอย่างหนึ่งของการเรียกซ้ำที่นี่ โดยทั่วไปแล้ว ผู้ที่เข้าใจพื้นฐานและการใช้การเรียกซ้ำโดยตรงในฟังก์ชันของตนสามารถข้ามเนื้อหานี้ได้ นี่คือตัวอย่างของการใช้การเรียกซ้ำ ดังในบทความ ฟังก์ชั่นใน C++ สำหรับผู้เริ่มต้น การเรียกซ้ำ
ปัญหาที่ 1 – การใช้การเรียกซ้ำ แสดงแฟกทอเรียลของตัวเลขตั้งแต่ 1 ถึง N
การเขียนโค้ด
=============================
ขั้นตอนที่ 1 เขียนโปรแกรมเปล่า
=============================
#รวม
#รวม
#รวม
int หลัก()
{
ระบบ("cls");
รับ();
กลับ 0 ;
}
สร้างโปรแกรมเปล่าแล้ว ฉันคิดว่าไม่จำเป็นต้องแสดงความคิดเห็น
ขั้นตอนที่ 2 เราเขียนเราเขียนฟังก์ชันแบบเรียกซ้ำเอง
=========================================
#รวม
#รวม
#รวม
//ฟังก์ชันเรียกซ้ำของเรา
ความเป็นจริงที่แท้จริง (int N )
{
//0! = 1, 1!=1, 2!=2, 3!=6... เพราะ ตัวเลข 2 ตัวแรกเป็นตัวเลขและไม่ได้เรียงลำดับอย่างเข้มงวด เราบังคับใช้จุดนี้ในโค้ด
ถ้าไม่มี<2
return 1
;
มิฉะนั้นให้ส่งคืน n * ข้อเท็จจริง(n–1)
//ในที่นี้ฟังก์ชันจะเรียกตัวมันเอง
}
int หลัก()
{
ระบบ("cls");
ศาล<
รับ();
กลับ 0 ;
}
ddd
============
ส่วนหลักในโปรแกรมเรียกซ้ำ C++
กลับไม่มี * ข้อเท็จจริง(n–1)
ฟังก์ชันของเราจะคำนวณใหม่เพื่อให้ได้ค่าก่อนหน้า ค่าจริงคือพารามิเตอร์ที่ส่งไป nจากจุดที่เรียกใช้ฟังก์ชัน จุดประสงค์ของการเรียกใช้ฟังก์ชันของเราคือการเรียกใช้จากบล็อกหลักของโปรแกรม ในกรณีของเรา เราเรียกมันจากฟังก์ชัน int หลัก()
ทำไมฉันถึงเขียนไม่ใช่อันถัดไป แต่อันก่อนหน้า? เมื่อคูณตัวเลขแล้ว 0 * 1 แรกนี่คือค่าปัจจุบันของเรา 1 และศูนย์คือค่าการคำนวณก่อนหน้า นี่คือสาระสำคัญทั้งหมดของการเรียกซ้ำ เราคำนวณมูลค่าปัจจุบันโดยใช้ค่าก่อนหน้า ในขณะที่ค่าก่อนหน้าจะได้มาจากการคำนวณเดียวกัน คอมไพเลอร์จะคำนวณค่าก่อนหน้าและเก็บค่านี้ไว้ในหน่วยความจำ สิ่งที่เราต้องทำคือให้คำแนะนำ
- ด้วยคุณสมบัติของคอมไพเลอร์นี้ ฟังก์ชันที่พบกับคำสั่งให้เรียกตัวเอง (ในกรณีของเรา ข้อเท็จจริง(n–1)
) จะไม่เขียนทับพารามิเตอร์ที่ส่งไปให้ nเพื่อคำนวณฟังก์ชัน พารามิเตอร์ที่ส่งผ่านไปยัง nมันยังคงอยู่ในความทรงจำ ในกรณีนี้ จะมีการกำหนดพื้นที่หน่วยความจำอื่นเพิ่มเติม โดยที่ฟังก์ชันแบบเรียกซ้ำของเราจะทำการคำนวณแบบเรียกซ้ำเพื่อให้ได้ผลลัพธ์ก่อนหน้า
ขอให้โปรแกรมเมอร์ยกโทษให้ฉันสำหรับการตัดสินที่เคี้ยวเอื้องเช่นนี้ นี่เป็นวิธีที่ผู้เริ่มต้นรับรู้ถึงการเรียกซ้ำโดยประมาณ
ฉันหวังว่าบล็อก C++ สำหรับผู้เริ่มต้นจะมีประโยชน์กับบางคนและช่วยให้ใครบางคนเข้าใจแนวคิดพื้นฐานของฟังก์ชันแบบเรียกซ้ำใน C++
บันทึก. ในบทความนี้ เช่นเดียวกับในบทความก่อนหน้านี้ ฉันไม่ได้คำนวณจาก 1 ถึง N แต่เป็นค่าที่ป้อนภายในโปรแกรม ประเด็นก็คือฉันไม่ต้องการเขียนโค้ดเพิ่มอีกบรรทัดและสันนิษฐานว่าผู้ใช้มีความชำนาญในการป้อนข้อมูลและแสดงข้อมูลบนหน้าจออยู่แล้ว
สวัสดีฮับราฮาเบอร์!
ในบทความนี้เราจะพูดถึงปัญหาการเรียกซ้ำและวิธีแก้ปัญหา
สั้น ๆ เกี่ยวกับการเรียกซ้ำ
การเรียกซ้ำเป็นปรากฏการณ์ทั่วไปที่เกิดขึ้นไม่เพียงแต่ในสาขาวิทยาศาสตร์เท่านั้น แต่ยังเกิดขึ้นในชีวิตประจำวันด้วย ตัวอย่างเช่น เอฟเฟ็กต์ Droste, สามเหลี่ยม Sierpinski เป็นต้น วิธีหนึ่งในการดูการเรียกซ้ำคือการชี้กล้องเว็บไปที่หน้าจอคอมพิวเตอร์ โดยธรรมชาติแล้วจะต้องเปิดกล้องขึ้นมาก่อน ดังนั้นกล้องจะบันทึกภาพหน้าจอคอมพิวเตอร์และแสดงบนหน้าจอนี้ก็จะมีลักษณะคล้ายวงปิด ผลก็คือเราจะสังเกตเห็นบางสิ่งที่คล้ายกับอุโมงค์ในการเขียนโปรแกรม การเรียกซ้ำมีความสัมพันธ์อย่างใกล้ชิดกับฟังก์ชัน ต้องขอบคุณฟังก์ชันในการเขียนโปรแกรมที่มีทั้งฟังก์ชันการเรียกซ้ำหรือฟังก์ชันเรียกซ้ำ พูดง่ายๆ ก็คือ การเรียกซ้ำคือคำจำกัดความของส่วนหนึ่งของฟังก์ชัน (วิธีการ) ผ่านตัวมันเอง นั่นคือ เป็นฟังก์ชันที่เรียกตัวเองโดยตรง (ในร่างกาย) หรือโดยอ้อม (ผ่านฟังก์ชันอื่น)
มีการพูดถึงเรื่องการเรียกซ้ำมากมาย นี่คือแหล่งข้อมูลที่ดีบางส่วน:
- ปัญหาการเรียกซ้ำและการเรียกซ้ำ พื้นที่การประยุกต์ใช้การเรียกซ้ำ
งาน
เมื่อเรียนรู้การเรียกซ้ำ วิธีที่มีประสิทธิภาพที่สุดในการทำความเข้าใจการเรียกซ้ำคือการแก้ปัญหาวิธีแก้ปัญหาการเรียกซ้ำ?
ก่อนอื่น คุณต้องเข้าใจว่าการเรียกซ้ำนั้นเป็นการกระทำที่มากเกินไป โดยทั่วไปแล้ว ทุกสิ่งที่ถูกแก้ไขแบบวนซ้ำสามารถแก้ไขได้แบบวนซ้ำ นั่นคือ การใช้ฟังก์ชันแบบวนซ้ำจากเครือข่าย
อัลกอริธึมใดๆ ที่นำมาใช้ในรูปแบบเรียกซ้ำสามารถเขียนใหม่ได้ในรูปแบบวนซ้ำและในทางกลับกัน คำถามยังคงอยู่ว่าสิ่งนี้จำเป็นหรือไม่และจะมีประสิทธิภาพเพียงใด
สามารถให้ข้อโต้แย้งต่อไปนี้เพื่อพิสูจน์สิ่งนี้
ประการแรก เราสามารถจำคำจำกัดความของการเรียกซ้ำและการวนซ้ำได้ การเรียกซ้ำเป็นวิธีการจัดการการประมวลผลข้อมูลที่โปรแกรมเรียกตัวเองโดยตรงหรือด้วยความช่วยเหลือของโปรแกรมอื่น การวนซ้ำเป็นวิธีการจัดการการประมวลผลข้อมูลซึ่งมีการกระทำบางอย่างซ้ำหลายครั้งโดยไม่ทำให้เกิดการเรียกโปรแกรมแบบเรียกซ้ำ
หลังจากนั้นเราสามารถสรุปได้ว่าพวกมันใช้แทนกันได้ แต่ต้นทุนและความเร็วไม่เท่ากันเสมอไป เพื่อพิสูจน์สิ่งนี้ เราสามารถยกตัวอย่างต่อไปนี้: มีฟังก์ชันที่ในการจัดระเบียบอัลกอริธึมบางอย่าง มีการวนซ้ำที่ดำเนินการตามลำดับของการกระทำ ขึ้นอยู่กับค่าปัจจุบันของตัวนับ (อาจไม่ขึ้นอยู่กับ มัน). เนื่องจากมีวัฏจักร หมายความว่าร่างกายจะทำซ้ำลำดับของการกระทำ นั่นคือการวนซ้ำของวัฏจักร คุณสามารถย้ายการดำเนินการไปยังรูทีนย่อยที่แยกจากกัน และส่งผ่านค่าตัวนับได้ ถ้ามี เมื่อเสร็จสิ้นการดำเนินการของรูทีนย่อย เราจะตรวจสอบเงื่อนไขในการดำเนินการลูป และหากเป็นจริง เราจะดำเนินการเรียกรูทีนย่อยใหม่ หากเป็นเท็จ เราจะดำเนินการให้เสร็จสิ้น เพราะ เราวางเนื้อหาทั้งหมดของลูปไว้ในรูทีนย่อย ซึ่งหมายความว่าเงื่อนไขในการดำเนินการวนซ้ำจะถูกวางไว้ในรูทีนย่อยด้วย และสามารถรับได้ผ่านค่าที่ส่งคืนของฟังก์ชัน พารามิเตอร์ที่ส่งผ่านโดยการอ้างอิงหรือตัวชี้ไปยังรูทีนย่อย ตลอดจนตัวแปรทั่วโลก นอกจากนี้ มันเป็นเรื่องง่ายที่จะแสดงให้เห็นว่าการเรียกไปยังรูทีนย่อยที่กำหนดจากลูปสามารถแปลงเป็นการเรียกหรือไม่เรียก (ส่งคืนค่าหรือเพียงแค่ทำงานให้เสร็จสิ้น) ของรูทีนย่อยจากตัวมันเองได้อย่างง่ายดาย โดยมีเงื่อนไขบางประการ (ที่ ก่อนหน้านี้อยู่ในสภาพวนซ้ำ) ทีนี้ ถ้าคุณดูโปรแกรมนามธรรมของเรา ดูเหมือนว่าการส่งค่าไปยังรูทีนย่อยคร่าวๆ แล้วใช้งาน ซึ่งรูทีนย่อยจะเปลี่ยนเมื่อเสร็จสิ้น เช่น เราแทนที่การวนซ้ำด้วยการเรียกซ้ำไปยังรูทีนย่อยเพื่อแก้ไขอัลกอริทึมที่กำหนด
งานในการนำการเรียกซ้ำมาสู่แนวทางวนซ้ำนั้นมีความสมมาตร
โดยสรุป เราสามารถแสดงความคิดต่อไปนี้: สำหรับแต่ละแนวทางจะมีประเภทของงานของตัวเอง ซึ่งถูกกำหนดโดยข้อกำหนดเฉพาะสำหรับงานเฉพาะ
คุณสามารถหาข้อมูลเพิ่มเติมเกี่ยวกับเรื่องนี้ได้
เช่นเดียวกับการแจงนับ (วงจร) การเรียกซ้ำต้องมีเงื่อนไขการหยุด - กรณีพื้นฐาน (ไม่เช่นนั้น เช่นเดียวกับวงจร การเรียกซ้ำจะทำงานตลอดไป - อนันต์) เงื่อนไขนี้เป็นกรณีที่การเรียกซ้ำดำเนินไป (ขั้นตอนการเรียกซ้ำ) ในแต่ละขั้นตอน ฟังก์ชันแบบเรียกซ้ำจะถูกเรียกจนกว่าการเรียกครั้งถัดไปจะทริกเกอร์เงื่อนไขพื้นฐานและการเรียกซ้ำจะหยุด (หรือค่อนข้างจะกลับไปสู่การเรียกฟังก์ชันครั้งล่าสุด) วิธีแก้ปัญหาทั้งหมดอยู่ที่การแก้ไขกรณีพื้นฐาน ในกรณีที่มีการเรียกใช้ฟังก์ชันแบบเรียกซ้ำเพื่อแก้ไขปัญหาที่ซับซ้อน (ไม่ใช่กรณีพื้นฐาน) จะมีการดำเนินการเรียกหรือขั้นตอนแบบเรียกซ้ำจำนวนหนึ่งเพื่อลดปัญหาให้เหลือปัญหาที่ง่ายขึ้น และต่อๆ ไปจนกว่าเราจะได้วิธีแก้ปัญหาเบื้องต้น
ดังนั้นฟังก์ชันแบบเรียกซ้ำจึงประกอบด้วย
- การหยุดสภาพหรือกรณีพื้นฐาน
- เงื่อนไขการต่อเนื่องหรือขั้นตอนการเรียกซ้ำเป็นวิธีหนึ่งในการลดปัญหาให้เกิดปัญหาที่ง่ายขึ้น
โซลูชันคลาสสาธารณะ ( การเรียกซ้ำ int แบบคงที่สาธารณะ (int n) ( // เงื่อนไขการออก // กรณีพื้นฐาน // เมื่อใดที่จะหยุดการเรียกซ้ำซ้ำ ถ้า (n == 1) ( ส่งคืน 1; ) // ขั้นตอนการเรียกซ้ำ / การส่งคืนเงื่อนไขการเรียกซ้ำ การเรียกซ้ำ ( n - 1) * n; ) public static void main (String args) ( System.out.println (recursion (5)); // เรียกใช้ฟังก์ชันแบบเรียกซ้ำ ) )
เงื่อนไขพื้นฐานคือเงื่อนไขเมื่อ n=1 เนื่องจากเรารู้ว่า 1!=1 และคำนวณ 1! เราไม่ต้องการอะไรเลย ในการคำนวณ 2! เราสามารถใช้ 1! เช่น 2!=1!*2. ในการคำนวณ 3! เราต้องการ 2!*3... เพื่อคำนวณ n! เราต้องการ (n-1)!*n นี่คือขั้นตอนการเรียกซ้ำ กล่าวอีกนัยหนึ่ง หากต้องการหาค่าแฟกทอเรียลของตัวเลข n ก็เพียงพอที่จะคูณค่าแฟกทอเรียลของตัวเลขก่อนหน้าด้วย n
แท็ก:
- การเรียกซ้ำ
- งาน
- ชวา
การเรียกซ้ำคือเมื่อรูทีนย่อยเรียกตัวเอง เมื่อต้องเผชิญกับการออกแบบอัลกอริทึมดังกล่าวเป็นครั้งแรก คนส่วนใหญ่ประสบปัญหาบางอย่าง แต่เมื่อฝึกฝนเพียงเล็กน้อย การเรียกซ้ำจะกลายเป็นเครื่องมือที่เข้าใจได้และมีประโยชน์มากในคลังแสงการเขียนโปรแกรมของคุณ
1. สาระสำคัญของการเรียกซ้ำ
ขั้นตอนหรือฟังก์ชันอาจมีการเรียกไปยังขั้นตอนหรือฟังก์ชันอื่น ขั้นตอนยังสามารถเรียกตัวเองได้ ไม่มีความขัดแย้งที่นี่ - คอมพิวเตอร์จะรันคำสั่งที่พบในโปรแกรมตามลำดับเท่านั้น และหากพบการเรียกโพรซีเดอร์ เครื่องก็จะเริ่มดำเนินการตามโพรซีเดอร์นี้ ไม่สำคัญว่าขั้นตอนใดจะสั่งให้ทำสิ่งนี้
ตัวอย่างของขั้นตอนการเรียกซ้ำ:
ขั้นตอน Rec (a: จำนวนเต็ม); เริ่มต้นถ้า >
ลองพิจารณาว่าจะเกิดอะไรขึ้นหากมีการเรียกเช่น แบบฟอร์ม Rec(3) ในโปรแกรมหลัก ด้านล่างนี้เป็นผังงานที่แสดงลำดับการดำเนินการของคำสั่ง
ข้าว. 1. บล็อกไดอะแกรมของโพรซีเดอร์แบบเรียกซ้ำ
Procedure Rec ถูกเรียกด้วยพารามิเตอร์ a = 3 โดยมีการเรียกไปยังโพรซีเดอร์ Rec ด้วยพารามิเตอร์ a = 2 การเรียกก่อนหน้านี้ยังไม่เสร็จสมบูรณ์ ดังนั้นคุณคงจินตนาการได้ว่ามีการสร้างโพรซีเดอร์อื่นขึ้นและโพรซีเดอร์แรกยังทำงานไม่เสร็จจนกว่า มันเสร็จสิ้น กระบวนการเรียกสิ้นสุดเมื่อพารามิเตอร์ a = 0 ณ จุดนี้ กระบวนการ 4 อินสแตนซ์จะถูกดำเนินการพร้อมกัน เรียกว่าจำนวนขั้นตอนที่ดำเนินการพร้อมกัน ความลึกของการเรียกซ้ำ.
ขั้นตอนที่สี่ที่เรียกว่า (Rec(0)) จะพิมพ์ตัวเลข 0 และทำงานให้เสร็จ หลังจากนั้น การควบคุมจะกลับสู่ขั้นตอนที่เรียกว่า (Rec(1)) และหมายเลข 1 จะถูกพิมพ์ และต่อๆ ไปจนกว่าขั้นตอนทั้งหมดจะเสร็จสมบูรณ์ การโทรครั้งแรกจะพิมพ์ตัวเลขสี่ตัว: 0, 1, 2, 3
ภาพอีกภาพของสิ่งที่เกิดขึ้นจะแสดงอยู่ในรูปที่. 2.
ข้าว. 2. การดำเนินการขั้นตอน Rec ด้วยพารามิเตอร์ 3 ประกอบด้วยการดำเนินการขั้นตอน Rec ด้วยพารามิเตอร์ 2 และการพิมพ์หมายเลข 3 ในทางกลับกัน การดำเนินการขั้นตอน Rec ด้วยพารามิเตอร์ 2 ประกอบด้วยการดำเนินการขั้นตอน Rec ด้วยพารามิเตอร์ 1 และการพิมพ์หมายเลข 2 เป็นต้น .
เพื่อเป็นแบบฝึกหัดของคุณเอง ให้พิจารณาว่าจะเกิดอะไรขึ้นเมื่อคุณเรียก Rec(4) นอกจากนี้ ให้พิจารณาสิ่งที่จะเกิดขึ้นหากคุณเรียกขั้นตอน Rec2(4) ด้านล่าง โดยที่ตัวดำเนินการกลับรายการ
ขั้นตอน Rec2(a: จำนวนเต็ม); เริ่มเขียน (a);
ถ้า a>0 แล้ว Rec2(a-1); จบ;
โปรดทราบว่าในตัวอย่างเหล่านี้ การเรียกซ้ำอยู่ในคำสั่งแบบมีเงื่อนไข นี่เป็นเงื่อนไขที่จำเป็นสำหรับการเรียกซ้ำสิ้นสุดลง โปรดทราบด้วยว่าโพรซีเดอร์เรียกตัวเองด้วยพารามิเตอร์ที่แตกต่างจากพารามิเตอร์ที่ถูกเรียก หากขั้นตอนไม่ได้ใช้ตัวแปรโกลบอล ก็จำเป็นเช่นกัน เพื่อไม่ให้การเรียกซ้ำดำเนินต่อไปอย่างไม่มีกำหนด รูปแบบที่ซับซ้อนกว่านี้เล็กน้อยเป็นไปได้: ฟังก์ชัน A เรียกฟังก์ชัน B ซึ่งจะเรียก A ซึ่งเรียกว่าการเรียกซ้ำที่ซับซ้อน
ขั้นตอน A(n: จำนวนเต็ม); (คำอธิบายไปข้างหน้า (ส่วนหัว) ของขั้นตอนแรก) ขั้นตอน B(n: จำนวนเต็ม); (คำอธิบายไปข้างหน้าของขั้นตอนที่สอง) ขั้นตอน A(n: จำนวนเต็ม); (คำอธิบายแบบเต็มของขั้นตอน A) เริ่มต้น writeln(n);
บี(n-1); จบ; ขั้นตอน B (n: จำนวนเต็ม); (คำอธิบายแบบเต็มของขั้นตอน B) เริ่มต้น writeln(n);
ถ้าไม่มี
การประกาศส่งต่อของขั้นตอน B ช่วยให้สามารถเรียกได้จากขั้นตอน A ไม่จำเป็นต้องประกาศล่วงหน้าของขั้นตอน A ในตัวอย่างนี้ และถูกเพิ่มเข้ามาเพื่อเหตุผลด้านสุนทรียภาพ
หากการเรียกซ้ำแบบธรรมดาสามารถเปรียบได้กับอูโรโบรอส (รูปที่ 3) ภาพของการเรียกซ้ำที่ซับซ้อนก็สามารถดึงมาจากบทกวีของเด็ก ๆ ที่มีชื่อเสียงซึ่ง“ หมาป่าหวาดกลัวและกินกันเอง” ลองนึกภาพหมาป่าสองตัวกินกัน แล้วคุณจะเข้าใจการเรียกซ้ำที่ซับซ้อน
ข้าว. 3. Ouroboros - งูกลืนหางของมันเอง ดึงมาจากบทความเกี่ยวกับการเล่นแร่แปรธาตุ “Synosius” โดย Theodore Pelecanos (1478)
ข้าว. 4. การเรียกซ้ำแบบซับซ้อน
3. การจำลองลูปโดยใช้การเรียกซ้ำ
ถ้าโพรซีเดอร์เรียกตัวเอง มันจะทำให้คำสั่งที่มีอยู่ถูกดำเนินการอีกครั้ง คล้ายกับการวนซ้ำ ภาษาโปรแกรมบางภาษาไม่มีโครงสร้างการวนซ้ำเลย ทำให้โปรแกรมเมอร์ต้องจัดการการทำซ้ำโดยใช้การเรียกซ้ำ (เช่น Prolog โดยที่การเรียกซ้ำเป็นเทคนิคการเขียนโปรแกรมพื้นฐาน)
ตัวอย่างเช่น ลองจำลองการทำงานของ for for loop ในการทำเช่นนี้ เราจำเป็นต้องมีตัวแปรตัวนับขั้นตอน ซึ่งสามารถนำไปใช้ได้ เช่น เป็นพารามิเตอร์ขั้นตอน ตัวอย่างที่ 1 ขั้นตอน LoopImitation (i, n: จำนวนเต็ม); (พารามิเตอร์แรกคือตัวนับขั้นตอน พารามิเตอร์ที่สองคือจำนวนขั้นตอนทั้งหมด) เริ่มต้น writeln("Hello N ", i); //ต่อไปนี้เป็นคำแนะนำใดๆ ที่จะทำซ้ำหากฉันผลลัพธ์ของการเรียกแบบฟอร์ม LoopImitation(1, 10) จะเป็นการดำเนินการตามคำสั่งสิบครั้งโดยตัวนับเปลี่ยนจาก 1 เป็น 10 ใน ในกรณีนี้ สวัสดี N.2 สวัสดี N10 โดยทั่วไปจะเห็นว่าพารามิเตอร์ของขั้นตอนเป็นขีดจำกัดในการเปลี่ยนแปลงค่าตัวนับได้ไม่ยาก
คุณสามารถสลับการโทรซ้ำและคำแนะนำในการทำซ้ำได้ ดังตัวอย่างต่อไปนี้ ในกรณีนี้ การเรียกโพรซีเจอร์แบบเรียกซ้ำจะเกิดขึ้นก่อนที่คำสั่งจะเริ่มดำเนินการ ก่อนอื่นอินสแตนซ์ใหม่ของโพรซีเดอร์จะเรียกอินสแตนซ์อื่นและต่อไปเรื่อย ๆ จนกว่าเราจะถึงค่าสูงสุดของตัวนับ หลังจากนี้เท่านั้น กระบวนการสุดท้ายที่ถูกเรียกจึงจะดำเนินการตามคำสั่ง จากนั้นขั้นตอนที่สองต่อสุดท้ายจะดำเนินการตามคำสั่ง ฯลฯ ผลลัพธ์ของการเรียก LoopImitation2(1, 10) จะเป็นการพิมพ์คำทักทายในลำดับย้อนกลับ: สวัสดี N10 ถ้าเราจินตนาการถึงสายโซ่ของโพรซีเดอร์ที่ถูกเรียกซ้ำ ดังนั้นในตัวอย่างที่ 1 เราจะผ่านมันจากโพรซีเจอร์ที่ถูกเรียกก่อนหน้านี้ไปยังโพรซีเดอร์ที่เรียกในภายหลัง ในตัวอย่างที่ 2 ตรงกันข้ามจากภายหลังไปก่อนหน้า ในที่สุด สามารถทำการเรียกซ้ำระหว่างสองบล็อคคำสั่งได้ ตัวอย่างเช่น: ขั้นตอน LoopImitation3 (i, n: จำนวนเต็ม); เริ่มเขียน ("Hello N ", i); (บล็อคคำสั่งแรกอาจอยู่ที่นี่) ถ้าฉัน ในที่นี้ คำสั่งจากบล็อกแรกจะดำเนินการตามลำดับก่อน จากนั้นคำสั่งจากบล็อกที่สองจะดำเนินการในลำดับย้อนกลับ เมื่อเรียก LoopImitation3(1, 10) เราจะได้รับ: ในกรณีนี้ จะใช้เวลาสองลูปในการทำสิ่งเดียวกันโดยไม่มีการเรียกซ้ำ คุณสามารถใช้ประโยชน์จากข้อเท็จจริงที่ว่าการดำเนินการส่วนต่างๆ ของขั้นตอนเดียวกันนั้นมีการเว้นระยะห่างออกไปเมื่อเวลาผ่านไป ตัวอย่างเช่น: ตัวอย่างที่ 3: การแปลงตัวเลขเป็นไบนารี
ดังที่ทราบกันดีว่าการได้รับเลขฐานสองนั้นเกิดขึ้นโดยการหารด้วยเศษด้วยฐานของระบบตัวเลข 2 หากมีตัวเลข ตัวเลขหลักสุดท้ายของเลขฐานสองจะเท่ากับ หารทั้งส่วนด้วย 2: เราได้ตัวเลขที่มีการแทนเลขฐานสองเหมือนกัน แต่ไม่มีเลขหลักสุดท้าย ดังนั้นจึงเพียงพอที่จะทำซ้ำการดำเนินการทั้งสองข้างต้นจนกว่าฟิลด์การหารถัดไปจะได้รับส่วนจำนวนเต็มเท่ากับ 0 หากไม่มีการเรียกซ้ำจะมีลักษณะดังนี้: ในขณะที่ x>0 เริ่มต้น c:=x mod 2; x:=x div 2; เขียน(ค); จบ; ปัญหาที่นี่คือตัวเลขของการเป็นตัวแทนไบนารี่ถูกคำนวณในลำดับย้อนกลับ (ล่าสุดอยู่ก่อน) หากต้องการพิมพ์ตัวเลขในรูปแบบปกติ คุณจะต้องจำตัวเลขทั้งหมดในองค์ประกอบอาร์เรย์และพิมพ์เป็นวงแยก โดยทั่วไปแล้ว เราไม่ได้รับรางวัลใด ๆ ตัวเลขของการแทนค่าไบนารี่จะถูกเก็บไว้ในตัวแปรท้องถิ่น ซึ่งจะแตกต่างกันไปในแต่ละอินสแตนซ์ที่รันอยู่ของโพรซีเดอร์แบบเรียกซ้ำ นั่นคือไม่สามารถบันทึกหน่วยความจำได้ ในทางตรงกันข้าม เราเปลืองหน่วยความจำเพิ่มเติมที่จัดเก็บตัวแปรท้องถิ่น x จำนวนมาก อย่างไรก็ตาม โซลูชันนี้ดูสวยงามสำหรับฉัน ลำดับของเวกเตอร์กล่าวได้ว่าได้รับจากความสัมพันธ์ที่เกิดซ้ำ ถ้าให้เวกเตอร์เริ่มต้นและการพึ่งพาเชิงฟังก์ชันของเวกเตอร์ที่ตามมากับเวกเตอร์ก่อนหน้า ตัวอย่างง่ายๆ ของปริมาณที่คำนวณโดยใช้ความสัมพันธ์ที่เกิดซ้ำคือแฟกทอเรียล แฟกทอเรียลถัดไปสามารถคำนวณได้จากค่าก่อนหน้าดังนี้: โดยการแนะนำสัญกรณ์ เราได้รับความสัมพันธ์: เวกเตอร์จากสูตร (1) สามารถตีความได้ว่าเป็นชุดของค่าตัวแปร จากนั้นการคำนวณองค์ประกอบที่ต้องการของลำดับจะประกอบด้วยการอัปเดตค่าซ้ำหลายครั้ง โดยเฉพาะอย่างยิ่งสำหรับแฟกทอเรียล: X:= 1; สำหรับ i:= 2 ถึง n ทำ x:= x * i; เขียน(x); การอัปเดตแต่ละครั้ง (x:= x * i) ถูกเรียก การวนซ้ำและกระบวนการของการวนซ้ำก็คือ การวนซ้ำ. อย่างไรก็ตาม ขอให้เราทราบว่าความสัมพันธ์ (1) เป็นคำจำกัดความแบบเรียกซ้ำเพียงอย่างเดียวของลำดับ และการคำนวณองค์ประกอบที่ n จริงๆ แล้วคือการรับฟังก์ชัน f ซ้ำจากตัวมันเอง: โดยเฉพาะอย่างยิ่งสำหรับแฟกทอเรียลสามารถเขียนได้: ฟังก์ชันแฟกทอเรียล (n: จำนวนเต็ม): จำนวนเต็ม; เริ่มต้นถ้า n > 1 แล้วแฟกทอเรียล:= n * แฟกทอเรียล(n-1) อย่างอื่นแฟกทอเรียล:= 1; จบ; ควรเข้าใจว่าการเรียกฟังก์ชันมีค่าใช้จ่ายเพิ่มเติม ดังนั้นตัวเลือกแรกในการคำนวณแฟกทอเรียลจะเร็วขึ้นเล็กน้อย โดยทั่วไป โซลูชันแบบวนซ้ำจะทำงานได้เร็วกว่าโซลูชันแบบเรียกซ้ำ ก่อนที่จะไปยังสถานการณ์ที่การเรียกซ้ำมีประโยชน์ ลองดูอีกตัวอย่างหนึ่งที่ไม่ควรใช้ ลองพิจารณากรณีพิเศษของความสัมพันธ์ที่เกิดซ้ำเมื่อค่าถัดไปในลำดับไม่ได้ขึ้นอยู่กับค่าเดียว แต่ขึ้นอยู่กับค่าก่อนหน้าหลายค่าในคราวเดียว ตัวอย่างคือลำดับฟีโบนัชชีที่มีชื่อเสียง ซึ่งแต่ละองค์ประกอบถัดไปคือผลรวมของสองลำดับก่อนหน้า: ด้วยแนวทาง "หน้าผาก" คุณสามารถเขียน: ฟังก์ชั่น Fib(n: จำนวนเต็ม): จำนวนเต็ม; เริ่มต้นถ้า n > 1 แล้ว Fib:= Fib(n-1) + Fib(n-2) else Fib:= 1; จบ; การเรียก Fib แต่ละครั้งจะสร้างสำเนาของตัวเองขึ้นมาสองชุด แต่ละสำเนาจะสร้างเพิ่มอีกสองชุด และต่อๆ ไป จำนวนการดำเนินการเพิ่มขึ้นตามจำนวน nเอ็กซ์โพเนนเชียล แม้ว่าจะมีวิธีแก้ปัญหาแบบวนซ้ำเชิงเส้นก็ตาม nจำนวนการดำเนินงาน อันที่จริง ตัวอย่างข้างต้นไม่ได้สอนเราเลย เมื่อไรไม่ควรใช้การเรียกซ้ำมิฉะนั้น ยังไงมันไม่ควรใช้ ท้ายที่สุด หากมีวิธีแก้ปัญหาแบบวนซ้ำอย่างรวดเร็ว (แบบวนซ้ำ) การวนซ้ำเดียวกันสามารถนำไปใช้ได้โดยใช้ขั้นตอนหรือฟังก์ชันแบบเรียกซ้ำ ตัวอย่างเช่น: // x1, x2 – เงื่อนไขเริ่มต้น (1, 1) // n – จำนวนของฟังก์ชันตัวเลขฟีโบนัชชีที่ต้องการ Fib(x1, x2, n: จำนวนเต็ม): จำนวนเต็ม; var x3: จำนวนเต็ม; เริ่มถ้า n > 1 แล้วเริ่ม x3:= x2 + x1; x1:= x2; x2:= x3; ตอแหล:= ตอแหล(x1, x2, n-1);จบอย่างอื่น Fib:= x2; จบ; ถึงกระนั้น วิธีแก้ปัญหาแบบวนซ้ำยังดีกว่า คำถามคือ เมื่อใดที่ควรใช้การเรียกซ้ำในกรณีนี้ใดๆ ขั้นตอนการเกิดซ้ำและฟังก์ชันที่มีการเรียกซ้ำเพียงครั้งเดียวจะถูกแทนที่ด้วยลูปวนซ้ำอย่างง่ายดาย เพื่อให้ได้สิ่งที่ไม่มีคู่ที่ไม่เรียกซ้ำแบบธรรมดา คุณต้องหันไปใช้โพรซีเดอร์และฟังก์ชันที่เรียกตัวเองว่าสองครั้งขึ้นไป ในกรณีนี้ ชุดของโพรซีเดอร์ที่ถูกเรียกจะไม่สร้างเป็นลูกโซ่อีกต่อไป ดังในรูป 1แต่เป็นต้นไม้ทั้งต้น มีปัญหามากมายเมื่อ ควรจะจัดแบบนี้ สำหรับพวกเขา การเรียกซ้ำจะเป็นวิธีที่ง่ายที่สุดและ โซลูชั่น
5. ต้นไม้ พื้นฐานทางทฤษฎีสำหรับฟังก์ชันเกิดซ้ำที่เรียกตัวเองมากกว่าหนึ่งครั้งคือสาขาวิชาคณิตศาสตร์แยกที่ศึกษาต้นไม้ 5.1. คำจำกัดความพื้นฐาน วิธีพรรณนาต้นไม้ ก) มีโหนดพิเศษหนึ่งโหนดที่เรียกว่ารากของทรีนี้ b) โหนดที่เหลือ (ไม่รวมรูท) จะอยู่ในเซตย่อยที่แยกจากกันแบบคู่ ซึ่งแต่ละโหนดจะเป็นต้นไม้ ต้นไม้เรียกว่าต้นไม้ย่อย ของต้นไม้ต้นนี้ ในรูป รูปที่ 3 แสดงต้นไม้ที่มีเจ็ดโหนด แม้ว่าต้นไม้ธรรมดาจะเติบโตจากล่างขึ้นบน แต่ก็เป็นธรรมเนียมที่จะดึงต้นไม้กลับกัน เมื่อวาดไดอะแกรมด้วยมือ วิธีนี้สะดวกกว่าอย่างเห็นได้ชัด เนื่องจากความไม่สอดคล้องกันนี้ บางครั้งความสับสนจึงเกิดขึ้นเมื่อโหนดหนึ่งถูกกล่าวว่าอยู่เหนือหรือต่ำกว่าอีกโหนดหนึ่ง ด้วยเหตุนี้ การใช้คำศัพท์ที่ใช้ในการอธิบายจึงสะดวกกว่า ต้นไม้ครอบครัวเรียกโหนดใกล้กับบรรพบุรุษรูทและลูกหลานของโหนดที่อยู่ห่างไกลมากขึ้น ในเชิงกราฟิก ต้นไม้สามารถพรรณนาได้ด้วยวิธีอื่น บางส่วนจะแสดงในรูป 4. ตามคำนิยาม ต้นไม้คือระบบของชุดที่ซ้อนกัน โดยที่ชุดเหล่านี้ไม่ได้ตัดกันหรือรวมกันทั้งหมด ฉากดังกล่าวสามารถแสดงเป็นขอบเขตบนเครื่องบินได้ (รูปที่ 4a) ในรูป 4b, เซตที่ซ้อนกันไม่ได้อยู่บนระนาบ แต่จะยาวออกเป็นเส้นเดียว ข้าว. 4b ยังสามารถใช้เป็นแผนภาพของสูตรพีชคณิตบางสูตรที่มีวงเล็บซ้อนกันได้ ข้าว. 4b ให้อีกหนึ่งอัน วิธียอดนิยมภาพโครงสร้างต้นไม้ในรูปแบบของรายการขั้นบันได ข้าว. 4. วิธีอื่นในการนำเสนอโครงสร้างต้นไม้: (a) ชุดที่ซ้อนกัน; (b) วงเล็บซ้อนกัน; (ค) รายการสัมปทาน รายการที่เซมีความคล้ายคลึงกันอย่างเห็นได้ชัดกับวิธีการจัดรูปแบบ รหัสโปรแกรม- แท้จริงแล้ว โปรแกรมที่เขียนภายในกรอบของกระบวนทัศน์การเขียนโปรแกรมแบบมีโครงสร้างสามารถแสดงเป็นแผนผังที่ประกอบด้วยโครงสร้างที่ซ้อนกันได้ คุณยังสามารถวาดการเปรียบเทียบระหว่างรายการขอบและ รูปร่างสารบัญในหนังสือที่ส่วนต่างๆ มีส่วนย่อย ซึ่งจะมีส่วนย่อย ฯลฯ วิธีดั้งเดิมการกำหนดหมายเลขของส่วนดังกล่าว (ส่วนที่ 1, ส่วนย่อย 1.1 และ 1.2, ส่วนย่อย 1.1.2 เป็นต้น) เรียกว่าระบบทศนิยมดิวอี้ นำไปใช้กับต้นไม้ในรูป 3 และ 4 ระบบนี้จะให้: 1. ก; 1.1B; 1.2 ซี; 1.2.1 ง; 1.2.2 จ; 1.2.3 ฟ; 1.2.3.1 กรัม; ในอัลกอริธึมทั้งหมดที่เกี่ยวข้องกับโครงสร้างต้นไม้ แนวคิดเดียวกันจะปรากฏขึ้นอย่างสม่ำเสมอ นั่นคือแนวคิด ผ่านหรือ การสำรวจต้นไม้- นี่เป็นวิธีการเยี่ยมชมโหนดต้นไม้ซึ่งแต่ละโหนดจะถูกสำรวจเพียงครั้งเดียว ส่งผลให้มีการจัดเรียงโหนดต้นไม้เป็นเส้นตรง โดยเฉพาะอย่างยิ่ง มีสามวิธี: คุณสามารถผ่านโหนดต่างๆ ในลำดับไปข้างหน้า ย้อนกลับ และสิ้นสุดได้ อัลกอริธึมการเคลื่อนที่ไปข้างหน้า: อัลกอริทึมนี้เป็นแบบเรียกซ้ำ เนื่องจากการข้ามต้นไม้มีการข้ามทรีย่อย และในทางกลับกัน พวกมันก็ถูกสำรวจโดยใช้อัลกอริธึมเดียวกัน โดยเฉพาะต้นไม้ในรูปนี้ 3 และ 4 การแวะผ่านโดยตรงให้ลำดับของโหนด: A, B, C, D, E, F, G ลำดับผลลัพธ์ที่สอดคล้องกับการแจงนับโหนดจากซ้ายไปขวาตามลำดับเมื่อแสดงต้นไม้โดยใช้วงเล็บที่ซ้อนกันและใน ระบบทศนิยมดิวอี้ เช่นเดียวกับข้อความจากบนลงล่างเมื่อนำเสนอในรูปแบบของรายการขั้นบันได เมื่อนำอัลกอริธึมนี้ไปใช้ในภาษาการเขียนโปรแกรม การกดปุ่มรูทจะสอดคล้องกับขั้นตอนหรือฟังก์ชันที่ดำเนินการบางอย่าง และการผ่านทรีย่อยจะสอดคล้องกับการเรียกซ้ำของตัวมันเอง โดยเฉพาะอย่างยิ่งสำหรับต้นไม้ไบนารี (โดยที่แต่ละโหนดมีทรีย่อยไม่เกินสองต้น) ขั้นตอนที่เกี่ยวข้องจะมีลักษณะดังนี้: // Preorder Traversal – ชื่อภาษาอังกฤษสำหรับขั้นตอนการสั่งซื้อโดยตรง PreorderTraversal((Arguments)); เริ่มต้น // ส่งผ่านรูท DoSomething ((อาร์กิวเมนต์)); // การเปลี่ยนทรีย่อยด้านซ้ายถ้า (มีทรีย่อยด้านซ้าย) จากนั้น PreorderTransversal ((อาร์กิวเมนต์ 2)); //การเปลี่ยนทรีย่อยที่ถูกต้องถ้า (มีทรีย่อยที่ถูกต้อง) จากนั้น PreorderTransversal((อาร์กิวเมนต์ 3)); จบ; ผ่านทรีย่อยถัดไปทางซ้าย และต่อไปเรื่อย ๆ จนกระทั่งทรีย่อยขวาสุดถูกสำรวจ นั่นคือ ทรีย่อยทั้งหมดจะถูกเคลื่อนที่จากซ้ายไปขวา และการกลับไปยังรากจะอยู่ระหว่างการเคลื่อนที่เหล่านี้ สำหรับต้นไม้ในรูป 3 และ 4 ให้ลำดับของโหนด: B, A, D, C, E, G, F ในขั้นตอนการเรียกซ้ำที่สอดคล้องกัน การดำเนินการจะอยู่ในช่องว่างระหว่างการเรียกซ้ำ โดยเฉพาะสำหรับต้นไม้ไบนารี: //การข้ามทรีย่อยที่ถูกต้องถ้า (มีทรีย่อยที่ถูกต้อง) จากนั้น InorderTraversal((อาร์กิวเมนต์ 3)); จบ; อัลกอริธึมการแวะผ่านลำดับสุดท้าย: // Postorder Traversal – ชื่อภาษาอังกฤษสำหรับขั้นตอนการสั่งซื้อสิ้นสุด PostorderTraversal((อาร์กิวเมนต์)); เริ่มต้น // การเดินทางทรีย่อยด้านซ้ายถ้า (มีทรีย่อยด้านซ้าย) จากนั้น PostorderTraversal ((อาร์กิวเมนต์ 2)); //ส่งผ่านรูท DoSomething((อาร์กิวเมนต์)); จบ; 5.3. การแสดงต้นไม้ในหน่วยความจำคอมพิวเตอร์หากข้อมูลบางอย่างอยู่ในโหนดต้นไม้ ก็สามารถใช้โครงสร้างข้อมูลไดนามิกที่เหมาะสมในการจัดเก็บข้อมูลได้ ใน Pascal สิ่งนี้เสร็จสิ้นโดยใช้ ประเภทตัวแปร บันทึกที่มีพอยน์เตอร์ไปยังทรีย่อยประเภทเดียวกัน ตัวอย่างเช่น ต้นไม้ไบนารีที่แต่ละโหนดมีจำนวนเต็มสามารถจัดเก็บได้โดยใช้ตัวแปรประเภท PTree ซึ่งอธิบายไว้ด้านล่าง: พิมพ์ PTree = ^TTree; TTree = บันทึก Inf: จำนวนเต็ม; LeftSubTree, RightSubTree: PTree; จบ; แต่ละโหนดมีประเภท PTree นี่คือพอยน์เตอร์ ซึ่งหมายความว่าแต่ละโหนดต้องถูกสร้างขึ้นโดยการเรียกโพรซีเดอร์ใหม่ หากโหนดเป็นโหนดปลายสุด ฟิลด์ LeftSubTree และ RightSubTree จะถูกกำหนดค่า ไม่มี พิมพ์ PTree = ^TTree;- มิฉะนั้น โหน LeftSubTree และ RightSubTree จะถูกสร้างขึ้นด้วยขั้นตอนใหม่ บันทึกดังกล่าวรายการหนึ่งแสดงอยู่ในแผนผังในรูป 5. ข้าว. 6. ต้นไม้ที่ประกอบด้วยบันทึกประเภท TTree แต่ละรายการจะเก็บตัวเลขและตัวชี้สองตัวที่สามารถมีอย่างใดอย่างหนึ่งได้ หรือที่อยู่ของบันทึกอื่นที่เป็นประเภทเดียวกัน หากคุณไม่เคยทำงานกับโครงสร้างที่ประกอบด้วยบันทึกที่มีลิงก์ไปยังบันทึกประเภทเดียวกันมาก่อน เราขอแนะนำให้คุณทำความคุ้นเคยกับเนื้อหาเกี่ยวกับ ตัวอย่างของขั้นตอนดังกล่าวซึ่งเขียนด้วยภาษา Delphi มีดังต่อไปนี้: ต้นไม้ขั้นตอน (Canvas: TCanvas; // ผืนผ้าใบที่ต้นไม้จะถูกวาด x, y: ขยาย; // พิกัดรูทมุม: ขยาย; // มุมที่ต้นไม้เติบโต TrunkLength: ขยาย; // ความยาวลำต้น n: จำนวนเต็ม / /จำนวนสาขา (เหลืออีกกี่สาย // การโทรแบบเรียกซ้ำ)); var x2, y2: ขยาย; // ท้ายลำ (จุดสาขา) เริ่มต้น x2:= x + TrunkLength * cos (มุม); y2:= y - TrunkLength * บาป (มุม); Canvas.MoveTo(รอบ(x), รอบ(y)); Canvas.LineTo(รอบ(x2), รอบ(y2)); ต้นไม้ (Canvas, x2, y2, Angle-Pi/4, 0.55*TrunkLength, n-1); โปรดทราบว่าการวาดจะดำเนินการก่อนการโทรแบบเรียกซ้ำ นั่นคือ ต้นไม้จะถูกวาดตามลำดับโดยตรง 6.2. ฮานอยทาวเวอร์ ตามตำนานในวิหารใหญ่แห่งบานารัส ใต้อาสนวิหารซึ่งทำเครื่องหมายไว้ตรงกลางของโลก มีจานทองสัมฤทธิ์ซึ่งมีแท่งเพชร 3 แท่งติดอยู่ สูง 1 ศอกและหนาเหมือนผึ้ง กาลครั้งหนึ่งนานมาแล้ว พวกภิกษุในอารามแห่งนี้ได้กระทำความผิดต่อพระพรหม พระพรหมทรงโกรธเคืองจึงทรงสร้างไม้เท้าสูง 3 อัน วางแผ่นทองคำบริสุทธิ์ 64 แผ่นไว้บนแผ่นหนึ่ง เพื่อให้แผ่นเล็กแต่ละแผ่นวางอยู่บนแผ่นที่ใหญ่กว่า ทันทีที่ดิสก์ทั้ง 64 แผ่นถูกย้ายจากไม้เท้าที่พระเจ้าพรหมวางไว้เมื่อสร้างโลกไปยังไม้เรียวอื่นหอคอยพร้อมกับวิหารจะกลายเป็นฝุ่นและโลกจะพินาศภายใต้เสียงฟ้าร้อง nกระบวนการนี้ต้องการสิ่งนั้น nดิสก์ขนาดใหญ่ขึ้น ไม่เคยพบว่าตัวเองอยู่เหนือสิ่งอื่นใดแม้แต่น้อย พระภิกษุอยู่ในความลังเลใจ ควรทำกะตามลำดับไหน? จำเป็นต้องจัดเตรียมซอฟต์แวร์เพื่อคำนวณลำดับนี้ nปริศนานี้ถูกเสนอขึ้นโดยอิสระจากพระพรหมเมื่อปลายศตวรรษที่ 19 โดยนักคณิตศาสตร์ชาวฝรั่งเศส เอดูอาร์ด ลูคัส รุ่นที่ขายมักจะใช้ดิสก์ 7-8 แผ่น (รูปที่ 7) -1 ดิสก์ n 2) กะ มาสร้างขั้นตอนแบบเรียกซ้ำที่พิมพ์ลำดับกะทั้งหมดสำหรับดิสก์ตามจำนวนที่กำหนด แต่ละครั้งที่มีการเรียกขั้นตอนดังกล่าว จะต้องพิมพ์ข้อมูลเกี่ยวกับหนึ่งกะ (จากจุดที่ 2 ของอัลกอริทึม) สำหรับการจัดเรียงใหม่จากจุด (1) และ (3) โพรซีเดอร์จะเรียกตัวเองโดยลดจำนวนดิสก์ลงหนึ่งดิสก์ //n – จำนวนดิสก์ //a, b, c – หมายเลขพิน การเลื่อนเสร็จสิ้นจากพิน a // ไปเป็นพิน b ด้วยพินเสริม c ขั้นตอนฮานอย (n, a, b, c: จำนวนเต็ม); เริ่มต้นถ้า n > 1 แล้วเริ่มฮานอย(n-1, a, c, b); writeln(a, " -> ", b); จบอย่างอื่น writeln(a, " -> ", b); จบ; โปรดสังเกตว่าชุดของกระบวนการที่เรียกซ้ำในกรณีนี้จะสร้างต้นไม้ที่เคลื่อนที่ในลำดับที่กลับกัน 6.3. การแยกวิเคราะห์นิพจน์ทางคณิตศาสตร์ (6). งานแยกวิเคราะห์คือการคำนวณค่าของนิพจน์โดยใช้สตริงที่มีอยู่ซึ่งมีนิพจน์ทางคณิตศาสตร์และค่าที่ทราบของตัวแปรที่รวมอยู่ในนั้น กระบวนการคำนวณนิพจน์ทางคณิตศาสตร์สามารถแสดงเป็นต้นไม้ไบนารีได้ อันที่จริง ตัวดำเนินการทางคณิตศาสตร์แต่ละตัว (+, –, *, /) ต้องใช้ตัวถูกดำเนินการสองตัว ซึ่งจะเป็นนิพจน์ทางคณิตศาสตร์ด้วย และด้วยเหตุนี้ จึงถือเป็นแผนผังย่อยได้ ข้าว. รูปที่ 8 แสดงตัวอย่างต้นไม้ที่สอดคล้องกับนิพจน์:ข้าว. 8. ต้นไม้ไวยากรณ์ที่สอดคล้องกัน นิพจน์ทางคณิตศาสตร์ในแผนผังดังกล่าว โหนดสุดท้ายจะเป็นตัวแปรเสมอ (ในที่นี้ x ) หรือค่าคงที่ตัวเลข และโหนดภายในทั้งหมดจะมี ตัวดำเนินการทางคณิตศาสตร์
- ในการรันโอเปอเรเตอร์ คุณต้องประเมินโอเปอเรเตอร์ของมันก่อน ดังนั้นต้นไม้ในรูปนี้จึงควรเคลื่อนที่ตามลำดับขั้ว ลำดับโหนดที่สอดคล้องกัน เรียกว่า ย้อนกลับสัญกรณ์โปแลนด์ นิพจน์ทางคณิตศาสตร์ เมื่อสร้างแผนผังไวยากรณ์คุณควรใส่ใจ คุณสมบัติถัดไป - เช่น ถ้ามีนิพจน์ – และเราจะอ่านการดำเนินการของการบวกและการลบจากซ้ายไปขวา จากนั้นแผนผังไวยากรณ์ที่ถูกต้องจะมีเครื่องหมายลบแทนเครื่องหมายบวก (รูปที่ 9a) โดยพื้นฐานแล้ว ต้นไม้นี้สอดคล้องกับนิพจน์ เป็นไปได้ที่จะทำให้การสร้างต้นไม้ง่ายขึ้นหากคุณวิเคราะห์นิพจน์ (8) ในทางกลับกัน จากขวาไปซ้าย ในกรณีนี้ ผลลัพธ์ที่ได้คือต้นไม้ที่มีรูปที่ 9b เทียบเท่ากับทรี 8a แต่ไม่จำเป็นต้องเปลี่ยนป้าย + ในทำนองเดียวกัน จากขวาไปซ้าย คุณต้องวิเคราะห์นิพจน์ที่มีตัวดำเนินการการคูณและการหารเมื่ออ่านจากซ้ายไปขวา (a) และจากขวาไปซ้าย (b) วิธีการนี้ไม่ได้กำจัดการเรียกซ้ำอย่างสมบูรณ์ อย่างไรก็ตาม ช่วยให้คุณสามารถจำกัดตัวเองให้เรียกใช้โพรซีเดอร์แบบเรียกซ้ำเพียงครั้งเดียว ซึ่งอาจเพียงพอหากจุดประสงค์คือการเพิ่มประสิทธิภาพสูงสุด ความคิด แนวทางนี้คือการแทนที่การเรียกซ้ำด้วยการวนซ้ำอย่างง่ายที่จะถูกดำเนินการหลายครั้งตามที่มีโหนดในแผนผังที่สร้างขึ้นโดยโพรซีเดอร์แบบเรียกซ้ำ สิ่งที่ต้องทำในแต่ละขั้นตอนนั้นควรถูกกำหนดโดยหมายเลขขั้นตอน จับคู่หมายเลขขั้นตอนและ การดำเนินการที่จำเป็น– งานไม่ใช่เรื่องเล็กน้อย และในแต่ละกรณีจะต้องแก้ไขแยกกัน เช่น สมมติว่าคุณต้องการทำ เคลูปที่ซ้อนกัน nขั้นตอนในแต่ละ: สำหรับ i1:= 0 ถึง n-1 ทำเพื่อ i2:= 0 ถึง n-1 ทำเพื่อ i3:= 0 ถึง n-1 ทำ … ถ้า เคไม่ทราบล่วงหน้าจึงไม่สามารถเขียนให้ชัดเจนดังที่แสดงไว้ข้างต้น การใช้เทคนิคที่แสดงให้เห็นในส่วน 6.5 คุณสามารถรับจำนวนลูปที่ซ้อนกันที่ต้องการได้โดยใช้ขั้นตอนแบบเรียกซ้ำ: ขั้นตอน NestedCycles (ดัชนี: อาร์เรย์ของจำนวนเต็ม; n, k, ความลึก: จำนวนเต็ม); var i: จำนวนเต็ม; เริ่มต้นถ้าความลึก หากต้องการกำจัดการเรียกซ้ำและลดทุกอย่างให้เหลือหนึ่งรอบ โปรดทราบว่าหากคุณนับขั้นตอนในระบบเลขฐาน nจากนั้นแต่ละขั้นตอนจะมีตัวเลขที่ประกอบด้วยตัวเลข i1, i2, i3, ... หรือค่าที่สอดคล้องกันจากอาร์เรย์ Indexes นั่นคือตัวเลขสอดคล้องกับค่าของตัวนับรอบ หมายเลขขั้นตอนในรูปแบบทศนิยมปกติ: โดยจะมีขั้นตอนทั้งหมด ไม่เป็นไร- โดยอ่านตัวเลขในระบบเลขฐานสิบแล้วแปลงแต่ละตัวเป็นระบบเลขฐาน nเราได้รับค่าดัชนี: M:= รอบ(IntPower(n, k)); สำหรับ i:= 0 ถึง M-1 จะเริ่มต้น Number:= i; สำหรับ p:= 0 ถึง k-1 จะเริ่มต้นดัชนี:= Number mod n; จบ; ทำอะไรสักอย่าง (ดัชนี); จบ; โปรดทราบอีกครั้งว่าวิธีการนี้ไม่เป็นสากลและคุณจะต้องคิดสิ่งที่แตกต่างออกไปสำหรับแต่ละงาน คำถามเพื่อความปลอดภัย 1. กำหนดว่าขั้นตอนและฟังก์ชันแบบเรียกซ้ำต่อไปนี้จะทำอะไร (a) ขั้นตอนต่อไปนี้จะพิมพ์ออกมาเมื่อ Rec(4) ถูกเรียกอย่างไร? ขั้นตอน Rec (a: จำนวนเต็ม); เริ่มเขียน (a); ขั้นตอน A(n: จำนวนเต็ม); ขั้นตอน B (n: จำนวนเต็ม); ขั้นตอน A (n: จำนวนเต็ม); เริ่มเขียน (n); บี(n-1); จบ; ขั้นตอน B (n: จำนวนเต็ม); เริ่มเขียน (n); ถ้าไม่มี (d) ขั้นตอนด้านล่างจะพิมพ์ออกมาเมื่อเรียก BT(0, 1, 3) อย่างไร? ขั้นตอน BT(x: จริง; D, MaxD: จำนวนเต็ม); เริ่มต้นถ้า D = MaxD แล้ว writeln(x) อย่างอื่นเริ่ม BT(x – 1, D + 1, MaxD); BT(x + 1, D + 1, สูงสุดD); จบ; จบ; 2. Ouroboros - งูที่กินหางของมันเอง (รูปที่ 14) เมื่อกางออกจะมีความยาว ล, เส้นผ่านศูนย์กลางรอบศีรษะ ดี , ความหนาของผนังช่องท้อง ง - พิจารณาว่าจะบีบหางเข้าตัวเองได้เท่าไร และหลังจากนั้นจะวางหางได้กี่ชั้น? ข้าว. 14. ขยายโอโรโบรอส 3. สำหรับต้นไม้ในรูป 10a บ่งชี้ลำดับของการเยี่ยมชมโหนดในลำดับการเคลื่อนที่ไปข้างหน้า ย้อนกลับ และสิ้นสุด 5. พรรณนาแผนผังไวยากรณ์สำหรับนิพจน์ทางคณิตศาสตร์ต่อไปนี้เป็นภาพกราฟิก: เขียนนิพจน์นี้ในรูปแบบย้อนกลับของโปแลนด์ 6. สำหรับกราฟด้านล่าง (รูปที่ 15) ให้เขียนเมทริกซ์ adjacency และเมทริกซ์อุบัติการณ์ งาน 1. ต้องคำนวณแฟกทอเรียลให้เพียงพอจำนวนมาก ครั้ง (ล้านหรือมากกว่า) เปรียบเทียบประสิทธิภาพของอัลกอริธึมแบบเรียกซ้ำและแบบวนซ้ำ เวลาดำเนินการจะแตกต่างกันมากน้อยเพียงใด และอัตราส่วนนี้จะขึ้นอยู่กับจำนวนแฟกทอเรียลที่กำลังคำนวณอยู่อย่างไร ตำแหน่งที่ถูกต้อง ตรงตามเงื่อนไข: (a) จำนวนวงเล็บเปิดและปิดเท่ากัน(b) ภายในคู่ใดๆ มีวงเล็บเปิด - ปิดที่สอดคล้องกัน วางวงเล็บอย่างถูกต้อง ตัวอย่างตำแหน่งที่ไม่ถูกต้อง:)(, ())(, ())(() ฯลฯ 3. เส้นอาจมีทั้งวงเล็บกลมและสี่เหลี่ยม แต่ละวงเล็บเปิดมีวงเล็บปิดที่สอดคล้องกันประเภทเดียวกัน (กลม - รอบ, - เขียนฟังก์ชันแบบเรียกซ้ำเพื่อตรวจสอบว่าวงเล็บถูกวางไว้อย่างถูกต้องในกรณีนี้หรือไม่ตัวอย่างตำแหน่งที่ไม่ถูกต้อง: ([) ] 4. จำนวนโครงสร้างวงเล็บปีกกาปกติที่มีความยาว 6 คือ 5: ()()(), (())(), ()(()), ((())), (()()) 5. สร้างขั้นตอนที่พิมพ์การเรียงสับเปลี่ยนที่เป็นไปได้ทั้งหมดสำหรับจำนวนเต็มตั้งแต่ 1 ถึง N 6. สร้างขั้นตอนที่จะพิมพ์ชุดย่อยทั้งหมดของชุด (1, 2, ..., N) 7. สร้างขั้นตอนที่พิมพ์การแทนจำนวนธรรมชาติ N ที่เป็นไปได้ทั้งหมดเป็นผลรวมของจำนวนธรรมชาติอื่นๆ 8. สร้างฟังก์ชันที่คำนวณผลรวมขององค์ประกอบอาร์เรย์ด้วย ไปยังอัลกอริธึมต่อไปนี้: อาร์เรย์จะถูกแบ่งครึ่ง ผลรวมขององค์ประกอบในแต่ละครึ่งจะถูกคำนวณและเพิ่ม ผลรวมขององค์ประกอบในครึ่งหนึ่งของอาร์เรย์คำนวณโดยใช้อัลกอริธึมเดียวกัน นั่นคืออีกครั้งโดยการหารครึ่งหนึ่ง การหารจะเกิดขึ้นจนกว่าชิ้นส่วนผลลัพธ์ของอาเรย์จะมีองค์ประกอบหนึ่งองค์ประกอบและการคำนวณผลรวมจะกลายเป็นเรื่องเล็กน้อย ความคิดเห็น: อัลกอริธึมนี้เป็นอีกทางเลือกหนึ่ง ในกรณีของอาร์เรย์ที่มีค่าจริง มักจะทำให้เกิดข้อผิดพลาดในการปัดเศษเล็กน้อย 10. สร้างขั้นตอนที่วาดเส้นโค้ง Koch (รูปที่ 12) 11. สร้างภาพขึ้นมาใหม่ 16. ในภาพ ในการวนซ้ำแต่ละครั้ง วงกลมจะเล็กลง 2.5 เท่า (สัมประสิทธิ์นี้สามารถใช้เป็นพารามิเตอร์ได้) 1. ดี. คนุธ. ศิลปะการเขียนโปรแกรมคอมพิวเตอร์ v. 1. (มาตรา 2.3 “ต้นไม้”) วาร์ก พูดว่า: สวัสดีตอนบ่าย ฉันไม่เข้าใจจริงๆ ว่าฟังก์ชันนี้คำนวณอย่างไร
{ ตามความเข้าใจของผม ใน n มี 5 เงื่อนไขไม่ตรงกันก็พอใจ รหัสนี้ sum(n-1)+n นั่นคือ สิ่งที่ได้รับในวงเล็บด้วยการลบจะถูกบวกเข้ากับ 5 แต่อะไรคือ (5 - 1)+5 และถ้าเป็นเช่นนั้น อะไรจะหยุดการดำเนินการทางคณิตศาสตร์นี้:?: :?: :?: ค่าก่อนหน้าคืออะไร มาจากไหน เท่ากับอะไร:?: :?: :?: ใช่เกือบทุกอย่างเป็นไปตามที่ฉันเข้าใจ (ในย่อหน้าสุดท้ายคุณแสดงการเรียกซ้ำ))) แต่คำถามยังคงอยู่: ผลรวมที่ได้จะปรากฏบนหน้าจอได้อย่างไร? : (5) //เราให้ 5 ให้กับฟังก์ชัน ตรวจสอบความเท่าเทียมกันกับ 1 ไม่เท่ากันก็เรียกฟังก์ชันอีกแล้วส่ง 5-1 เข้าไป 2-1 == 1 เรียกใช้ฟังก์ชันเสร็จแล้ว ใช่ ใช่ ใช่ ฉันไม่มีเวลาเขียนที่ฉันเข้าใจ ทุกอย่างถูกต้อง มีบางอย่างไม่ผ่านทันที ขอบคุณเว็บไซต์ที่ดี)) และยังไม่สมเหตุสมผลทั้งหมดในบรรทัดที่ 8 หากคุณเปลี่ยนหมายเลขที่ส่งคืนจากส่งคืน 1 เป็น 2 จำนวนจะเปลี่ยนเป็น 16 เงื่อนไขนี้เกี่ยวข้องกับบรรทัดที่ 9 อย่างไร :
จะถูกพิมพ์:
…
สวัสดี N.1
…
สวัสดี N.1
…
สวัสดี N.1
สวัสดี N.1
…
สวัสดี N.14. ความสัมพันธ์ที่เกิดซ้ำ การเรียกซ้ำและการวนซ้ำ
กระบวนการคำนวณ
ด้วยวิธีธรรมชาติ
คำนิยาม:
เราจะเรียกเซตจำกัด ตประกอบด้วยหนึ่งหรือหลายโหนดที่:5.2. ต้นไม้ที่ผ่าน
// การข้ามทรีย่อยที่ถูกต้องถ้า (มีทรีย่อยที่ถูกต้อง) จากนั้น PostorderTraversal ((อาร์กิวเมนต์ 3));
ข้าว. 5. การแสดงแผนผังของบันทึกประเภท TTree บันทึกมีสามฟิลด์: Inf - ตัวเลข, LeftSubTree และ RightSubTree - ตัวชี้ไปยังบันทึกประเภท TTree เดียวกัน
ตัวอย่างของต้นไม้ที่ประกอบด้วยบันทึกดังกล่าวแสดงในรูปที่ 6
ถ้า n > 1 ให้เริ่ม Tree(Canvas, x2, y2, Angle+Pi/4, 0.55*TrunkLength, n-1);
จบ; จบ; เพื่อให้ได้รูป 6 ขั้นตอนนี้ถูกเรียกด้วยพารามิเตอร์ต่อไปนี้:ต้นไม้ (Image1.Canvas, 175, 325, Pi/2, 120, 15);
ข้าว. 7. ปริศนา “หอคอยแห่งฮานอย” nสมมติว่ามีวิธีแก้ปัญหาสำหรับ
-1 ดิสก์ แล้วสำหรับการขยับ nดิสก์ ให้ดำเนินการดังนี้: n 1) กะฮานอย(n-1, c, b, a);
7.3. การกำหนดโหนดต้นไม้ตามหมายเลข
หมายเลข:= หมายเลข div n;
4. บรรยายภาพต้นไม้ที่กำหนดโดยใช้วงเล็บเหลี่ยมแบบซ้อน: (A(B(C, D), E), F, G)
2. เขียนฟังก์ชันแบบเรียกซ้ำที่จะตรวจสอบตำแหน่งที่ถูกต้องของวงเล็บในสตริง ที่
สี่เหลี่ยม - สี่เหลี่ยม n.
เขียนโปรแกรมแบบเรียกซ้ำเพื่อสร้างโครงสร้างวงเล็บเหลี่ยมปกติทั้งหมดที่มีความยาว 2วรรณกรรม
2. เอ็น. เวิร์ธ. อัลกอริทึมและโครงสร้างข้อมูล
ถ้า (n==1) ส่งคืน 1; //หากค่าใหม่เป็น 1 เราจะบวกมันด้วย 1 ไม่ใช่ค่าก่อนหน้า เพราะ อันก่อนหน้าคือศูนย์ และการบวก 1+0 จะไม่มีที่สิ้นสุด
มิฉะนั้นจะส่งคืนผลรวม (n-1)+n; //แต่ถ้า n>1 ให้บวกด้วยค่าก่อนหน้าเท่ากับผลรวมขององค์ประกอบทั้งหมดจนถึง n
}
ฉันกำลังทำงานกับ Dev C++ สำหรับฉัน ตัวอย่างนี้แสดงผลรวม ==15 หากคุณนับตามที่เขียนไว้ในตัวอย่าง ผลรวมจะแตกต่างออกไป
ฉันเขียนไว้ข้างต้น เอาล่ะ (5-1)+5=4+5=9
1+2+3+4+5 = 15 ตัวอย่างผลลัพธ์ถูกต้อง
(5-1+(5)) //...
(4-1+(5-1+(5)))
(3-1+(4-1+(5-1+(5))))
(2-1+(3-1+(4-1+(5-1+(5)))))
(2-1+(3-1+(4-1+(5-1+(5))))) == 15
นี่คือผลลัพธ์
ผลลัพธ์ของฟังก์ชันนี้คือผลต่างของตัวเลขสองตัวแรก และ n คือส่วนที่เหลือทางด้านขวา
__________________________________
คุณเพียงแค่ต้องเข้าใจฟังก์ชันอย่างถูกต้องและยอมรับว่าเป็นค่าจากการคำนวณ แต่ไม่เข้าใจว่าเป็นตัวแปร มันคล้ายกับตัวแปร แต่ใกล้กับค่าคงที่ที่คำนวณได้มากกว่า แม้ว่าจะไม่ใช่ค่าคงที่ แต่ก็สะดวกกว่าที่จะรับรู้ด้วยวิธีนี้
ด้วยเหตุนี้ทุกอย่างชัดเจนเพียงแค่ส่งคืน 2 ก็บวกการกีดกันเข้ากับผลรวม
ไม่ใช่ดาวพิเศษ แต่เป็นสองดวงนี้ และถ้าคุณเขียน -3 แล้วเมื่อบวกหนึ่งครั้งก็จะลบทั้งสามดวง เป็นต้น
ตรรกะทั้งหมดก็คือฟังก์ชันแบบเรียกซ้ำจำเป็นต้องมีจุดส่งคืน
การเชื่อมต่อกับบรรทัดที่เก้าคือตัวเลขจะถูกส่งผ่านไปยังฟังก์ชันผลรวมเมื่อเรียกจากภายในหลัก ในระหว่างการเรียกซ้ำ หมายเลขนี้จะลดลงหนึ่ง (n-1) ในแต่ละครั้ง ผลลัพธ์นี้ n-1 จะถูกตรวจสอบความเท่าเทียมกันด้วย หนึ่ง และหากความเท่าเทียมกันเป็นจริง จำนวนเงินที่ได้รับทั้งหมดจะถูกรวมเข้ากับตัวเลขในการส่งคืนนั้น มิฉะนั้นผลรวมทั้งหมดจะถูกรวมเข้ากับ n-1 ใหม่นี้