Upload
others
View
1
Download
0
Embed Size (px)
Citation preview
บทท่ี7 รเีคอรซ์ฟิ
วตัถปุระสงค์เชงิพฤติกรรม (Behavioral Objectives)หลังจากศึกษาจบบทเรยีนน้ีแล้ว นักศึกษาจะมคีวามสามารถดังน้ี(After studying this chapter, you will be able to)
1. ศึกษา / ปฏิบติัการฟงัก์ชนัรเีคอรซ์ฟิ2. แสดงตัวอยา่งการใชฟ้งัก์ชนัรเีคอรซ์ฟิ3. ยกตัวอยา่งรเีคอรซ์ฟิโดยอ้อม4. ปฏิบติัการการสบัเปล่ียนลำาดับ5. ลำาดับขัน้ตอนปฏิบติัการทำางานแบบรเีคอรซ์ฟิ6. จดับอรด์เชงิปฏิบติัการ รเีคอรซ์ฟิ“ ”7. สนทนาเชงิปฏิบติัการ การสบัเปล่ียนลำาดับ“ ”8. อธบิายคำาศัพท์ได้ 10 คำา
ในระหวา่งการออกแบบเขยีนโปรแกรมแบบบนลงล่าง (Top-down Design) จะมงีานยอ่ย (Subtask) เพื่อแก้ปัญหาในแต่ละเร ื่อง และผู้เขยีนโปรแกรมต้องการใชง้านยอ่ยที่เรยีบง่ายและสามารถแก้ไขปัญหาที่มขีนาดใหญ่ได้ จงึมกีารใชง้านยอ่ยในลักษณะที่เรยีกตัวเองขึ้นมาทำางานเรยีกวา่ รเีคอรซ์ฟิ (Recursive) เป็นเทคนิคที่สว่นใหญ่ใชก้ ับโ ค ร ง ส ร า้ ง ข อ้ ม ูล ท ี่ไ ม เ่ ป ็น เ ช งิ เ ส น้ อ ย า่ ง เ ช น่ ท ร (ีTree) แ ล ะกราฟ (Graph) เพื่อใหก้ารใชง้านมคีวามสะดวกและง่าย ฟงัก์ชนัรเีคอรซ์ฟิโดยสว่นใหญ่การเขยีนโปรแกรมจะใชฟ้งัก์ชนัเรยีกฟงัก์ชนัอ่ืนขึ้นมาทำางาน บางครัง้อาจต้องมกีารใหฟ้งัก์ชนัเรยีกตัวเองขึ้นมาทำางานเรยีกวา่ฟงัก์ชนัรีเคอรซ์ฟิ (Recursive Function) ซึ่งใชใ้นบางภาษา เชน่ ภาษาซ ีปาสคาล จาวา ปกติแล้วการเขยีนอัลกอรทึิมท่ีมกีารทำางานซำ้าแบบเดิมจะใชใ้นการวนลปูแบบต่าง ๆ แต่ใชเ้ป็นฟงัก์ชนัรเีคอรซ์ฟิแทนได้ในลักษณะท ำาซ ำ้าอัตโนมติั บางครัง้จงึเรยีกวา่ ลปูเสมอืน (Virtual Loop)
ลักษณะของฟงัก์ชนัรเีคอรซ์ฟิมคีวามรวบรดัสัน้กวา่แบบวนลปู โดยพจิารณาจากปัญหาในการคำานวณหาค่าของ xn , ให ้x เป็นเลขทศนิยมและ n เป็นเลขยกกำาลังจำานวนเต็มไมเ่ป็นค่าลบ ครัง้แรกกำาหนดใหก้ารทำางานเป็นแบบวนลปู จะเป็นการนำา x มาคณูกัน n ครัง้ หากนำามาใช้คำานวณกับเลขยกกำาลังที่เรยีงต่อกันทีละค่า เชน่ คำานวณหาค่าของ 30 - 35 จะได้เป็นดังต่อไปน้ี 30 = 1 31 = 3 32 = 3 * 3 = 9 33 = 3 * 3 * 3 = 27 34 = 3 * 3 * 3 * 3 = 81 35 = = 3 * 3 * 3 * 3 * 3 = 243 จากตัวอยา่งจะเหน็วา่การคำานวณหาค่าจากเลขยกกำาลังของ 3 สามารถนำาค่าจากการคำานวณของเลขยกกำาลังก่อนหน้านี้มาใช ้เชน่ นำาค่าของ 33 =27 มาใชก้ับ 34 หรอืค่าของ 34 = 81 มาใชก้ับ 35 ได้เป็น ดังน้ี 34 = 3 * 33 = 3 * 27 = 81 35 = 3 * 34 = 3 * 81 = 243 จากการคำานวณหาค่าจากเลขยกกำาลังของ 3 ในลักษณะดังกล่าว สิง่ที่เราต้องทราบก็คือ 30 ซึ่งเท่ากับ 0 และความสมัพนัธพ์ื้นฐานของเลขยกกำาลังนี้ได้เป็น 3n = 3 * 3 n-1 เป็นฟงัก์ชนัรเีคอรซ์ฟิได้โดยมีกฎเกณฑ์ ดังน้ี
x 0 = 1x n = x * x n -1, for x > 0รูปแบบของฟงัก์ชนัรเีคอรซ์ฟิดังกล่าวจะมลัีกษณะท่ีเรยีกวา่ Induct
ive ซึ่งเป็นการตรวจสอบกฎเกณฑ์ที่กำาหนดขึ้นมาทางคณิตศาสตร ์แบง่เป็นสองขัน้ตอน คือ กฎเกณฑ์เป็นจรงิเมื่อมกีารตรวจสอบกับกรณีหรอืปัญหาขนาดเล็กและกฎเกณฑ์ที่ใชก้ับกรณีหรอืปัญหาขนาดเล็กที่เป็นจรงินี้สามารถนำาไปขยายใชก้ับกรณีหรอืปัญหาที่มขีนาดใหญ่ขึ้น เชน่ กฎเกณฑ์ที่กำาหนดขึ้นเป็นจรงิเมื่อ 1 < n < k และจะเป็นจรงิเชน่กันเมื่อ 1 < n <
k + 1 โดยทัว่ไปฟงัก์ชนัรเีคอรซ์ฟิที่ก ำาหนดใหม้กีารเรยีกตัวเองขึ้นมาทำางานจะประกอบด้วย 2 สว่น คือ1. กรณีพื้นฐาน (Base Case) เป็นสว่นที่ค่าของฟงัก์ชนัสอดคล้องหรอืเท่ากับค่าของพารามเิตอรท์ี่สง่มาหรอืปัญหาได้ถกูแก้ไขเพยีงพอแล้ว ไมม่ีการเรยีกตัวเองขึ้นมาทำางานอีก2.เรยีกตัวเอง (Recursive Call) เป็นสว่นที่ค่าของฟงัก์ชนัที่ถกูเรยีกนำามาใชก้ับค่าของพารามเิตอรท์ี่สง่มาหรอืปัญหายงัแก้ไขไมเ่พยีงพอ จะต้องเรยีกตัวเองขึ้นมาทำางานต่อไปจนกวา่จะได้เป็นกรณีพื้นฐานจงึจบการทำางาน จากตัวอยา่งเลขยกกำาลังที่ผ่านมาแบง่สว่นท่ีเป็นกรณีพื้นฐานและสว่นท่ีเรยีกตัวเอง คือ Base Case: x0 = 1 Recursive Call: xn = x * xn-1, for x > 0 จากตัวอยา่งเลขยกกำาลังเมื่อใชก้ับฟงัก์ชนัรเีคอรซ์ฟิคำานวณหาค่าของ 35 จะมดี ังในร ูปที่ 7.1 ในคร ัง้แรกหาค่าของ 35 จะต้องคำานวณ 34 หาค่ามาคณูกับ 3 และหาค่าของ 34 ต้องคำานวณ 33 หาค่ามาคณูกับ 3 เชน่กัน การทำางานจงึเป็นชว่งการเรยีกตัวเองขึ้นมาทำางานไปเรื่อยๆ กับการหาค่าของ 32,31, และ 30 ซึ่งเป็นกรณีพื้นฐาน
รูปท่ี 7.1 ขัน้ตอนการเรยีกตัวเองทำางานของการหาค่าเลขยกกำาลัง จากกรณีพื้นฐานจะได้ค่า 30 = 1 ก็เป็นการถอยกลับไปตามทางเดิม (Backtracking) ดังในรูปที่ 7.2 โดยนำาค่าที่ได้กลับไปคำานวณกับ 31 เม ื่อได ้ค ่าผลลัพธก์ ็สง่ถอยกลับไปเร ื่อย ๆ แบบเดียวกันกับ 32 ,33 ,34 , และครัง้สดุท้ายใหก้ับ 35 ท่ีเป็นคำาตอบ
รูปท่ี 7.2 ขัน้ตอนการถอยกลับทางเดิมของการหาค่าเลขยกกำาลัง รูปแบบการทำางานแบบรเีคอรซ์ฟิดังกล่าวนำามาเขยีนเป็นฟงัก์ชนั Power ได้เป็นตารางท่ี 7.1 และลักษณะการทำางานของฟงัก์ชนัน้ีเป็นดังในรูปที่ 7.3 ที่ต้องการหาค่าของ 35 เร ิม่ต้นด้วย Power (3.0, 5) เรยีกตัวเองมาทำางานลงไปเรื่อย ๆ จนถึงสว่นกรณีพื้นฐาน จากนัน้ถอยกลับขึ้นมาตามทางเดิม ซึ่งคำาตอบท่ีได้ คือ 243.0 ตารางท่ี 7.1 ฟงัก์ชนัหาค่าของเลขยกกำาลัง
ผลลัพธท่ี์ได้
เพิม่รูปรูปท่ี 7.3 ขัน้ตอนการทำางานของฟงัก์ชนั Power (3.0, 5)
ลักษณะฟงัก์ชนัรเีคอรซ์ฟิที่ผ่านมา การทำางานในสว่นแรกตัวเองใหข้ึ้นมาจะมกีารเรยีกเพยีงคร ัง้เดียว แต่บางฟงัก์ชนัอาจต้องมกีารเรยีกตัวเองขึ้นมาทำางานได้มากกวา่หนึ่งครัง้ เชน่ การหาตัวเลขไฟโบแนคซ ี(Fibonacci Number) ค ือ 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, … โดยเริม่ต้นเลข 0 กับ 1 จากนัน้เลขถัดไปจะเท่ากับเลขก่อนหน้าสองตัวมาบวกกันเมื่อนำามากำาหนดเป็นฟงัก์ชนัรเีคอรซ์ฟิก็จะได้กฎเกณฑ์เป็นดังน้ี f0 = 0 f1 = 1 f n = f n-1 + f n-2 , for n > 1 จากกฎเกณฑ์ดังกล่าวแบง่เป็นสองสว่น สว่นกรณีพื้นฐาน คือ f0 = 0 กับ f1 = 1 สว่นเรยีกตัวเอง คือ f n = fn-1 + fn-2 เขยีนเป็นฟงัก์ชนัรเีคอรซ์ฟิ Fib ได้เป็นตารางท่ี 7.2
ตารางที่ 7.2 ฟงัก์ชนัหาค่าตัวเลขไฟโบแนคซี long fib (int n ) {
if (n < 1) return 0 ; if (n < 3) return 1; return fib (n-1) + fib (n-2);
เมื่อต้องการทราบตัวเลขไฟโบแนคซใีนลำาดับที่ 4 จะเรยีกฟงัก์ชนัรเีคอรซ์ฟิด้วย Fib (4) ลักษณะการทำางานของฟงัก์ชนัน้ีได้เป็นดังในรูปท่ี 7.4
เพิม่รูปรูปท่ี 7.4 ขัน้ตอนการทำางานของฟงัก์ชนั Fib (4)
จากรูปที่ 7.4 เป็นฟงัก์ชนัที่เรยีกตัวเองขึ้นมาทำางานและมีพารามเิตอรส์ง่ค่ามาในแบบเดียวกันมลัีกษณะท่ีเรยีกวา่ทรรีเีคอรซ์ฟิ (Recursive Tree) เนื่องจากสว่นเรยีกตัวเองมกีารเรยีกตัวเองมาทำางาน 2 ครัง้ ทำาใหม้กีารเรยีกตัวเองซำ้ามา 2 ครัง้กับพารามเิตอร ์0 เรยีกซำ้ามา 3 ครัง้กับพารามเิตอร ์1 และเรยีกซำ้ามา 2 ครัง้กับพารามเิตอร ์1 จะเหน็วา่ในชว่งท้ายของการเรยีกตัวเองมกีารเรยีกซำ้าตัวเดิมที่มพีารามเิตอรเ์ท่ากัน เมื่อนับจำานวนการเรยีกตัวเองมาทำางานของ Fib (4) หรอืรูปที่ 7.4 จะได้เท่ากับ 9 ครัง้เป็น ดังน้ี
จำานวนการเรยีกตัวเอง Fib (4) 1 Fib (3) 1
Fib (2) 2Fib (1) 3Fib (0) 2
พจิารณากับฟงัก์ชนัรเีคอรซ์ฟิที่เรยีกด้วย Fib (5) จะเรยีกตัวเองที่พารามเิตอร ์5 ในครัง้แรก 1 ครัง้ เรยีกตัวเองที่พารามเิตอร ์4 ม ีจำานวนการเรยีก 9 ครัง้ และเรยีกตัวเองที่พารามเิตอร ์3 มจีำานวนการเรยีก 5 ครัง้ จำานวนการเรยีกตัวเองทัง้หมดจะเท่ากับ 15 แต่ถ้าเร ิม่ต้น
ที่ Fib (6) จำานวนการเรยีกตัวเองทัง้หมดจะเท่ากับ 25 ซึ่งเป็นการเพิม่จ ำานวนอยา่งรวดเรว็ในลักษณะของเอ็กโปแนนเซลี(Exponential, xn) สรา้งเป็นตารางการเพิม่จำานวนครัง้การเรยีกตัวเองตามลำาดับค่าดังรูปท่ี 7.5
n Fib (n) n Fib (n)0 1 10 1771 1 11 2872 3 12 4653 5 13 7534 9 14 1,2195 15 15 1,9736 25 16 3,1937 41 17 5,1678 67 18 8,3619 109 19 13,529
รูปท่ี 7.5 ตารางการเพิม่จำานวนครัง้การเรยีกตัวเอง
การแก้ปัญหาแบบรเีคอรซ์ฟิกับการหาตัวเลขไฟโบแนคซจีงึไมใ่ชว่ธิกีารที่ดีเมื่อค่า n มค่ีามากขึ้น แต่จะใชว้ธิกีารแบบวนลปูมาใชแ้ทนจะเหมาะสมกวา่ เพยีงแต่ต้องมกีารเก็บค่าที่ได้จากการคำานวณและปรบัปรุงค่าเพื่อนำาไปใชใ้นครัง้ต่อไปในแต่ละรอบของการวนลปู ดังในตารางที่ 7.3
ซ ึ่งมตี ัวแปร f2 เก ็บผลรวมไว ้และ f0 กับ f1 เก็บค่าลำาดับถัดไปเพื่อใชใ้นรอบถัดไปของการวนลปู ตารางที่ 7.3 ฟงัก์ชนัหาค่าตัวเลขไฟโบแนคซแีบบวนลปู
long fibI (int n){ int i, f0, f1, f2; if (n < 1) return 0; if (n < 3)
return 1; f0 = 1; f1 = 1;
for (I = 3; i <= n; i++){f2 = f1 + f0;f0 = f1;f1 = f2;
}return f2;}
อ ีกวธิที ี่น ำามาใชห้ล ักเล ี่ยงการใชร้ เีคอรซ์ฟิค ือการใช ้โครงสรา้งขอ้มูลแบบอารเ์รยเ์ก็บค่าไว ้แต่ต้องใชพ้ื้นที่หน่วยความจำาเก็บขอ้มูลเพิม่ขึ้น ดังในตารางที่ 7.4 ซึ่งคล้ายกับแบบที่แล้วโดยไมม่ตีัวแปรเก็บผลรวมและค่าลำาดับถัดไปแต่จะมอีารเ์รย์ F เก็บผลรวมที่เป็นตัวเลขไฟโบแนคซ ีและในรอบถัดไปก็นำาค่าก่อนหน้านี้ในอารเ์รยม์าใชไ้ด้เลยตารางท่ี 7.4 ฟงัก์ชนัหาค่าตัวเลขไฟโบแนคซแีบบใชพ้ื้นท่ีเพิม่
long fibM (int n){ int i; long F[100];
if(n < 1) return 0; if(n < 3) return 1;
f[1] = 1; f[2] = 1;
for (i = 3; i <= n; i++) F[i] = F[i-1] + F[i-2];
Return F[n];}
นอกจากนี้การเรยีกตัวเองทำางานสองครัง้ในสว่นเรยีกตัวเองยงันำาไปใชก้ับปัญหาหอคอยฮานอย (Tower of Hanoi) การจดัเรยีงลำาดับขอ้มูลแบบรวดเรว็ (Quick Sort) ซึ่งแต่ละคร ัง้ที่เรยีกตัวเองมาทำางานปัญหาจะถกูแบง่เล็กลงเหลือเพยีงครึง่เดียวตัวอยา่งการใชฟ้งัก์ชนัรเีคอรซ์ฟิ ตัวอยา่งท่ีรูจ้กัซึ่งเหมาะสมกับการแก้ปัญหาแบบรเีคอรซ์ฟิคือปัญหาหอคอยฮานอย (Tower of Hanoi) ซึ่งมคีวามยุง่ยากหากจะแก้ไขด้วยวธิกีารอ่ืนที่ไมใ่ชร่เีคอรซ์ฟิ ปัญหาหอคอยฮานอยมคีวามซบัซอ้นในการแก้ไข ดังในรูปที่ 7.6 เพื่อต้องการยา้ยแผ่นไมท้ัง้หมดที่มขีนาดไมเ่ท่ากันจากเสาหลักด้านซา้ยไปท่ีเสาหลักด้านขวา และในการยา้ยแผ่นจะมกีฎเกณฑ์ท่ีกำาหนดไวด้ังน้ี
1. เมื่อมกีารยา้ยแผ่นไม ้จะต้องนำาไปวางในเสาหลักเสาใดเสาหน่ึง2. การยา้ยแผ่นไมจ้ะยา้ยได้คร ัง้ละแผ่น และเป็นแผ่นที่อยูข่า้งบน
สดุของเสาหลัก3. แผ่นไมท้ี่มขีนาดใหญ่กวา่ไมส่ามารถวางอยูบ่นแผ่นไมท้ี่มขีนาด
เล็กกวา่เพิม่รูป
รูปท่ี 7.6 ลักษณะปัญหาหอคอยฮานอย จากต ำานานเล ่าวา่มนี ักบวชลามะจากวดัแหง่หน ึ่งได ้ม ีพธิกีรรมที่เกี่ยวกับปัญหาท่ีซบัซอ้นซึ่งประกอบด้วยเจดียท์องคำา 3 ยอดซึ่งมลีักษณะเป็นเสาหลักเพื่อใชว้างแผ่นจานทองคำาจำานวน 64 แผ่นซอ้นทับกัน แต่ละแผ่นมขีนาดเล็กลงไมเ่ท่ากัน และนักบวชจะต้องยา้ยแผ่นจานจากเสาหลักหนึ่งตามกฎเกณฑ์ทัง้สามที่กล่าวมาปัญหาจงึจะสิน้สดุลง ถ้านักบวชยา้ยแต่ละแผ่นจานใชเ้วลา 1 วนิาทีและเริม่ต้นยา้ยเมื่อปีที่ 0 อยากทราบวา่ต้องใชเ้วลานานเท่าไรจงึจะยา้ยแผ่นจานทองคำาสำาเรจ็ ลักษณะของปัญหาที่ซบัซอ้นดังกล่าวสามารถแก้ไขได้ง่ายถ้าไขได้ง่ายถ้าจำานวนแผ่นจานมจีำานวนน้อย แต่จะยุง่ยากขึ้นเมื่อจำานวนแผ่น
จานมมีากเป็นเจด็แปดแผ่นหรอืมากกวา่นี้ แต่เมื่อใชห้ลักการแก้ปัญหาหอคอยฮานอยก็จะง่ายขึ้น ถ้าใหเ้สาหลักเป็นเสา A, B, C ตามลำาดับจากซา้ยไปขวา และมแีผ่นจานหนึ่งแผ่น ก็จะยา้ยจากเสา A ไปยงัเสา B ปัญหานี้แก้ไขได้เมื่อมีแผ่นจาน n = 1 (เป็นการยา้ยแผ่นจานทัง้หมดไปเก็บไวท้ี่เสา B สว่นอีกรูปแบบนำาไปเก็บไวท้ี่เสา C) แต่ถ้าการแก้ปัญหานี้สามารถใชก้ับแผ่นจานจำานวน n-1 การแก้ปัญหา n แผ่นจะง่ายเมื่อใชร้เีคอรซ์ฟิ ดังน้ี
1. ยา้ยแผ่นจานจำานวน n -1 ที่อยูบ่นสดุจากเสาหลัก A ไปไวย้งัเสาหลัก C โดยมเีสาหลัก B ทำาหน้าที่ชว่ยเหลือในการสลับแผ่นไปมา
2. ยา้ยแผ่นจานขนาดใหญ่สดุที่เหลืออยูจ่ากเสาหลัก A ไปไวย้งัเสาหลัก B
3. ยา้ยแผ่นจานจำานวน n-1 จากเสาหลัก C ไปไวย้งัเสาหลัก Bตามลำาดับเมื่อยา้ยแผ่นจานจำานวน n-1 จากเสาหลัก A ไปไวเ้สาหลัก
C ก็จะต้องยา้ยแผ่นจานจำานวน n-2 จากเสาหลัก A ไปไวเ้สาหลัก B ก่อน จากนัน้จงึยา้ยแผ่นจานที่ n-1 ไปไวเ้สาหลัก C จากอัลกอรทิึมดังกล่าวนำามาเขยีนเป็นฟงัก์ชนัรเีคอรซ์ฟิ Move Disk และเขยีนเป็นโปรแกรมจะได้ดังในตารางที่ 7.5 คือ โปรแกรม Towers.cตารางท่ี 7.5 ตัวอยา่งโปรแกรม Towers.c
ผลลัพธท่ี์ได้
วธิกีารแก้ปัญหาหอคอยฮานอยเมื่อเขยีนเป็นโปรแกรมและใหท้ำางานจะมกีารรอรบัค่าที่เป็นจำานวนแผ่นจานทัง้หมดไวท้ี่เสาหลัก A และเรยีกฟงัก์ชนั MoveDisk ถ้ากำาหนดใหแ้ผ่นจานมทีัง้หมด 4 แผ่นได้เป็นในรูปท่ี 7.7
เพิม่รูปรูปท่ี 7.7 จำานวนแผ่นจาน 4 แผ่นอยูบ่นเสาหลัก A ตอนเริม่ต้น
หลังจากนัน้ฟงัก ์ชนั MoveDisk ทำาการเรยีกตัวเองมาทำางานพรอ้มกับทำาการยา้ยแผ่นจานตามลำาดับทีละแผ่น ดังในรูปที่ 7.8 ตัง้แต่รูป (a) จนถึงรูป (0) ซึ่งแผ่นจานทัง้หมดถกูยา้ยไปอยูท่ี่เสาหลัก B จำานวนการยา้ยแผ่นจานทัง้หมดทีละแผ่นเท่ากับ 15 ครัง้
เพิม่รูปรูปท่ี 7.8 ลำาดับการยา้ยแผ่นจานจากเสาหลัก A ไปไวท่ี้เสาหลัก B
การทำางานของฟงัก์ชนันี้ถ้ามแีผ่นจาน 1 แผ่นจะมกีารยา้ย 1 ครัง้ ม ี2 แผ่นจะมกีารยา้ย 3 ครัง้ และม ี3 แผ่นจะมกีารยา้ย 7 ครัง้ ซึ่งอยูใ่นรูปแบบของ M (n) = 2n-1 ดังนัน้ ถ้าแผ่นจานมทีัง้หมด 64 แผ่นจะมกีารยา้ยแผ่นจานจำานวนประมาณ 1.84 * 1019 คร ัง้ หากการยา้ย
แผ่นแต่ละครัง้ใชเ้วลา 1 วนิาทีจะต้องใชเ้วลาทัง้หมดประมาณ 585 พนัล้านปีจงึจะยา้ยสำาเรจ็หรอืใหค้อมพวิเตอรท์ำาการยา้ยได้หนึ่งล้านคร ัง้ต่อวนิาทีจะใชเ้วลาทัง้หมดประมาณ 585,000 ปีรเีคอรซ์ฟิโดยอ้อม ตัวอยา่งฟงัชนัรเีคอรซ์ฟิที่ผ ่านมาจะเรยีกวา่รเีคอรซ์ฟิโดยตรง (Direct Recursion) คือฟงัก ์ชนัท ี่เรยีกตัวเองมาท ำางานโดยตรง แต่จะมรีเีคอรซ์ฟิโดยอ้อม (Indirect Recursion) ที่มกีารเรยีกฟงัก์ชนัอื่นมาทำางานและเป็นการเรยีกแบบลกูโซท่ี่ยอ้นกลับมาเรยีกฟงัก์ชนัแรก ดังในรูปที่ 7.9 ชนิดของรเีคอรซ์ฟิโดยอ้อมแบบง่าย ๆ คือ รีเคอรซ์ฟิสองฝ่าย (Mutual Recursion) ซึ่งมสีองฟงัก์ชนัที่ต่างเรยีกฟงัก์ชนัตรงขา้มมาท ำางาน และรเีคอรซ์ฟิโดยอ้อมทัว่ไป (General Indirect Recursion) ที่มหีลายฟงัก์ชนัเรยีกต่อกันโดยฟงัก์ชนัสดุท้ายเรยีกยอ้นกลับมายงัฟงัก์ชนัแรก
เพิม่รูปรูปท่ี 7.9 ชนิดของรเีคอรซ์ฟิโดยตรงและโดยอ้อม
ตัวอยา่งที่นำามาแสดงการใชร้เีคอรซ์ฟิโดยอ้อมจะเป็นรเีคอรซ์ฟิสองฝ่าย เป็นการคำานวณหาค่าทางตรโีกณมติิของ Sine และ Cose ซึ่งมีสมการคำานวณหาดังน้ี
Sin2θ = 2 Sin2θcos2θcos2θ = 1-2(sinθ) 2
จะเหน็วา่ Sin มกีารเรยีกตัวเองและเรยีก Cos มาทำางานสว่น Cos มกีารเรยีก sin มาทำางาน ถ้าให ้x=2θ จะได้ θ=x/2 และแทนท่ีจะได้เป็น Sin x = 2Sin(x/2)Cos (x/2) Cos x = 1-2(Sin(x/2)) 2
จากสมการดังกล่าวนำามาเขยีนเป็นฟงัก์ชนัรเีคอรซ์ฟิ Sine และ Cose ดังในตัวอยา่งโปรแกรม Mutual.c จากตารางท่ี 7.6 เน่ืองจากฟงัก์ชนั Cose กำาหนดขึ้นมาทีหลังทำาใหฟ้งัก์ชนั Sine ที่ต้องการเรยีกใช้ง า น แ ต ่ม อ ง ไ ม เ่ ห น็ จ งึ ต ้อ ง ป ร ะ ก า ศ ฟ งั ก ์ช นั Cose ล ่ว ง
หน้า (Prototype) โดยกำาหนดเฉพาะสว่นหวัของฟงัก์ชนั (ในภาษาซ ีสำาหรบัภาษาปาสคาลจะมคีำาสงวน Forward ต่อท้าย) ฟงัก์ชนัรเีคอรซ์ฟิทัง้สองต้องมสีองต้องมสีว่นที่เป็นกรณีพื้นฐานเพื่อสิน้สดุการทำางาน โดยกำาหนดค่าเป็นขอบเขตซึ่งอยูใ่นชว่ง-0.01< x < 0.01
ตารางที่ 7.6 ตัวอยา่งโปรแกรม Mutual.c
#include<stdio.h>#include<stdlib.h>#include<conio.h>#include<math.h>double code(double x);double cose(double x){ if((-0.01<x)&&(x<0.01)) return x-x*x*x/6;return 2* sine(x/2)*cose(x/2);}double cose(double x); if((-0.01<x)&&(x<0.01)) return 1.0-x*x/2;return 1-2*sine(x/2)*sine(x/2);}main(){ double x; printf("- - - - - - - - - - - - - - - - \n"); for(x=0.0; x<1.0; x+=0.1) printf("%f\t%f\n",sine(x),sin(x)); printf("- - - - - - - - - - - - - - - - \n"); for(x=0.0; x<1.0; x+=0.1) printf("%f\t%f\n",cose(x),cose(x));getch();}
ตัวอยา่งโปรแกรมในตารางท ี่ 7.6 น ี้จ ะแสดงตารางค ่าทางตรโีกณมติิของ Sine และ Cose โดยใชฟ้งัก์ชนัรเีคอรซ์ฟิที่เขยีนขึ้นมา และมกีารแสดงค่าที่ได้โดยใชฟ้งัก์ชนั Sin และ Cos ของภาษาซทีี่ใหม้า ซึ่งผลลัพธข์องทัง้สองค่าเท่ากันการสบัเปลี่ยนลำาดับ วธิกีารอยา่งหนึ่งที่น ำามาใชง้าน คือ การเรยีงลำาดับค่าหรอืขอ้มูล (Sorting) ซึ่งเรยีงจากค่าน้อยที่สดุไปยงัค่ามากที่สดุตามลำาดับ
หรอืจากค่ามากที่สดุไปยงัค่าน้อยที่สดุ แต่ในบางครัง้อาจต้องการจดัลำาดับค่าเหล่านี้ตามตำาแหน่งที่ไมซ่ำ้าแบบเดิมเรยีกวา่การสบัเปลี่ยนลำาดับ (Permutation) เชน่ มสีมาชกิ A, B, C เม ื่อสบัเปลี่ยนลำาด ับได้เป ็น ABC, ACB, BAC, BCA, CBA, CAB และจำานวนรูปแบบของการสบัเปลี่ยนลำาดับที่ไมซ่ำ้ากันเลยเท่ากับ 6 ถ้ามสีมาชกิ n ตัวที่แตกต่างกัน ดังนัน้ การเลือกสมาชกิมาจดัลำาดับในคร ัง้แรกจงึมโีอกาสเลือกได้ n ตัว คร ัง้ที่สองมโีอกาสเลือกได้ n-1 ตัว ไปเรื่อย ๆ จนถึงครัง้สดุท้ายจะเหลือเพยีงตัวเดียว จำานวนรูปแบบที่เป็นไปได้ของการสบัเปลี่ยนลำาดับได้เป็น n! (n Factorial) ซึ่งจะมากขึ้นอยา่งรวดเรว็ เชน่ มสีมาชกิ 1 ตัวจะม ี1 แบบ ถ้า 3 ตัวจะม ี6 แบบ ถ้า 5 ตัวจะมถีึง 120 แบบ ปัญหาการสบัเปลี่ยนลำาดับไมไ่ด้ขึ้นกับจำานวนรูปแบบที่มากขึ้นอยา่งรวดเรว็ แต่เป็นเร ื่องแบบของการสบัเปลี่ยนลำาดับโดยการทำางานเป็นแบบวนลปูซอ้นกัน และใชก้ับสมาชกิ n=3ตารางท่ี 7.7 ฟงัก์ชนั Permute3void permute3 (){ int i, j, k;
for (i=0;i<3;i++) for(j=0;j<3;j++){ if(j= = i) continue; for(k=0;k<3;k++){ if(k= =i | | k= =j) continue; printf(“%d %d %d\n”, i, j, k); } }}
ฟงัก์ชนันี้มกีารวนลปูซอ้นกัน 3 ลปู โดยตัวแปร k จะสรา้งได้ 18 แบบจากทัง้หมด 27 แบบที่ค่าของตัวแปร k กับ j ไมเ่ท่ากัน และเหลือเพยีง 6 แบบที่ค่าของตัวแปรทัง้สามไมเ่ท่ากันฟงัก์ชนันี้ง่ายในการเขยีนขึ้นมาก แต่ถ้าต้องการสบัเปลี่ยนลำาดับสำาหรบัสมาชกิ 4 ตัวก็ต้องเขยีนฟงัก์ชนัขึ้นมาใหม ่ดังนัน้ทำาอยา่งไรที่จะใหฟ้งัก์ชนัสามารถสบัเปลี่ยนลำาดับสำาหรบัสมาชกิกี่ตัวก็ได้ เมื่อนำาฟงัก์ชนัรเีคอรซ์ฟิมาใชท้ำางานแทนการวนลปูมคีวามเป็นไปได้ที่เหมาะสมกวา่ ถ้าให ้p(n) เป็นจำานวนการสบัเปลี่ยนลำาดับของสมาชกิ n ตัว โดยที่ p(1)=1 และ p(2)=2 และได ้ p(n) =n*p(n-1) ทำาใหท้ราบวา่ p(n)=n! สำาหรบั n>0 ซึ่งเป็นฟงัก์ชนัแฟคทอเรยีลมีการทำางานเป็นแบบรเีคอรซ์ฟิ ดังนัน้ ก็จะได้ฟงัก์ชนัการสบัเปลี่ยนลำาดับที่เป ็นแบบรเีคอรซ์ฟิดังในตารางที่ 7.8 คือ ฟงัก์ชนั Permute ที่ใชใ้นตัวอยา่งโปรแกรม Permute.c ตารางท่ี 7.8 ตัวอยา่งโปรแกรม Permute.c
#include<stdio.h>#include<stdlib.h>#include<conio.h>void print (char *s, int 1){ int i; for( i = 0; i <= 1; i++ ) printf("%c",s[i]); printf("\n");}void swap ( char *s, int i, int j ){ char tmp = s[i]; s[i] = s[j]; s[j] = tmp;}void permute( char *s, int k, int m ){ int i; if( k = = m) print( s,m ); else for( i = k; i <= m; i++ ){ swap( s,k,i); permute( s,k+1,m ); swap ( s,k,i ); }}main (){
char str[] = {"01234"}; printf("- - - - - -\n"); permute( str,0,2 ); getch();}
จากตัวอยา่งโปรแกรมเป็นการสบัเปลี่ยนลำาดับที่มสีมาชกิ 3 ตัว คือ
0, 1, 2 โดยเรยีก Permute (str, 0, 2) ขึ้นมาทำางาน หากต้องการใช ้4 ตัว คือ 0, 1, 2, 3 ก็จะเรยีก Permute (str, 0, 3) มาทำางานแทนได้เลย นอกจากฟงัก์ชนัรเีคอรซ์ฟิจะนำามาใชแ้ทนการทำางานแบบวนลปูท่ีมคีวามเหมาะสมกวา่แล้ว ในบางครัง้การแก้ปัญหาบางอยา่งไมส่ามารถใชก้ารทำางานแบบวนลปูไ ด ้ จ ะ ใ ช ไ้ ด ้เ ฉ พ า ะ ฟ งั ก ์ช นั ร เี ค อ ร ซ์ ฟิ เ ช น่ ฟ งั ก ์ช นั แ อ ค เ ค อ ร ์แมน (Ackerman’s Function) ซึ่งมกีฎเกณฑ์ ดังน้ี a (m, n) = n+1 if m = 0 a (m, n) = a (m-1, n) if m ≠ 0, n = 0 a (m, n) = a (m-1, a (m, n-1)) if m ≠ 0, n ≠ 0 ขัน้ตอนการทำางานแบบรเีคอรซ์ฟิ การทำางานในคอมพวิเตอรจ์ะคอยดเูสน้ทางการทำางานของโปรแกรมหรอืโปรแกรมยอ่ยที่ถกูเรยีกข ึ้นมาท ำางาน ด ังน ัน้ ก ่อนท ี่โปรแกรมหรอืโปรแกรมยอ่ยจะเริม่ทำางานจะมกีารบนัทึกเก็บขอ้มูลขา่วสาร
เด ิมที่ก ำาล ังใชง้านอยูใ่นขณะนัน้เพ ื่อจะน ำากลับมาใชต้ ่อไปหลังจากที่โปรแกรมหรอืโปรแกรมยอ่ยทำางานจบสิน้ลง โดยคอมพวิเตอรม์กีารจดัสรรพื้นที่หน่วยความจำา สว่นหนึ่งเก็บขอ้มูลขา่วสารเหล่านี้ และเรยีกวา่ แอคติเวชัน่เรคคอรด์ (Activation Record) เมื่อมโีปรแกรมยอ่ยหนึ่งถกูขดัจงัหวะ (Interrupt) จากโปรแกรมยอ่ยอื่นก็จะมกีารเก็บค่าของตัวแปรพารามเิตอร ์แอดเดรสถอยกลับ (Return Address) และค่าอ่ืนๆ ของโปรแกรมยอ่ยที่ถกูขดัจงัหวะไวใ้นแอคติเวชัน่เรคคอรด์ หลังจากท่ีโปรแกรมยอ่ยอ่ืนทำางานเสรจ็ก็จะนำาค่าต่างๆ ก่อนถกูขดัจงัหวะท่ีอยูใ่นแอคติเวชัน่เรคคอรด์กลับมาใชต่้อไป ถ้าใหโ้ปรแกรม A มกีารเรยีกฟงัก์ชนั B ขึ้นมาทำางาน ซ ึ่งฟงัก์ชนั B ก็มกีารเรยีกฟงัก์ชนั C ขึ้นมาทำางานต่อดังในรูปที่ 7.10 ขณะท่ีโปรแกรม A เรยีกฟงัก ์ชนั B (หมายเลข 1) ก็จะสรา้งแอคติเวช ัน่เรคคอรด์เก็บขอ้มูลขา่วสารของโปรแกรม A และฟงัชนั B จงึเร ิม่ทำางาน เม ื่อฟงัก์ชนั B เรยีกฟงัก์ชนั C (หมายเลข 2) ก็จะสรา้งแอคติเวช ัน่เรคคอรด์เก็บขอ้มูลขา่วสารของฟงัก์ชนั B และฟงัก์ชนั C เริม่ทำางาน หลังจากฟงัก์ชนั C ทำางานจบลงก็จะถอยกลับไปยงัฟงัก์ชนั B และนำาค่าที่เก็บไวใ้นแอคติเวชัน่เรคคอรด์มาใชง้าน เชน่กันเมื่อฟงัก์ชนั B ทำางานจบลงก็จะถอยกลับไปยงัโปรแกรม A และจะนำาค่าที่เก็บไวใ้นแอคติเวชัน่เรคคอรด์มาใชง้าน
เพิม่รูปรูปท่ี 7.10 ลำาดับการเรยีงฟงัก์ชนั B และ C มาทำางานของโปรแกรม A
จะเหน็วา่โปรแกรมยอ่ยที่ถกูขดัจงัหวะคร ัง้สดุท้ายจะเป็นโปรแกรมยอ่ยแรกที่กลับมาทำางานก่อนในลักษณะของสแตก ดังนัน้ จงึนำาสแตกดำาเนินการ (Execution Slack) มาใชเ้ก็บแอดเดรสของแอคติเวชั ่นเรคคอรค์ที่สรา้งขึ้นมาและมกีารดึงค่ามาใชง้่านเป็นแบบเขา้ทีหลังออกก่อน ( Last-In-First-Out) ในความเป็นจรงิการถอยกลับไมไ่ด้กลับไปยงัโปรแกรมยอ่ยที่ถกูขดัจงัหวะโดยตรง แต่จะไปที่สแตกดำาเนินการทำาการ
ดึงแอคติเวชัน่เรคคอรด์ออกมาและนำาแอดเดรสถอยกลับที่เก็บไวอ้้างกลับไปยงัโปรแกรมยอ่ยที่ตำาแหน่งเดิมอีกทีหน่ึง จากตัวอยา่งฟงัก์ชนัรเีคอรซ์ฟิ Power ในตอนต้นของบทนี ้และสว่นการทำางานของโปรแกรม Main ท่ีเรยีกฟงัก์ชนั Power มาทำางานดังในตารางที่ 7.9 โดยมี M และ P เป็นตำาแหน่งแอดเดรสถอยกลับหลังจากเรยีกฟงัก์ชนัอ่ืนมาทำางานตารางท่ี 7.9 ฟงัก์ชนั Power และฟงัก์ชนั Maindouble power (double x, int n){ if(n = = 0) return 1; else return x * power(x, n-1); (P)}main (){ ...; ...; ...; y = power (2.0,3); (M) ...; ...; ...;}
เพิม่รูปรูปท่ี 7.11 การเก็บขอ้มูลขา่วสารในแอคติเวชัน่เรคคอรด์
เม ื่อโปรแกรม Main เร ิม่ต ้นท ำางานจะมกีารสรา้งแอคติเวช ัน่เรคคอรด์ขึ้นมาในระหวา่งการทำางานอยูด่ังในรูปที่ 7.11 เพื่อเก็บขอ้มูลขา่วสารของโปรแกรม ในที่นี้แสดงเฉพาะค่าของพารามเิตอรแ์ละแอดเดรสถอยกลับ เมื่อโปรแกรม Main ถกูขดัจงัหวะในระหวา่งทำางานเนื่องจากมกีารเรยีกฟงัชนั Power พรอ้มกับสง่ค่าพารามเิตอรใ์ห ้คือ 2.0 และ 3 ค่าพารามเิตอรแ์ละแอดเดรสถอยกลับ M รวมกับค่าอื่น ๆ จะถกูนำาไปเก็บไวท้ ี่แอคติเวช ัน่เรคคอรด์ที่สรา้งขึ้นมาและเก็บลงในสแตกดังในรูปที ่7.12 (a) จากนัน้ฟงัก์ชนั Power ก็เร ิม่ต้นทำางานในคร ัง้แรกพรอ้มกับสรา้งแอคติเวชัน่เรคคอรด์ขึ้นมา เมื่อฟงัก์ชนัทำางานถึงคำาสัง่ Return x * Power(x, n-1); ก็จะถกูขดัจงัหวะ ค่าพารามเิตอร ์2.0, 2 และแอดเดรสถอยกลับ P1 ถกูนำาไปเก็บไวท้ี่แอคติเวชัน่เรอคอรด์และเก็บลงในสแตกดังในรูปท่ี 7.12 (b)
เพิม่รูปรูปท่ี 7.12 ลำาดับการเรยีกฟงัก์ชนัรเีคอรซ์ฟิ Power กับการเก็บแอคติ
เวชัน่เรคคอรด์ลงในสแตก ฟงัก์ชนั Power เป็นแบบรเีคอรซ์ฟิมกีารเรยีกตัวเองขึ้นมาทำางานครัง้ที่สองและสรา้งแอคติเวชัน่เรคคอรด์ขึ้นมา เมื่อถกูขดัจงัหวะค่าพารามเิตอร ์2.0, 1 และแอดเดรสถอยกลับ P2 เก็บไวท้ ี่แอคติเวช ัน่เรคคอรด์และเก็บลงในสแตกดังในรูปที่ 7.12 (c) เมื่อเรยีกมาทำางานครัง้ท ี่สามและถกูขดัจงัหวะค ่าพารามเิตอร ์2.0, 0 และแอดเดรสถอยกลับ P3 เก็บไวท้ี่แอคติเวช ัน่เรคคอรด์และเก็บลงในสแตกดังในรูปที ่7.12 (d) การทำางานของฟงัก์ชนั Power ที่มคี่าพารามเิตอร ์2.0, 0 จะทำาในสว่นกรณีพื้นฐานจงึไมม่กีารขดัจงัหวะ เมื่อฟงัก์ชนั Power ตัวสดุท้ายจบการทำางานก็ดึงแอคติเวชั ่นเรคคอรด์ออกจากสแตกและใชแ้อดเดรสถอยกลับอ้างไปยงัฟงัก์ชนั Power ก่อนหน้านี้เพื่อกลับคืนค่าต่างๆให ้เชน่กันเมื่อมกีารทำางานจบก็ดึงแอคติเวชัน่เรคคอรด์ออกจากสแตกและใชแ้อดเดรสถอยกลับอ้างไปยงั
ฟงัก์ชนั Power ในลำาดับถัดไปก่อนหน้านี้ การยอ้ยถอยกลับตามทางเดิมจะต่อเนื่องไปเร ื่อย ๆ จนกลับมาที่โปรแกรม Main แอคติเวชัน่เรคคอรด์ทัง้หมดก็จะถกูดึงออกจากสแตก