อัลกอริธึมการเรียกซ้ำและการเรียกซ้ำ อัลกอริธึมการเรียกซ้ำและอัลกอริธึมแบบเรียกซ้ำ รับขั้นตอนวิธีอัลกอริธึมแบบเรียกซ้ำ f n จำนวนเต็ม
การนำเสนอในหัวข้อ “Recursive Algorithms” ถูกสร้างขึ้นเพื่อเตรียมนักเรียนสำหรับการสอบ Unified State ในสาขาวิทยาการคอมพิวเตอร์และ ICT งานนี้จะตรวจสอบคำจำกัดความของการเรียกซ้ำและจัดเตรียมตัวอย่างของอ็อบเจ็กต์กราฟิกที่กำหนดแบบเรียกซ้ำ การนำเสนอประกอบด้วยวิธีการแก้ไขงานหมายเลข 11 จากเวอร์ชันสาธิตร่างของ Unified State Exam - 2015 ในสาขาวิทยาการคอมพิวเตอร์ วิธีแรกเกี่ยวข้องกับการสร้างแผนผังการเรียก วิธีที่สองแก้ปัญหาโดยใช้วิธีทดแทน มีการพิจารณาตัวอย่างการแก้ปัญหา 4 ตัวอย่างโดยใช้ทั้งสองวิธี การนำเสนอต่อไปนี้ประกอบด้วยแบบฝึกหัด 25 แบบสำหรับการฝึกอบรมพร้อมคำตอบจากเว็บไซต์ของ Konstantin Polyakov
ดาวน์โหลด:
ดูตัวอย่าง:
หากต้องการใช้ตัวอย่างการนำเสนอ ให้สร้างบัญชีสำหรับตัวคุณเอง ( บัญชี) Google และเข้าสู่ระบบ: https://accounts.google.com
คำอธิบายสไลด์:
ภารกิจที่ 11 การสอบ Unified State (ระดับพื้นฐาน, เวลา - 5 นาที) อัลกอริธึมแบบเรียกซ้ำ ผู้เขียน – Korotun O.V. ครูวิทยาการคอมพิวเตอร์ สถาบันการศึกษาเทศบาล “โรงเรียนมัธยมหมายเลข 71”
สิ่งที่คุณต้องรู้: การเรียกซ้ำอยู่ในคำจำกัดความ คำอธิบาย การพรรณนาของวัตถุหรือกระบวนการภายในวัตถุนี้หรือตัวกระบวนการเอง นั่นคือ สถานการณ์ที่วัตถุเป็นส่วนหนึ่งของตัวมันเอง ตราแผ่นดิน สหพันธรัฐรัสเซียเป็นวัตถุกราฟิกที่กำหนดแบบวนซ้ำ: ในอุ้งเท้าขวาของนกอินทรีสองหัวที่ปรากฎบนนั้น คทาถูกหนีบซึ่งสวมมงกุฎด้วยเสื้อคลุมแขนที่ลดลง เนื่องจากบนเสื้อคลุมแขนนี้มีคทาอยู่ที่อุ้งเท้าขวาของนกอินทรีจึงได้รับการเรียกซ้ำอย่างไม่สิ้นสุด ตราแผ่นดินแบบเรียกซ้ำของรัสเซีย
ในการเขียนโปรแกรม การเรียกซ้ำคือการเรียกใช้ฟังก์ชันจากตัวมันเอง โดยตรงหรือผ่านฟังก์ชันอื่นๆ เช่น ฟังก์ชัน A เรียกใช้ฟังก์ชัน B และฟังก์ชัน B เรียกฟังก์ชัน A จำนวนการเรียกใช้ฟังก์ชันหรือขั้นตอนที่ซ้อนกันเรียกว่าความลึกของการเรียกซ้ำ ตัวอย่างการเรียกซ้ำ: หากคุณมีคราบมันบนชุดของคุณ ไม่ต้องกังวล คราบน้ำมันจะถูกกำจัดออกด้วยน้ำมันเบนซิน คราบน้ำมันจะถูกกำจัดออกด้วยสารละลายอัลคาไล
ตัวอย่างการกำหนด: ให้อัลกอริทึมแบบเรียกซ้ำ: ขั้นตอน F(n: จำนวนเต็ม); เริ่มเขียน (n); ถ้าไม่มี
ตัวอย่างการกำหนด: ให้อัลกอริทึมแบบเรียกซ้ำ: ขั้นตอน F(n: จำนวนเต็ม); เริ่มเขียน (n) ; ถ้าไม่มี
ตัวอย่างการมอบหมาย: ให้อัลกอริทึมแบบเรียกซ้ำ: ขั้นตอน F(n: จำนวนเต็ม); เริ่มเขียน (n); ถ้า n สไลด์ 9
ตัวอย่างการมอบหมาย: ให้อัลกอริทึมแบบเรียกซ้ำ: ขั้นตอน F(n: จำนวนเต็ม); เริ่มเขียน (n); ถ้า n สไลด์ 10
ตัวอย่างการมอบหมาย: ให้อัลกอริทึมแบบเรียกซ้ำ: ขั้นตอน F(n: จำนวนเต็ม); เริ่มเขียน (n); ถ้า n สไลด์ 11
15 ตัวอย่างที่ 2: กำหนดอัลกอริธึมแบบเรียกซ้ำ: ขั้นตอน F(n: จำนวนเต็ม); เริ่มเขียน (n); ถ้าไม่มี
ตัวอย่างที่ 2: กำหนดอัลกอริธึมแบบเรียกซ้ำ: ขั้นตอน F(n: จำนวนเต็ม); เริ่มเขียน (n); ถ้า n สไลด์ 13
ตัวอย่างที่ 3: กำหนดอัลกอริธึมแบบเรียกซ้ำ: ขั้นตอน F(n: จำนวนเต็ม); เริ่ม writeln("*" ; ถ้า n > 0 ให้เริ่ม F(n-2); F(n div 2) ปลาย ปลาย ; เมื่อโทร F(7) จะพิมพ์เครื่องหมายดอกจันบนหน้าจอกี่อัน? 7 5 3 2 3 1 1 1 1 ในตัวอย่างนี้สัญลักษณ์ * -2 div 2 1 0 -1 0 -1 0 -1 -1 -1 0 0 0 จะแสดงบนหน้าจอแทนที่จะเป็นค่าของ พารามิเตอร์ n
ตัวอย่างที่ 3: ให้อัลกอริทึมแบบเรียกซ้ำ: ขั้นตอน F(n: จำนวนเต็ม); เริ่มเขียน ln("*"); ถ้า n > 0 ให้เริ่ม F(n-2); F(n div 2) ปลาย ปลาย ; เมื่อโทร F(7) จะพิมพ์เครื่องหมายดอกจันบนหน้าจอกี่อัน? * โดยการนับจำนวน “ดาว” เราได้ 21 ในตัวอย่างนี้ไม่ใช่ค่าของพารามิเตอร์ n ที่แสดงบนหน้าจอ แต่เป็นสัญลักษณ์ * * * * * * * * * * * * * * * * * * * * * *
ตัวอย่างที่ 3: ให้อัลกอริทึมแบบเรียกซ้ำ: ขั้นตอน F(n: จำนวนเต็ม); เริ่มเขียน ln("*"); ถ้า n > 0 ให้เริ่ม F(n-2); F(n div 2) ปลาย ปลาย ; เมื่อโทร F(7) จะพิมพ์เครื่องหมายดอกจันบนหน้าจอกี่อัน? มาแก้ปัญหาโดยไม่มีต้นไม้กันเถอะ ให้ S(n) เป็นจำนวน “ดาว” ที่จะพิมพ์เมื่อเรียก F(n) จากนั้น 1+S(n-2)+ S(n div 2), n>0 1 , n เราจำเป็นต้องรู้ S(7) ส(7)= 1 +ส(5)+ ส(3) ส(5)= 1 +ส(3)+S(2) ส(3)= 1 +ส(1)+S(1) ส( 2)=1+ส(0)+ส(1)=1+1+ส(1)=2+ส(1) ส(1)= 1+ ส(-1)+ส(0)=1+ 1+1=3 ย้อนกลับ: S(2)=2+3=5 S(3)=1+3+3=7 S(5)=1+7+5=13 S(7)=1+ 13+ 7= 21 ส(n)=
ตัวอย่างที่ 4: ขั้นตอน F(n: จำนวนเต็ม); เริ่มต้นถ้า n สไลด์ 18
ตัวอย่างที่ 4: ขั้นตอน F(n: จำนวนเต็ม); เริ่มต้นถ้า n สไลด์ 19
ตัวอย่างที่ 4: ขั้นตอน F(n: จำนวนเต็ม); เริ่มต้นถ้า n
ตัวอย่างที่ 4: ขั้นตอน F(n: จำนวนเต็ม); เริ่มต้นถ้า n
งานฝึกอบรม
ปัญหาที่ 1: กำหนดอัลกอริธึมแบบเรียกซ้ำ: ขั้นตอน F(n: จำนวนเต็ม); เริ่มเขียน ln("*"); ถ้า n > 0 ให้เริ่ม F(n-2); F(n div 2); F(n div 2); สิ้นสุด ; เมื่อโทร F(5) จะพิมพ์เครื่องหมายดอกจันบนหน้าจอกี่อัน? คำตอบ: 34
ปัญหาที่ 2: กำหนดอัลกอริธึมแบบเรียกซ้ำ: ขั้นตอน F(n: จำนวนเต็ม); เริ่มเขียน ln("*"); ถ้า n > 0 ให้เริ่ม F(n-2); ฉ(n-2); F(n div 2); สิ้นสุด ; เมื่อโทร F(6) จะพิมพ์เครื่องหมายดอกจันบนหน้าจอกี่อัน? คำตอบ: 58
ปัญหาที่ 3: กำหนดอัลกอริธึมแบบเรียกซ้ำ: ขั้นตอน F(n: จำนวนเต็ม); เริ่มเขียน ln("*"); ถ้า n > 0 ให้เริ่ม F(n-3); F(n div 2); สิ้นสุด ; เมื่อโทร F(7) จะพิมพ์เครื่องหมายดอกจันบนหน้าจอกี่อัน? คำตอบ: 15
ปัญหาที่ 4: กำหนดอัลกอริธึมแบบเรียกซ้ำ: ขั้นตอน F(n: จำนวนเต็ม); เริ่มเขียน ln("*"); ถ้า n > 0 ให้เริ่ม F(n-3); ฉ(n-2); F(n div 2); สิ้นสุด ; เมื่อโทร F(7) จะพิมพ์เครื่องหมายดอกจันบนหน้าจอกี่อัน? คำตอบ: 55
ปัญหาที่ 5: กำหนดอัลกอริธึมแบบเรียกซ้ำ: ขั้นตอน F(n: จำนวนเต็ม); เริ่มเขียน ln("*"); ถ้า n > 0 ให้เริ่ม F(n-3); ฉ(n-2); F(n div 2); F(n div 2); สิ้นสุด ; เมื่อโทร F(6) จะพิมพ์เครื่องหมายดอกจันบนหน้าจอกี่อัน? คำตอบ: 97
ปัญหาที่ 6: กำหนดอัลกอริธึมแบบเรียกซ้ำ: ขั้นตอน F(n: จำนวนเต็ม); เริ่มเขียน ln("*"); ถ้า n > 0 ให้เริ่ม writeln("*"); ฉ(n-2); F(n div 2); สิ้นสุด ; เมื่อโทร F(7) จะพิมพ์เครื่องหมายดอกจันบนหน้าจอกี่อัน? คำตอบ: 31
ปัญหาที่ 7: กำหนดอัลกอริธึมแบบเรียกซ้ำ: ขั้นตอน F(n: จำนวนเต็ม); เริ่มเขียน ln("*"); ถ้า n > 0 ให้เริ่ม writeln("*"); ฉ(n-2); F(n div 2); F(n div 2); ปลายสุด; เมื่อโทร F(7) จะพิมพ์เครื่องหมายดอกจันบนหน้าจอกี่อัน? คำตอบ: 81
ปัญหาที่ 8: กำหนดอัลกอริธึมแบบเรียกซ้ำ: ขั้นตอน F(n: จำนวนเต็ม); เริ่มเขียน ln("*"); ถ้า n > 0 ให้เริ่ม writeln("*"); ฉ(n-2); ฉ(n-2); F(n div 2); ปลายสุด; เมื่อโทร F(6) จะพิมพ์เครื่องหมายดอกจันบนหน้าจอกี่อัน? คำตอบ: 77
ปัญหาที่ 9: กำหนดอัลกอริธึมแบบเรียกซ้ำ: ขั้นตอน F(n: จำนวนเต็ม); เริ่มต้นถ้า n > 0 แล้วเริ่มต้น F(n-2); ฉ(n-1); ฉ(n-1); จบ; writeln("*"); จบ ; เมื่อโทร F(5) จะพิมพ์เครื่องหมายดอกจันบนหน้าจอกี่อัน? คำตอบ: 148
ปัญหาที่ 10: กำหนดอัลกอริธึมแบบเรียกซ้ำ: ขั้นตอน F(n: จำนวนเต็ม); เริ่มถ้า n > 0 ให้เริ่ม writeln("*"); ฉ(n-2); ฉ(n-1); ฉ(n-1); จบ; writeln("*"); จบ; เมื่อโทร F(5) จะพิมพ์เครื่องหมายดอกจันบนหน้าจอกี่อัน? คำตอบ: 197
ปัญหาที่ 11: กำหนดอัลกอริธึมแบบเรียกซ้ำ: ขั้นตอน F(n: จำนวนเต็ม); เริ่มต้นถ้า n > 1 แล้วเริ่มต้น F(n-2); ฉ(n-1); F(n div 2); จบ; writeln("*"); จบ ; เมื่อโทร F(7) จะพิมพ์เครื่องหมายดอกจันบนหน้าจอกี่อัน? คำตอบ: 88
ปัญหาที่ 12: กำหนดอัลกอริธึมแบบเรียกซ้ำ: ขั้นตอน F(n: จำนวนเต็ม); เริ่มต้นถ้า n > 2 ให้เริ่ม writeln("*"); ฉ(n-2); ฉ(n-1); F(n div 2); จบ ; writeln("*"); จบ; เมื่อโทร F(6) จะพิมพ์เครื่องหมายดอกจันบนหน้าจอกี่อัน? คำตอบ: 33
ปัญหาที่ 13: ให้อัลกอริทึมแบบเรียกซ้ำ: ขั้นตอน F (n: จำนวนเต็ม); เริ่มเขียน (n); ถ้าไม่มี
ปัญหาที่ 14: ให้อัลกอริทึมแบบเรียกซ้ำ: ขั้นตอน F (n: จำนวนเต็ม); เริ่มเขียน (n); ถ้าไม่มี
ปัญหาที่ 15: ให้อัลกอริทึมแบบเรียกซ้ำ: ขั้นตอน F (n: จำนวนเต็ม); เริ่มเขียน (n); ถ้าไม่มี
ปัญหาที่ 16: ให้อัลกอริทึมแบบเรียกซ้ำ: ขั้นตอน F (n: จำนวนเต็ม); เริ่มเขียน (n); ถ้าไม่มี
ปัญหาที่ 17: ให้อัลกอริทึมแบบเรียกซ้ำ: ขั้นตอน F (n: จำนวนเต็ม); เริ่มเขียน (n); ถ้าไม่มี
ปัญหาที่ 18: ให้อัลกอริทึมแบบเรียกซ้ำ: ขั้นตอน F (n: จำนวนเต็ม); เริ่มเขียน (n); ถ้าไม่มี
ปัญหาที่ 19: ให้อัลกอริทึมแบบเรียกซ้ำ: ขั้นตอน F (n: จำนวนเต็ม); เริ่มเขียน (n); ถ้าไม่มี
ปัญหาที่ 20: ให้อัลกอริทึมแบบเรียกซ้ำ: ขั้นตอน F (n: จำนวนเต็ม); เริ่มเขียน (n); ถ้าไม่มี
ปัญหาที่ 21: ให้อัลกอริทึมแบบเรียกซ้ำ: ขั้นตอน F (n: จำนวนเต็ม); เริ่มเขียน (n); ถ้าไม่มี
ปัญหาที่ 22: ให้อัลกอริทึมแบบเรียกซ้ำ: ขั้นตอน F (n: จำนวนเต็ม); เริ่มเขียน (n); ถ้าไม่มี
ปัญหาที่ 23: ให้อัลกอริทึมแบบเรียกซ้ำ: ขั้นตอน F (n: จำนวนเต็ม); เริ่มเขียน (n); ถ้าไม่มี
ปัญหาที่ 24: ให้อัลกอริทึมแบบเรียกซ้ำ: ขั้นตอน F (n: จำนวนเต็ม); เริ่มเขียน (n); ถ้าไม่มี
ปัญหาที่ 25: ให้อัลกอริทึมแบบเรียกซ้ำ: ขั้นตอน F (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 โดยทั่วไปจะเห็นว่าพารามิเตอร์ของขั้นตอนเป็นขีดจำกัดในการเปลี่ยนแปลงค่าตัวนับได้ไม่ยาก คุณสามารถสลับการโทรซ้ำและคำแนะนำในการทำซ้ำได้ ดังตัวอย่างต่อไปนี้ ตัวอย่างที่ 2
ขั้นตอน LoopImitation2 (i, n: จำนวนเต็ม); เริ่มต้นถ้าฉัน ในกรณีนี้ การเรียกโพรซีเจอร์แบบเรียกซ้ำจะเกิดขึ้นก่อนที่คำสั่งจะเริ่มดำเนินการ ก่อนอื่นอินสแตนซ์ใหม่ของโพรซีเดอร์จะเรียกอินสแตนซ์อื่นและต่อไปเรื่อย ๆ จนกว่าเราจะถึงค่าสูงสุดของตัวนับ หลังจากนี้เท่านั้น กระบวนการสุดท้ายที่ถูกเรียกจึงจะดำเนินการตามคำสั่ง จากนั้นขั้นตอนที่สองต่อสุดท้ายจะดำเนินการตามคำสั่ง ฯลฯ ผลลัพธ์ของการเรียก LoopImitation2(1, 10) จะเป็นการพิมพ์คำทักทายในลำดับย้อนกลับ: สวัสดี N10 ถ้าเราจินตนาการถึงสายโซ่ของโพรซีเดอร์ที่ถูกเรียกซ้ำ ดังนั้นในตัวอย่างที่ 1 เราจะผ่านมันจากโพรซีเจอร์ที่ถูกเรียกก่อนหน้านี้ไปยังโพรซีเดอร์ที่เรียกในภายหลัง ในตัวอย่างที่ 2 ตรงกันข้ามจากภายหลังไปก่อนหน้า ในที่สุด สามารถทำการเรียกซ้ำระหว่างคำสั่งสองช่วงตึกได้ ตัวอย่างเช่น: ขั้นตอน LoopImitation3 (i, n: จำนวนเต็ม); เริ่มเขียน ln("Hello N ", i); (บล็อคคำสั่งแรกอาจอยู่ที่นี่) ถ้าฉัน ในที่นี้ คำสั่งจากบล็อกแรกจะดำเนินการตามลำดับก่อน จากนั้นคำสั่งจากบล็อกที่สองจะดำเนินการในลำดับย้อนกลับ เมื่อเรียก LoopImitation3(1, 10) เราจะได้รับ: สวัสดี N.1 จะใช้เวลาสองลูปในการทำสิ่งเดียวกันโดยไม่มีการเรียกซ้ำ คุณสามารถใช้ประโยชน์จากข้อเท็จจริงที่ว่าการดำเนินการส่วนต่างๆ ของขั้นตอนเดียวกันนั้นมีการเว้นระยะห่างออกไปเมื่อเวลาผ่านไป ตัวอย่างเช่น: ตัวอย่างที่ 3: การแปลงตัวเลขเป็นไบนารี
ดังที่ทราบกันดีว่าการได้รับเลขฐานสองนั้นเกิดขึ้นโดยการหารด้วยเศษด้วยฐานของระบบตัวเลข 2 หากมีตัวเลข ตัวเลขหลักสุดท้ายของเลขฐานสองจะเท่ากับ นำส่วนจำนวนเต็มจากการหารด้วย 2: เราได้ตัวเลขที่มีการแทนเลขฐานสองเหมือนกัน แต่ไม่มีเลขหลักสุดท้าย ดังนั้นจึงเพียงพอที่จะทำซ้ำการดำเนินการทั้งสองข้างต้นจนกว่าฟิลด์การหารถัดไปจะได้รับส่วนจำนวนเต็มเท่ากับ 0 หากไม่มีการเรียกซ้ำจะมีลักษณะดังนี้: ในขณะที่ x>0 เริ่มต้น c:=x mod 2; x:=x div 2; เขียน(ค); จบ; ปัญหาที่นี่คือตัวเลขของการเป็นตัวแทนไบนารี่ถูกคำนวณในลำดับย้อนกลับ (ล่าสุดอยู่ก่อน) หากต้องการพิมพ์ตัวเลขในรูปแบบปกติ คุณจะต้องจำตัวเลขทั้งหมดในองค์ประกอบอาร์เรย์และพิมพ์เป็นวงแยก การใช้การเรียกซ้ำ ไม่ใช่เรื่องยากที่จะได้เอาต์พุตในลำดับที่ถูกต้องโดยไม่มีอาร์เรย์และการวนซ้ำที่สอง กล่าวคือ: ขั้นตอน BinaryRepresentation (x: จำนวนเต็ม); var c, x: จำนวนเต็ม; เริ่มต้น (บล็อกแรก ดำเนินการตามลำดับการเรียกขั้นตอน) c:= x mod 2; x:= x div 2; (การโทรแบบเรียกซ้ำ) ถ้า x>0 แล้ว BinaryRepresentation(x); (บล็อกที่สอง ดำเนินการในลำดับย้อนกลับ) เขียน (c); จบ; โดยทั่วไปแล้ว เราไม่ได้รับรางวัลใด ๆ ตัวเลขของการแทนค่าไบนารี่จะถูกเก็บไว้ในตัวแปรท้องถิ่น ซึ่งจะแตกต่างกันไปในแต่ละอินสแตนซ์ที่รันอยู่ของโพรซีเดอร์แบบเรียกซ้ำ นั่นคือไม่สามารถบันทึกหน่วยความจำได้ ในทางตรงกันข้าม เราเปลืองหน่วยความจำเพิ่มเติมเพื่อจัดเก็บตัวแปรภายในเครื่อง 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 แต่ละครั้งจะสร้างสำเนาของตัวเอง 2 ชุด แต่ละสำเนาจะสร้างอีก 2 ชุด และต่อๆ ไป จำนวนการดำเนินการเพิ่มขึ้นตามจำนวน 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แต่เป็นต้นไม้ทั้งต้น มีปัญหาหลายประเภทเมื่อต้องจัดระเบียบกระบวนการคำนวณในลักษณะนี้ สำหรับพวกเขา การเรียกซ้ำจะเป็นวิธีแก้ปัญหาที่ง่ายและเป็นธรรมชาติที่สุด พื้นฐานทางทฤษฎีสำหรับฟังก์ชันแบบเรียกซ้ำที่เรียกตัวเองมากกว่าหนึ่งครั้งคือส่วน คณิตศาสตร์แบบไม่ต่อเนื่องศึกษาต้นไม้ คำนิยาม:
เราจะเรียกเซตจำกัด ตประกอบด้วยหนึ่งหรือหลายโหนดที่: คำจำกัดความนี้เป็นแบบเรียกซ้ำ กล่าวโดยสรุป ต้นไม้คือชุดที่ประกอบด้วยรากและทรีย่อยที่ติดอยู่กับต้นไม้ ซึ่งก็คือต้นไม้เช่นกัน ต้นไม้ถูกกำหนดด้วยตัวมันเอง อย่างไรก็ตาม คำจำกัดความนี้สมเหตุสมผล เนื่องจากการเรียกซ้ำมีจำกัด แต่ละแผนผังย่อยมีโหนดน้อยกว่าแผนผังที่มีอยู่ ในท้ายที่สุด เราก็มาถึงแผนผังย่อยที่มีโหนดเดียว และนี่ก็ชัดเจนแล้วว่าคืออะไร ข้าว. 3. ต้นไม้. ในรูป รูปที่ 3 แสดงต้นไม้ที่มีเจ็ดโหนด แม้ว่าต้นไม้ธรรมดาจะเติบโตจากล่างขึ้นบน แต่ก็เป็นธรรมเนียมที่จะดึงต้นไม้กลับกัน เมื่อวาดไดอะแกรมด้วยมือ วิธีนี้สะดวกกว่าอย่างเห็นได้ชัด เนื่องจากความไม่สอดคล้องกันนี้ บางครั้งความสับสนจึงเกิดขึ้นเมื่อโหนดหนึ่งถูกกล่าวว่าอยู่เหนือหรือต่ำกว่าอีกโหนดหนึ่ง ด้วยเหตุนี้ จึงสะดวกกว่าที่จะใช้คำศัพท์ที่ใช้ในการอธิบายแผนภูมิลำดับวงศ์ตระกูล การเรียกโหนดที่ใกล้ชิดกับบรรพบุรุษราก และการเรียกโหนดที่อยู่ห่างไกลมากขึ้น ต้นไม้สามารถแสดงเป็นภาพกราฟิกได้ด้วยวิธีอื่น บางส่วนจะแสดงในรูป 4. ตามคำนิยาม ต้นไม้คือระบบของชุดที่ซ้อนกัน โดยที่ชุดเหล่านี้ไม่ได้ตัดกันหรือรวมกันทั้งหมด ฉากดังกล่าวสามารถแสดงเป็นขอบเขตบนเครื่องบินได้ (รูปที่ 4a) ในรูป 4b, เซตที่ซ้อนกันไม่ได้อยู่บนระนาบ แต่ถูกต่อให้ยาวเป็นเส้นเดียว ข้าว. 4b ยังสามารถใช้เป็นแผนภาพของสูตรพีชคณิตบางสูตรที่มีวงเล็บซ้อนกันได้ ข้าว. รูปที่ 4c ให้อีกวิธีหนึ่งที่ได้รับความนิยมในการแสดงโครงสร้างต้นไม้เป็นรายการที่เซ ข้าว. 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 ในขั้นตอนการเรียกซ้ำที่สอดคล้องกัน การดำเนินการจะอยู่ในช่องว่างระหว่างการเรียกซ้ำ โดยเฉพาะสำหรับต้นไม้ไบนารี: // Inorder Traversal – ชื่อภาษาอังกฤษสำหรับขั้นตอนลำดับย้อนกลับ InorderTraversal((อาร์กิวเมนต์)); เริ่มต้น // การเดินทางทรีย่อยด้านซ้ายถ้า (มีทรีย่อยด้านซ้าย) จากนั้น InorderTraversal ((อาร์กิวเมนต์ 2)); //ส่งผ่านรูท DoSomething((อาร์กิวเมนต์)); //สำรวจทรีย่อยที่ถูกต้องถ้า (มีทรีย่อยที่ถูกต้อง) จากนั้น InorderTraversal((อาร์กิวเมนต์ 3)); จบ; อัลกอริธึมการแวะผ่านลำดับสุดท้าย: สำหรับต้นไม้ในรูป 3 และ 4 จะให้ลำดับของโหนด: B, D, E, G, F, C, A ในขั้นตอนการเรียกซ้ำที่สอดคล้องกัน การดำเนินการจะอยู่หลังจากการเรียกซ้ำ โดยเฉพาะสำหรับต้นไม้ไบนารี: // Postorder Traversal – ชื่อภาษาอังกฤษสำหรับขั้นตอนการสั่งซื้อสิ้นสุด PostorderTraversal((อาร์กิวเมนต์)); เริ่มต้น // การเดินทางทรีย่อยด้านซ้ายถ้า (มีทรีย่อยด้านซ้าย) จากนั้น PostorderTraversal ((อาร์กิวเมนต์ 2)); // การข้ามทรีย่อยที่ถูกต้องถ้า (มีทรีย่อยที่ถูกต้อง) จากนั้น PostorderTraversal ((อาร์กิวเมนต์ 3)); //ส่งผ่านรูท DoSomething((อาร์กิวเมนต์)); จบ; หากข้อมูลบางอย่างอยู่ในโหนดต้นไม้ ก็สามารถใช้โครงสร้างข้อมูลไดนามิกที่เหมาะสมในการจัดเก็บข้อมูลได้ ใน Pascal ทำได้โดยใช้ตัวแปรประเภทบันทึกที่มีพอยน์เตอร์ไปยังแผนผังย่อยที่เป็นประเภทเดียวกัน ตัวอย่างเช่น ต้นไม้ไบนารีที่แต่ละโหนดมีจำนวนเต็มสามารถจัดเก็บได้โดยใช้ตัวแปรประเภท PTree ซึ่งอธิบายไว้ด้านล่าง: พิมพ์ PTree = ^TTree; TTree = บันทึก Inf: จำนวนเต็ม; LeftSubTree, RightSubTree: PTree; จบ; แต่ละโหนดมีประเภท PTree นี่คือพอยน์เตอร์ ซึ่งหมายความว่าแต่ละโหนดจะต้องถูกสร้างขึ้นโดยการเรียกโพรซีเดอร์ใหม่ หากโหนดเป็นโหนดปลายสุด ฟิลด์ LeftSubTree และ RightSubTree จะถูกกำหนดค่า ไม่มี- มิฉะนั้น โหน LeftSubTree และ RightSubTree จะถูกสร้างขึ้นด้วยขั้นตอนใหม่ บันทึกดังกล่าวรายการหนึ่งแสดงอยู่ในแผนผังในรูป 5. ข้าว. 5. การแสดงแผนผังของบันทึกประเภท TTree บันทึกมีสามฟิลด์: Inf - ตัวเลข, LeftSubTree และ RightSubTree - ตัวชี้ไปยังบันทึกประเภท TTree เดียวกัน ตัวอย่างของต้นไม้ที่ประกอบด้วยบันทึกดังกล่าวแสดงในรูปที่ 6 ข้าว. 6. ต้นไม้ที่ประกอบด้วยบันทึกประเภท TTree แต่ละรายการจะเก็บตัวเลขและตัวชี้สองตัวที่สามารถมีอย่างใดอย่างหนึ่งได้ ไม่มีหรือที่อยู่ของบันทึกอื่นที่เป็นประเภทเดียวกัน หากคุณไม่เคยทำงานกับโครงสร้างที่ประกอบด้วยบันทึกที่มีลิงก์ไปยังบันทึกประเภทเดียวกันมาก่อน เราขอแนะนำให้คุณทำความคุ้นเคยกับเนื้อหาเกี่ยวกับ ลองพิจารณาอัลกอริธึมสำหรับการวาดต้นไม้ที่แสดงในรูปที่ 1 6. หากแต่ละบรรทัดถือเป็นโหนด รูปภาพนี้จะตรงตามคำจำกัดความของแผนผังต้นไม้ที่ให้ไว้ในส่วนก่อนหน้าโดยสมบูรณ์ ข้าว. 6. ต้นไม้. โพรซีเดอร์แบบเรียกซ้ำจะวาดหนึ่งบรรทัดอย่างชัดเจน (ส่วน trunk ขึ้นไปถึงสาขาแรก) จากนั้นเรียกตัวเองให้วาดแผนผังย่อยทั้งสอง ต้นไม้ย่อยแตกต่างจากต้นไม้ที่อยู่ในพิกัดของจุดเริ่มต้น มุมการหมุน ความยาวลำต้น และจำนวนกิ่งก้านที่มีอยู่ (น้อยกว่าหนึ่งอัน) ความแตกต่างทั้งหมดนี้ควรทำเป็นพารามิเตอร์ของโพรซีเดอร์แบบเรียกซ้ำ ตัวอย่างของขั้นตอนดังกล่าวซึ่งเขียนด้วยภาษา 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); โปรดทราบว่าการวาดจะดำเนินการก่อนการโทรแบบเรียกซ้ำ นั่นคือ ต้นไม้จะถูกวาดในลำดับโดยตรง ตามตำนานในวิหารใหญ่แห่งบานารัส ใต้อาสนวิหารซึ่งทำเครื่องหมายไว้ตรงกลางของโลก มีจานทองสัมฤทธิ์ซึ่งมีแท่งเพชร 3 แท่งติดอยู่ สูง 1 ศอกและหนาเหมือนผึ้ง กาลครั้งหนึ่งนานมาแล้ว พวกภิกษุในอารามแห่งนี้ได้กระทำความผิดต่อพระพรหม พระพรหมทรงโกรธเคืองจึงทรงสร้างไม้เท้าสูง 3 อัน วางแผ่นทองคำบริสุทธิ์ 64 แผ่นไว้บนแผ่นหนึ่ง เพื่อให้แผ่นเล็กแต่ละแผ่นวางอยู่บนแผ่นที่ใหญ่กว่า ทันทีที่ดิสก์ทั้ง 64 แผ่นถูกย้ายจากไม้เท้าที่พระเจ้าพรหมวางไว้เมื่อสร้างโลกไปยังไม้เรียวอื่นหอคอยพร้อมกับวิหารจะกลายเป็นฝุ่นและโลกจะพินาศภายใต้เสียงฟ้าร้อง ปริศนานี้ถูกเสนอขึ้นโดยอิสระจากพระพรหมเมื่อปลายศตวรรษที่ 19 โดยนักคณิตศาสตร์ชาวฝรั่งเศส เอดูอาร์ด ลูคัส รุ่นที่ขายมักจะใช้ดิสก์ 7-8 แผ่น (รูปที่ 7) ข้าว. 7. ปริศนา “หอคอยแห่งฮานอย” สมมติว่ามีวิธีแก้ปัญหาสำหรับ n-1 ดิสก์ แล้วสำหรับการขยับ nดิสก์ ให้ดำเนินการดังนี้: 1) กะ n-1 ดิสก์ เพราะสำหรับกรณีนี้ n= 1 อัลกอริธึมการจัดเรียงใหม่นั้นชัดเจน จากนั้นโดยการเหนี่ยวนำโดยใช้การกระทำ (1) - (3) เราสามารถจัดเรียงดิสก์ตามจำนวนที่ต้องการได้ มาสร้างขั้นตอนแบบเรียกซ้ำที่พิมพ์ลำดับกะทั้งหมดสำหรับดิสก์ตามจำนวนที่กำหนด แต่ละครั้งที่มีการเรียกขั้นตอนดังกล่าว จะต้องพิมพ์ข้อมูลเกี่ยวกับหนึ่งกะ (จากจุดที่ 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); จบ; โปรดสังเกตว่าชุดของกระบวนการที่เรียกซ้ำในกรณีนี้จะสร้างต้นไม้ที่เคลื่อนที่ในลำดับที่กลับกัน งานแยกวิเคราะห์คือการคำนวณค่าของนิพจน์โดยใช้สตริงที่มีอยู่ซึ่งมีนิพจน์ทางคณิตศาสตร์และค่าที่ทราบของตัวแปรที่รวมอยู่ในนั้น กระบวนการคำนวณนิพจน์ทางคณิตศาสตร์สามารถแสดงเป็นต้นไม้ไบนารีได้ อันที่จริง ตัวดำเนินการทางคณิตศาสตร์แต่ละตัว (+, –, *, /) ต้องใช้ตัวถูกดำเนินการสองตัว ซึ่งจะเป็นนิพจน์ทางคณิตศาสตร์ด้วย และด้วยเหตุนี้ จึงถือเป็นแผนผังย่อยได้ ข้าว. รูปที่ 8 แสดงตัวอย่างต้นไม้ที่สอดคล้องกับนิพจน์: ข้าว. 8. แผนผังไวยากรณ์ที่สอดคล้องกับนิพจน์ทางคณิตศาสตร์ (6) ในแผนผังดังกล่าว โหนดสุดท้ายจะเป็นตัวแปรเสมอ (ในที่นี้ x) หรือค่าคงที่ตัวเลข และโหนดภายในทั้งหมดจะมีตัวดำเนินการทางคณิตศาสตร์ ในการรันโอเปอเรเตอร์ คุณต้องประเมินโอเปอเรเตอร์ของมันก่อน ดังนั้นต้นไม้ในรูปนี้จึงควรเคลื่อนที่ตามลำดับขั้ว ลำดับโหนดที่สอดคล้องกัน เรียกว่า สัญกรณ์โปแลนด์ย้อนกลับนิพจน์ทางคณิตศาสตร์ เมื่อสร้างแผนผังไวยากรณ์คุณควรใส่ใจ คุณสมบัติถัดไป- เช่น ถ้ามีนิพจน์ และเราจะอ่านการดำเนินการของการบวกและการลบจากซ้ายไปขวา จากนั้นแผนผังไวยากรณ์ที่ถูกต้องจะมีเครื่องหมายลบแทนเครื่องหมายบวก (รูปที่ 9a) ที่จริงแล้ว ต้นไม้นี้สอดคล้องกับนิพจน์ เป็นไปได้ที่จะทำให้การสร้างต้นไม้ง่ายขึ้นหากคุณวิเคราะห์นิพจน์ (8) ในทางกลับกัน จากขวาไปซ้าย ในกรณีนี้ ผลลัพธ์ที่ได้คือต้นไม้ที่มีรูปที่ 9b เทียบเท่ากับทรี 8a แต่ไม่จำเป็นต้องเปลี่ยนป้าย ในทำนองเดียวกัน จากขวาไปซ้าย คุณต้องวิเคราะห์นิพจน์ที่มีตัวดำเนินการการคูณและการหาร ข้าว. 9. ต้นไม้ไวยากรณ์สำหรับการแสดงออก ก – ข + คเมื่ออ่านจากซ้ายไปขวา (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; หมายเลข:= หมายเลข div n; จบ; ทำอะไรสักอย่าง (ดัชนี); จบ; คำถามควบคุม 1. กำหนดว่าขั้นตอนและฟังก์ชันแบบเรียกซ้ำต่อไปนี้จะทำอะไร (a) ขั้นตอนต่อไปนี้จะพิมพ์ออกมาเมื่อ Rec(4) ถูกเรียกอย่างไร? ขั้นตอน Rec (a: จำนวนเต็ม); เริ่มเขียน (a); ถ้า a>0 แล้ว Rec(a-1); เขียน(ก); จบ; (b) ค่าของฟังก์ชัน Nod(78, 26) จะเป็นเท่าใด? ฟังก์ชั่น Nod(a, b: integer): จำนวนเต็ม; เริ่มถ้า a > b แล้วพยักหน้า:= พยักหน้า(a – b, b) อย่างอื่น ถ้า b > a แล้วพยักหน้า:= พยักหน้า(a, b – a) อย่างอื่น พยักหน้า:= a; จบ; (c) สิ่งที่จะพิมพ์โดยขั้นตอนด้านล่างเมื่อมีการเรียก A(1)? ขั้นตอน 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. ต้องคำนวณแฟกทอเรียลให้เพียงพอ จำนวนมากครั้ง (ล้านหรือมากกว่า) เปรียบเทียบประสิทธิภาพของอัลกอริธึมแบบเรียกซ้ำและแบบวนซ้ำ เวลาดำเนินการจะแตกต่างกันมากน้อยเพียงใด และอัตราส่วนนี้จะขึ้นอยู่กับจำนวนแฟกทอเรียลที่กำลังคำนวณอยู่อย่างไร 2. เขียนฟังก์ชันแบบเรียกซ้ำเพื่อตรวจสอบว่าวงเล็บอยู่ในสตริงอย่างถูกต้องหรือไม่ หากการจัดเตรียมถูกต้องจะตรงตามเงื่อนไขต่อไปนี้: (a) จำนวนวงเล็บเปิดและปิดเท่ากัน ตัวอย่างตำแหน่งที่ไม่ถูกต้อง:)(, ())(, ())(() ฯลฯ 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.2
…
สวัสดี N10
…
สวัสดี N.1
…
สวัสดี N10
สวัสดี N10
…
สวัสดี N.14. ความสัมพันธ์ที่เกิดซ้ำ การเรียกซ้ำและการวนซ้ำ
5. ต้นไม้
5.1. คำจำกัดความพื้นฐาน วิธีพรรณนาต้นไม้
ก) มีโหนดพิเศษหนึ่งโหนดที่เรียกว่ารากของทรีนี้
b) โหนดที่เหลือ (ไม่รวมราก) จะอยู่ในเซตย่อยที่แยกจากกันแบบคู่ ซึ่งแต่ละโหนดจะเป็นต้นไม้ ต้นไม้เรียกว่า ต้นไม้ย่อยของต้นไม้ต้นนี้5.2. ต้นไม้ที่ผ่าน
5.3. การแสดงต้นไม้ในหน่วยความจำคอมพิวเตอร์
6. ตัวอย่างของอัลกอริธึมแบบเรียกซ้ำ
6.1. วาดต้นไม้
6.2. ฮานอยทาวเวอร์
กระบวนการนี้กำหนดให้ดิสก์ที่มีขนาดใหญ่กว่าไม่เคยไปอยู่ทับดิสก์ที่เล็กกว่า พระภิกษุอยู่ในภาวะที่กลืนไม่เข้าคายไม่ออก: ควรทำกะตามลำดับไหน? จำเป็นต้องจัดเตรียมซอฟต์แวร์เพื่อคำนวณลำดับนี้
2) กะ nดิสก์แผ่นที่ 3 ลงบนพินว่างที่เหลือ
3) เราเปลี่ยนสแต็กจาก n-1 ดิสก์ที่ได้รับในจุด (1) ด้านบน n-ดิสก์ที่6.3. การแยกวิเคราะห์นิพจน์ทางคณิตศาสตร์
7.3. การกำหนดโหนดต้นไม้ตามหมายเลข
โปรดทราบอีกครั้งว่าวิธีการนี้ไม่เป็นสากลและคุณจะต้องคิดสิ่งที่แตกต่างออกไปสำหรับแต่ละงาน
งาน
(b) ภายในคู่ใดๆ มีวงเล็บเปิด - ปิดที่สอดคล้องกัน วางวงเล็บอย่างถูกต้อง
เขียนโปรแกรมแบบเรียกซ้ำเพื่อสร้างโครงสร้างวงเล็บเหลี่ยมปกติทั้งหมดที่มีความยาว 2 n.
(b) ถ้าเขียนโครงสร้างขนาดเล็กสองโครงสร้างตามลำดับวรรณกรรม
2. เอ็น. เวิร์ธ. อัลกอริทึมและโครงสร้างข้อมูล