อัลกอริธึมการเรียกซ้ำและการเรียกซ้ำ อัลกอริธึมการเรียกซ้ำและอัลกอริธึมแบบเรียกซ้ำ รับขั้นตอนวิธีอัลกอริธึมแบบเรียกซ้ำ 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
สวัสดี N.2

สวัสดี N10

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

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

ตัวอย่างที่ 2

ขั้นตอน LoopImitation2 (i, n: จำนวนเต็ม); เริ่มต้นถ้าฉัน

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

สวัสดี N10

สวัสดี N.1

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

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

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

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

สวัสดี N.1

สวัสดี N10
สวัสดี N10

สวัสดี 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 จำนวนมาก อย่างไรก็ตาม โซลูชันนี้ดูสวยงามสำหรับฉัน

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 แต่ละครั้งจะสร้างสำเนาของตัวเอง 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แต่เป็นต้นไม้ทั้งต้น มีปัญหาหลายประเภทเมื่อต้องจัดระเบียบกระบวนการคำนวณในลักษณะนี้ สำหรับพวกเขา การเรียกซ้ำจะเป็นวิธีแก้ปัญหาที่ง่ายและเป็นธรรมชาติที่สุด

5. ต้นไม้

พื้นฐานทางทฤษฎีสำหรับฟังก์ชันแบบเรียกซ้ำที่เรียกตัวเองมากกว่าหนึ่งครั้งคือส่วน คณิตศาสตร์แบบไม่ต่อเนื่องศึกษาต้นไม้

5.1. คำจำกัดความพื้นฐาน วิธีพรรณนาต้นไม้

คำนิยาม: เราจะเรียกเซตจำกัด ประกอบด้วยหนึ่งหรือหลายโหนดที่:
ก) มีโหนดพิเศษหนึ่งโหนดที่เรียกว่ารากของทรีนี้
b) โหนดที่เหลือ (ไม่รวมราก) จะอยู่ในเซตย่อยที่แยกจากกันแบบคู่ ซึ่งแต่ละโหนดจะเป็นต้นไม้ ต้นไม้เรียกว่า ต้นไม้ย่อยของต้นไม้ต้นนี้

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

ข้าว. 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 กรัม;

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

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

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

สำหรับต้นไม้ในรูป 3 และ 4 จะให้ลำดับของโหนด: B, D, E, G, F, C, A

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

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

5.3. การแสดงต้นไม้ในหน่วยความจำคอมพิวเตอร์

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

พิมพ์ PTree = ^TTree; TTree = บันทึก Inf: จำนวนเต็ม; LeftSubTree, RightSubTree: PTree; จบ;

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

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

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

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

ข้าว. 6. ต้นไม้ที่ประกอบด้วยบันทึกประเภท TTree แต่ละรายการจะเก็บตัวเลขและตัวชี้สองตัวที่สามารถมีอย่างใดอย่างหนึ่งได้ ไม่มีหรือที่อยู่ของบันทึกอื่นที่เป็นประเภทเดียวกัน

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

6. ตัวอย่างของอัลกอริธึมแบบเรียกซ้ำ

6.1. วาดต้นไม้

ลองพิจารณาอัลกอริธึมสำหรับการวาดต้นไม้ที่แสดงในรูปที่ 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);

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

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

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

ปริศนานี้ถูกเสนอขึ้นโดยอิสระจากพระพรหมเมื่อปลายศตวรรษที่ 19 โดยนักคณิตศาสตร์ชาวฝรั่งเศส เอดูอาร์ด ลูคัส รุ่นที่ขายมักจะใช้ดิสก์ 7-8 แผ่น (รูปที่ 7)

ข้าว. 7. ปริศนา “หอคอยแห่งฮานอย”

สมมติว่ามีวิธีแก้ปัญหาสำหรับ n-1 ดิสก์ แล้วสำหรับการขยับ nดิสก์ ให้ดำเนินการดังนี้:

1) กะ n-1 ดิสก์
2) กะ nดิสก์แผ่นที่ 3 ลงบนพินว่างที่เหลือ
3) เราเปลี่ยนสแต็กจาก n-1 ดิสก์ที่ได้รับในจุด (1) ด้านบน n-ดิสก์ที่

เพราะสำหรับกรณีนี้ 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); จบ;

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

6.3. การแยกวิเคราะห์นิพจน์ทางคณิตศาสตร์

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

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

ข้าว. 8. แผนผังไวยากรณ์ที่สอดคล้องกับนิพจน์ทางคณิตศาสตร์ (6)

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

เรียกว่า สัญกรณ์โปแลนด์ย้อนกลับนิพจน์ทางคณิตศาสตร์

เมื่อสร้างแผนผังไวยากรณ์คุณควรใส่ใจ คุณสมบัติถัดไป- เช่น ถ้ามีนิพจน์

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

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

ข้าว. 9. ต้นไม้ไวยากรณ์สำหรับการแสดงออก + เมื่ออ่านจากซ้ายไปขวา (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>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) จำนวนวงเล็บเปิดและปิดเท่ากัน
(b) ภายในคู่ใดๆ มีวงเล็บเปิด - ปิดที่สอดคล้องกัน วางวงเล็บอย่างถูกต้อง

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

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

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

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

บันทึก: โครงสร้างวงเล็บที่ถูกต้องที่มีความยาวขั้นต่ำ "()" โครงสร้างที่มีความยาวมากกว่านั้นได้มาจากโครงสร้างที่มีความยาวน้อยกว่าได้สองวิธี:

(ก) หากนำโครงสร้างที่มีขนาดเล็กกว่ามาใส่ในวงเล็บ
(b) ถ้าเขียนโครงสร้างขนาดเล็กสองโครงสร้างตามลำดับ

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

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

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

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

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

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

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

วรรณกรรม

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




สูงสุด