การเรียกใช้ฟังก์ชันแบบเรียกซ้ำ อัลกอริธึมการเรียกซ้ำและการเรียกซ้ำ

เพื่อไม่ให้เขียนบทความใหญ่ๆ สักบทความ ซึ่งจะมีตัวอย่างมากมายของการเรียกซ้ำในภาษา 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");
ศาล<//จุดโทร ฟังก์ชันแบบเรียกซ้ำ- กำลังแสดงแฟคทอเรียล 10 บนหน้าจอ
รับ();
กลับ 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.1

สวัสดี N.2

สวัสดี N10

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

คุณสามารถสลับการโทรซ้ำและคำแนะนำในการทำซ้ำได้ ดังตัวอย่างต่อไปนี้

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

สวัสดี N10

สวัสดี N.1

ถ้าเราจินตนาการถึงสายโซ่ของโพรซีเดอร์ที่ถูกเรียกซ้ำ ดังนั้นในตัวอย่างที่ 1 เราจะผ่านมันจากโพรซีเจอร์ที่ถูกเรียกก่อนหน้านี้ไปยังโพรซีเดอร์ที่เรียกในภายหลัง ในตัวอย่างที่ 2 ตรงกันข้ามจากภายหลังไปก่อนหน้า

ในที่สุด สามารถทำการเรียกซ้ำระหว่างสองบล็อคคำสั่งได้ ตัวอย่างเช่น:

ขั้นตอน LoopImitation3 (i, n: จำนวนเต็ม); เริ่มเขียน ("Hello N ", i); (บล็อคคำสั่งแรกอาจอยู่ที่นี่) ถ้าฉัน

ในที่นี้ คำสั่งจากบล็อกแรกจะดำเนินการตามลำดับก่อน จากนั้นคำสั่งจากบล็อกที่สองจะดำเนินการในลำดับย้อนกลับ เมื่อเรียก LoopImitation3(1, 10) เราจะได้รับ:

ในกรณีนี้

สวัสดี N.1
สวัสดี N.1

สวัสดี N.1

จะใช้เวลาสองลูปในการทำสิ่งเดียวกันโดยไม่มีการเรียกซ้ำ

คุณสามารถใช้ประโยชน์จากข้อเท็จจริงที่ว่าการดำเนินการส่วนต่างๆ ของขั้นตอนเดียวกันนั้นมีการเว้นระยะห่างออกไปเมื่อเวลาผ่านไป ตัวอย่างเช่น:

ตัวอย่างที่ 3: การแปลงตัวเลขเป็นไบนารี

ดังที่ทราบกันดีว่าการได้รับเลขฐานสองนั้นเกิดขึ้นโดยการหารด้วยเศษด้วยฐานของระบบตัวเลข 2 หากมีตัวเลข ตัวเลขหลักสุดท้ายของเลขฐานสองจะเท่ากับ

หารทั้งส่วนด้วย 2:

เราได้ตัวเลขที่มีการแทนเลขฐานสองเหมือนกัน แต่ไม่มีเลขหลักสุดท้าย ดังนั้นจึงเพียงพอที่จะทำซ้ำการดำเนินการทั้งสองข้างต้นจนกว่าฟิลด์การหารถัดไปจะได้รับส่วนจำนวนเต็มเท่ากับ 0 หากไม่มีการเรียกซ้ำจะมีลักษณะดังนี้:

ในขณะที่ x>0 เริ่มต้น c:=x mod 2;

x:=x div 2;

เขียน(ค); จบ;

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

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

4. ความสัมพันธ์ที่เกิดซ้ำ การเรียกซ้ำและการวนซ้ำ

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

ตัวอย่างง่ายๆ ของปริมาณที่คำนวณโดยใช้ความสัมพันธ์ที่เกิดซ้ำคือแฟกทอเรียล

แฟกทอเรียลถัดไปสามารถคำนวณได้จากค่าก่อนหน้าดังนี้:

โดยการแนะนำสัญกรณ์ เราได้รับความสัมพันธ์:

เวกเตอร์จากสูตร (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 กรัม;

5.2. ต้นไม้ที่ผ่าน

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

อัลกอริธึมการเคลื่อนที่ไปข้างหน้า:

  • ไปที่ราก
  • ไล่ดูแผนผังย่อยทั้งหมดจากซ้ายไปขวาตามลำดับโดยตรง

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

โดยเฉพาะต้นไม้ในรูปนี้ 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

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

  • // Inorder Traversal – ชื่อภาษาอังกฤษสำหรับขั้นตอนลำดับย้อนกลับ InorderTraversal((อาร์กิวเมนต์)); เริ่มต้น // การเดินทางทรีย่อยด้านซ้ายถ้า (มีทรีย่อยด้านซ้าย) จากนั้น InorderTraversal ((อาร์กิวเมนต์ 2));
  • //ส่งผ่านรูท DoSomething((อาร์กิวเมนต์));

//การข้ามทรีย่อยที่ถูกต้องถ้า (มีทรีย่อยที่ถูกต้อง) จากนั้น InorderTraversal((อาร์กิวเมนต์ 3)); จบ;

อัลกอริธึมการแวะผ่านลำดับสุดท้าย:

// Postorder Traversal – ชื่อภาษาอังกฤษสำหรับขั้นตอนการสั่งซื้อสิ้นสุด PostorderTraversal((อาร์กิวเมนต์)); เริ่มต้น // การเดินทางทรีย่อยด้านซ้ายถ้า (มีทรีย่อยด้านซ้าย) จากนั้น PostorderTraversal ((อาร์กิวเมนต์ 2));

// การข้ามทรีย่อยที่ถูกต้องถ้า (มีทรีย่อยที่ถูกต้อง) จากนั้น PostorderTraversal ((อาร์กิวเมนต์ 3));

//ส่งผ่านรูท DoSomething((อาร์กิวเมนต์)); จบ; 5.3. การแสดงต้นไม้ในหน่วยความจำคอมพิวเตอร์หากข้อมูลบางอย่างอยู่ในโหนดต้นไม้ ก็สามารถใช้โครงสร้างข้อมูลไดนามิกที่เหมาะสมในการจัดเก็บข้อมูลได้ ใน Pascal สิ่งนี้เสร็จสิ้นโดยใช้

ประเภทตัวแปร

บันทึกที่มีพอยน์เตอร์ไปยังทรีย่อยประเภทเดียวกัน ตัวอย่างเช่น ต้นไม้ไบนารีที่แต่ละโหนดมีจำนวนเต็มสามารถจัดเก็บได้โดยใช้ตัวแปรประเภท PTree ซึ่งอธิบายไว้ด้านล่าง: พิมพ์ PTree = ^TTree; TTree = บันทึก Inf: จำนวนเต็ม;

LeftSubTree, RightSubTree: PTree;

จบ;

แต่ละโหนดมีประเภท PTree นี่คือพอยน์เตอร์ ซึ่งหมายความว่าแต่ละโหนดต้องถูกสร้างขึ้นโดยการเรียกโพรซีเดอร์ใหม่ หากโหนดเป็นโหนดปลายสุด ฟิลด์ LeftSubTree และ RightSubTree จะถูกกำหนดค่า

ไม่มี พิมพ์ PTree = ^TTree;- มิฉะนั้น โหน LeftSubTree และ RightSubTree จะถูกสร้างขึ้นด้วยขั้นตอนใหม่

บันทึกดังกล่าวรายการหนึ่งแสดงอยู่ในแผนผังในรูป 5.

ข้าว. 5. การแสดงแผนผังของบันทึกประเภท TTree บันทึกมีสามฟิลด์: Inf - ตัวเลข, LeftSubTree และ RightSubTree - ตัวชี้ไปยังบันทึกประเภท TTree เดียวกัน

ตัวอย่างของต้นไม้ที่ประกอบด้วยบันทึกดังกล่าวแสดงในรูปที่ 6

ข้าว. 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));

ถ้า n > 1 ให้เริ่ม Tree(Canvas, x2, y2, Angle+Pi/4, 0.55*TrunkLength, n-1);

ต้นไม้ (Canvas, x2, y2, Angle-Pi/4, 0.55*TrunkLength, n-1);
จบ; จบ; เพื่อให้ได้รูป 6 ขั้นตอนนี้ถูกเรียกด้วยพารามิเตอร์ต่อไปนี้:ต้นไม้ (Image1.Canvas, 175, 325, Pi/2, 120, 15);

โปรดทราบว่าการวาดจะดำเนินการก่อนการโทรแบบเรียกซ้ำ นั่นคือ ต้นไม้จะถูกวาดตามลำดับโดยตรง

6.2. ฮานอยทาวเวอร์

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

ไม่เคยพบว่าตัวเองอยู่เหนือสิ่งอื่นใดแม้แต่น้อย พระภิกษุอยู่ในความลังเลใจ ควรทำกะตามลำดับไหน? จำเป็นต้องจัดเตรียมซอฟต์แวร์เพื่อคำนวณลำดับนี้ nปริศนานี้ถูกเสนอขึ้นโดยอิสระจากพระพรหมเมื่อปลายศตวรรษที่ 19 โดยนักคณิตศาสตร์ชาวฝรั่งเศส เอดูอาร์ด ลูคัส รุ่นที่ขายมักจะใช้ดิสก์ 7-8 แผ่น (รูปที่ 7)
ข้าว. 7. ปริศนา “หอคอยแห่งฮานอย” nสมมติว่ามีวิธีแก้ปัญหาสำหรับ
-1 ดิสก์ แล้วสำหรับการขยับ nดิสก์ ให้ดำเนินการดังนี้: n 1) กะ

-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);

ฮานอย(n-1, c, b, a);

จบอย่างอื่น writeln(a, " -> ", b);

จบ;

โปรดสังเกตว่าชุดของกระบวนการที่เรียกซ้ำในกรณีนี้จะสร้างต้นไม้ที่เคลื่อนที่ในลำดับที่กลับกัน 6.3. การแยกวิเคราะห์นิพจน์ทางคณิตศาสตร์ (6).

งานแยกวิเคราะห์คือการคำนวณค่าของนิพจน์โดยใช้สตริงที่มีอยู่ซึ่งมีนิพจน์ทางคณิตศาสตร์และค่าที่ทราบของตัวแปรที่รวมอยู่ในนั้น กระบวนการคำนวณนิพจน์ทางคณิตศาสตร์สามารถแสดงเป็นต้นไม้ไบนารีได้ อันที่จริง ตัวดำเนินการทางคณิตศาสตร์แต่ละตัว (+, –, *, /) ต้องใช้ตัวถูกดำเนินการสองตัว ซึ่งจะเป็นนิพจน์ทางคณิตศาสตร์ด้วย และด้วยเหตุนี้ จึงถือเป็นแผนผังย่อยได้ ข้าว. รูปที่ 8 แสดงตัวอย่างต้นไม้ที่สอดคล้องกับนิพจน์:ข้าว. 8. ต้นไม้ไวยากรณ์ที่สอดคล้องกัน นิพจน์ทางคณิตศาสตร์ในแผนผังดังกล่าว โหนดสุดท้ายจะเป็นตัวแปรเสมอ (ในที่นี้

x ) หรือค่าคงที่ตัวเลข และโหนดภายในทั้งหมดจะมี ตัวดำเนินการทางคณิตศาสตร์ - ในการรันโอเปอเรเตอร์ คุณต้องประเมินโอเปอเรเตอร์ของมันก่อน ดังนั้นต้นไม้ในรูปนี้จึงควรเคลื่อนที่ตามลำดับขั้ว ลำดับโหนดที่สอดคล้องกัน

เรียกว่า ย้อนกลับสัญกรณ์โปแลนด์

นิพจน์ทางคณิตศาสตร์

เมื่อสร้างแผนผังไวยากรณ์คุณควรใส่ใจ

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

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

7.3. การกำหนดโหนดต้นไม้ตามหมายเลข

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

เช่น สมมติว่าคุณต้องการทำ เคลูปที่ซ้อนกัน 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;

หมายเลข:= หมายเลข div 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 บ่งชี้ลำดับของการเยี่ยมชมโหนดในลำดับการเคลื่อนที่ไปข้างหน้า ย้อนกลับ และสิ้นสุด

4. บรรยายภาพต้นไม้ที่กำหนดโดยใช้วงเล็บเหลี่ยมแบบซ้อน: (A(B(C, D), E), F, G)

5. พรรณนาแผนผังไวยากรณ์สำหรับนิพจน์ทางคณิตศาสตร์ต่อไปนี้เป็นภาพกราฟิก: เขียนนิพจน์นี้ในรูปแบบย้อนกลับของโปแลนด์ 6. สำหรับกราฟด้านล่าง (รูปที่ 15) ให้เขียนเมทริกซ์ adjacency และเมทริกซ์อุบัติการณ์

งาน 1. ต้องคำนวณแฟกทอเรียลให้เพียงพอจำนวนมาก

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

ตำแหน่งที่ถูกต้อง

ตรงตามเงื่อนไข: (a) จำนวนวงเล็บเปิดและปิดเท่ากัน(b) ภายในคู่ใดๆ มีวงเล็บเปิด - ปิดที่สอดคล้องกัน วางวงเล็บอย่างถูกต้อง

ตัวอย่างตำแหน่งที่ไม่ถูกต้อง:)(, ())(, ())(() ฯลฯ

3. เส้นอาจมีทั้งวงเล็บกลมและสี่เหลี่ยม แต่ละวงเล็บเปิดมีวงเล็บปิดที่สอดคล้องกันประเภทเดียวกัน (กลม - รอบ,
สี่เหลี่ยม - สี่เหลี่ยม n.

- เขียนฟังก์ชันแบบเรียกซ้ำเพื่อตรวจสอบว่าวงเล็บถูกวางไว้อย่างถูกต้องในกรณีนี้หรือไม่ตัวอย่างตำแหน่งที่ไม่ถูกต้อง: ([) ]

4. จำนวนโครงสร้างวงเล็บปีกกาปกติที่มีความยาว 6 คือ 5: ()()(), (())(), ()(()), ((())), (()())
เขียนโปรแกรมแบบเรียกซ้ำเพื่อสร้างโครงสร้างวงเล็บเหลี่ยมปกติทั้งหมดที่มีความยาว 2

5. สร้างขั้นตอนที่พิมพ์การเรียงสับเปลี่ยนที่เป็นไปได้ทั้งหมดสำหรับจำนวนเต็มตั้งแต่ 1 ถึง N

6. สร้างขั้นตอนที่จะพิมพ์ชุดย่อยทั้งหมดของชุด (1, 2, ..., N)

7. สร้างขั้นตอนที่พิมพ์การแทนจำนวนธรรมชาติ N ที่เป็นไปได้ทั้งหมดเป็นผลรวมของจำนวนธรรมชาติอื่นๆ

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

ความคิดเห็น: อัลกอริธึมนี้เป็นอีกทางเลือกหนึ่ง ในกรณีของอาร์เรย์ที่มีค่าจริง มักจะทำให้เกิดข้อผิดพลาดในการปัดเศษเล็กน้อย

10. สร้างขั้นตอนที่วาดเส้นโค้ง Koch (รูปที่ 12)

11. สร้างภาพขึ้นมาใหม่ 16. ในภาพ ในการวนซ้ำแต่ละครั้ง วงกลมจะเล็กลง 2.5 เท่า (สัมประสิทธิ์นี้สามารถใช้เป็นพารามิเตอร์ได้)

วรรณกรรม

1. ดี. คนุธ. ศิลปะการเขียนโปรแกรมคอมพิวเตอร์ v. 1. (มาตรา 2.3 “ต้นไม้”)
2. เอ็น. เวิร์ธ. อัลกอริทึมและโครงสร้างข้อมูล

วาร์ก พูดว่า:

สวัสดีตอนบ่าย ฉันไม่เข้าใจจริงๆ ว่าฟังก์ชันนี้คำนวณอย่างไร

{
ถ้า (n==1) ส่งคืน 1; //หากค่าใหม่เป็น 1 เราจะบวกมันด้วย 1 ไม่ใช่ค่าก่อนหน้า เพราะ อันก่อนหน้าคือศูนย์ และการบวก 1+0 จะไม่มีที่สิ้นสุด
มิฉะนั้นจะส่งคืนผลรวม (n-1)+n; //แต่ถ้า n>1 ให้บวกด้วยค่าก่อนหน้าเท่ากับผลรวมขององค์ประกอบทั้งหมดจนถึง n
}

ตามความเข้าใจของผม ใน n มี 5 เงื่อนไขไม่ตรงกันก็พอใจ รหัสนี้ sum(n-1)+n นั่นคือ สิ่งที่ได้รับในวงเล็บด้วยการลบจะถูกบวกเข้ากับ 5 แต่อะไรคือ (5 - 1)+5 และถ้าเป็นเช่นนั้น อะไรจะหยุดการดำเนินการทางคณิตศาสตร์นี้:?: :?: :?: ค่าก่อนหน้าคืออะไร มาจากไหน เท่ากับอะไร:?: :?: :?:

ใช่เกือบทุกอย่างเป็นไปตามที่ฉันเข้าใจ (ในย่อหน้าสุดท้ายคุณแสดงการเรียกซ้ำ))) แต่คำถามยังคงอยู่: ผลรวมที่ได้จะปรากฏบนหน้าจอได้อย่างไร?
ฉันกำลังทำงานกับ Dev C++ สำหรับฉัน ตัวอย่างนี้แสดงผลรวม ==15 หากคุณนับตามที่เขียนไว้ในตัวอย่าง ผลรวมจะแตกต่างออกไป
ฉันเขียนไว้ข้างต้น เอาล่ะ (5-1)+5=4+5=9

:
1+2+3+4+5 = 15 ตัวอย่างผลลัพธ์ถูกต้อง

(5) //เราให้ 5 ให้กับฟังก์ชัน ตรวจสอบความเท่าเทียมกันกับ 1 ไม่เท่ากันก็เรียกฟังก์ชันอีกแล้วส่ง 5-1 เข้าไป
(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 == 1 เรียกใช้ฟังก์ชันเสร็จแล้ว
(2-1+(3-1+(4-1+(5-1+(5))))) == 15
นี่คือผลลัพธ์
ผลลัพธ์ของฟังก์ชันนี้คือผลต่างของตัวเลขสองตัวแรก และ n คือส่วนที่เหลือทางด้านขวา
__________________________________
คุณเพียงแค่ต้องเข้าใจฟังก์ชันอย่างถูกต้องและยอมรับว่าเป็นค่าจากการคำนวณ แต่ไม่เข้าใจว่าเป็นตัวแปร มันคล้ายกับตัวแปร แต่ใกล้กับค่าคงที่ที่คำนวณได้มากกว่า แม้ว่าจะไม่ใช่ค่าคงที่ แต่ก็สะดวกกว่าที่จะรับรู้ด้วยวิธีนี้

ใช่ ใช่ ใช่ ฉันไม่มีเวลาเขียนที่ฉันเข้าใจ ทุกอย่างถูกต้อง มีบางอย่างไม่ผ่านทันที ขอบคุณเว็บไซต์ที่ดี))

และยังไม่สมเหตุสมผลทั้งหมดในบรรทัดที่ 8 หากคุณเปลี่ยนหมายเลขที่ส่งคืนจากส่งคืน 1 เป็น 2 จำนวนจะเปลี่ยนเป็น 16 เงื่อนไขนี้เกี่ยวข้องกับบรรทัดที่ 9 อย่างไร
ด้วยเหตุนี้ทุกอย่างชัดเจนเพียงแค่ส่งคืน 2 ก็บวกการกีดกันเข้ากับผลรวม

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