คุณจะล้างแจกันที่มีดอกไม้ห้าดอกได้อย่างไร
คำตอบ: ถ้าแจกันไม่ว่างคุณจะเอาดอกไม้หนึ่งดอกออกแล้วคุณจะทำแจกันที่มีดอกไม้สี่ดอกให้ว่าง
คุณจะล้างแจกันที่มีดอกไม้สี่ดอกได้อย่างไร
คำตอบ: ถ้าแจกันไม่ว่างคุณจะเอาดอกไม้หนึ่งดอกออกแล้วคุณจะล้างแจกันที่มีดอกไม้สามดอก
คุณจะล้างแจกันที่มีดอกไม้สามดอกได้อย่างไร
คำตอบ: ถ้าแจกันไม่ว่างคุณจะเอาดอกไม้หนึ่งดอกออกแล้วคุณจะล้างแจกันที่มีดอกไม้สองดอก
คุณจะล้างแจกันที่มีดอกไม้สองดอกได้อย่างไร
คำตอบ: ถ้าแจกันไม่ว่างคุณจะเอาดอกไม้หนึ่งดอกออกแล้วคุณจะทำแจกันที่มีดอกไม้หนึ่งดอกให้ว่าง
คุณจะล้างแจกันที่มีดอกไม้หนึ่งดอกได้อย่างไร
คำตอบ: ถ้าแจกันไม่ว่างคุณจะเอาดอกไม้หนึ่งดอกออกแล้วจากนั้นก็ให้คุณล้างแจกันที่ไม่มีดอกไม้
คุณจะล้างแจกันที่ไม่มีดอกไม้ได้อย่างไร
คำตอบ: ถ้าแจกันไม่ว่างคุณจะเอาดอกไม้หนึ่งดอกออกมา แต่แจกันนั้นว่างแล้วคุณก็ทำเสร็จแล้ว
มันซ้ำ ๆ มาพูดคุยกัน:
คุณจะล้างแจกันที่มีNดอกไม้ได้อย่างไร
คำตอบ: ถ้าแจกันไม่ว่างคุณจะเอาดอกไม้หนึ่งดอกออกแล้วจากนั้นคุณก็ล้างแจกันที่มีดอกไม้N-1
อืมเราจะเห็นว่าในรหัส?
void emptyVase( int flowersInVase ) {
if( flowersInVase > 0 ) {
// take one flower and
emptyVase( flowersInVase - 1 ) ;
} else {
// the vase is empty, nothing to do
}
}
อืมเราไม่ได้ทำแบบนั้นในการวนซ้ำหรือไม่?
ทำไมใช่การสอบถามซ้ำสามารถถูกแทนที่ด้วยการวนซ้ำ แต่บ่อยครั้งที่การเรียกซ้ำเป็นสง่ามากขึ้น
มาพูดถึงต้นไม้กันเถอะ ในวิทยาการคอมพิวเตอร์ต้นไม้เป็นโครงสร้างที่ประกอบด้วยโหนดซึ่งแต่ละโหนดมีลูกจำนวนหนึ่งที่เป็นโหนดหรือโมฆะ ต้นไม้ไบนารีเป็นต้นไม้ที่ทำจากโหนดที่มีว่าสองเด็กมักจะเรียกว่า "ซ้าย" และ "ขวา"; เด็กอีกครั้งสามารถเป็นโหนดหรือ null รากเป็นโหนดที่ไม่ได้เป็นลูกของโหนดอื่น ๆ ใด ๆ
ลองนึกภาพว่าโหนดนอกเหนือจากลูกมันมีค่าตัวเลขและจินตนาการว่าเราต้องการรวมค่าทั้งหมดในต้นไม้บางต้น
ในการหาค่าผลรวมในโหนดใดโหนดหนึ่งเราจะเพิ่มมูลค่าของโหนดเองให้กับค่าของลูกย่อยของมันถ้ามีและค่าของลูกที่ถูกต้องถ้ามี ตอนนี้จำได้ว่าเด็ก ๆ ถ้าพวกเขาไม่ว่างก็เป็นโหนด
ดังนั้นเมื่อรวมผลรวมของลูกซ้ายเราจะเพิ่มมูลค่าของโหนดลูกเองกับมูลค่าของลูกซ้ายของมันถ้ามีและมูลค่าของลูกที่ถูกต้องของมันถ้ามี
ดังนั้นเพื่อรวมมูลค่าของลูกซ้ายของเด็กซ้ายเราจะเพิ่มมูลค่าของโหนดลูกเองกับมูลค่าของลูกซ้ายของมันถ้ามีและมูลค่าของลูกที่เหลือของมันถ้ามี
บางทีคุณคาดว่าฉันจะไปกับสิ่งนี้และต้องการที่จะเห็นรหัสบางส่วน? ตกลง:
struct node {
node* left;
node* right;
int value;
} ;
int sumNode( node* root ) {
// if there is no tree, its sum is zero
if( root == null ) {
return 0 ;
} else { // there is a tree
return root->value + sumNode( root->left ) + sumNode( root->right ) ;
}
}
โปรดสังเกตว่าแทนที่จะทดสอบเด็ก ๆ อย่างชัดเจนเพื่อดูว่าพวกเขาเป็นโมฆะหรือโหนดเราเพียงแค่ทำให้ฟังก์ชัน recursive คืนค่าศูนย์สำหรับโหนดว่าง
สมมติว่าเรามีต้นไม้ที่มีลักษณะเช่นนี้ (ตัวเลขคือค่าเครื่องหมายสแลชชี้ไปที่เด็กและ @ หมายถึงตัวชี้ชี้เป็นโมฆะ):
5
/ \
4 3
/\ /\
2 1 @ @
/\ /\
@@ @@
หากเราเรียก sumNode บนรูท (โหนดที่มีค่า 5) เราจะส่งคืน:
return root->value + sumNode( root->left ) + sumNode( root->right ) ;
return 5 + sumNode( node-with-value-4 ) + sumNode( node-with-value-3 ) ;
ลองขยายมันให้เข้าที่ ทุกที่ที่เราเห็น sumNode เราจะแทนที่ด้วยการขยายคำสั่ง return:
sumNode( node-with-value-5);
return root->value + sumNode( root->left ) + sumNode( root->right ) ;
return 5 + sumNode( node-with-value-4 ) + sumNode( node-with-value-3 ) ;
return 5 + 4 + sumNode( node-with-value-2 ) + sumNode( node-with-value-1 )
+ sumNode( node-with-value-3 ) ;
return 5 + 4
+ 2 + sumNode(null ) + sumNode( null )
+ sumNode( node-with-value-1 )
+ sumNode( node-with-value-3 ) ;
return 5 + 4
+ 2 + 0 + 0
+ sumNode( node-with-value-1 )
+ sumNode( node-with-value-3 ) ;
return 5 + 4
+ 2 + 0 + 0
+ 1 + sumNode(null ) + sumNode( null )
+ sumNode( node-with-value-3 ) ;
return 5 + 4
+ 2 + 0 + 0
+ 1 + 0 + 0
+ sumNode( node-with-value-3 ) ;
return 5 + 4
+ 2 + 0 + 0
+ 1 + 0 + 0
+ 3 + sumNode(null ) + sumNode( null ) ;
return 5 + 4
+ 2 + 0 + 0
+ 1 + 0 + 0
+ 3 + 0 + 0 ;
return 5 + 4
+ 2 + 0 + 0
+ 1 + 0 + 0
+ 3 ;
return 5 + 4
+ 2 + 0 + 0
+ 1
+ 3 ;
return 5 + 4
+ 2
+ 1
+ 3 ;
return 5 + 4
+ 3
+ 3 ;
return 5 + 7
+ 3 ;
return 5 + 10 ;
return 15 ;
ทีนี้มาดูกันว่าเราเอาชนะโครงสร้างของความลึกโดยพลการและ "branchiness" โดยพิจารณาว่าเป็นการประยุกต์ใช้ซ้ำของเทมเพลตคอมโพสิตหรือไม่ แต่ละครั้งผ่านฟังก์ชั่น sumNode ของเราเราจัดการเพียงโหนดเดียวโดยใช้ if / then branch และคำสั่งการคืนง่าย ๆ สองคำที่เกือบจะเขียนพวกมันโดยตรงจากสเปคของเรา?
How to sum a node:
If a node is null
its sum is zero
otherwise
its sum is its value
plus the sum of its left child node
plus the sum of its right child node
นั่นคือพลังของการเรียกซ้ำ
แจกันตัวอย่างข้างต้นเป็นตัวอย่างของการเรียกซ้ำหาง การเรียกซ้ำหางทั้งหมดนั้นหมายความว่าในฟังก์ชั่นวนซ้ำหากเราเรียกซ้ำ (นั่นคือถ้าเราเรียกฟังก์ชันอีกครั้ง) นั่นคือสิ่งสุดท้ายที่เราทำ
ตัวอย่างต้นไม้ไม่ใช่หางที่ซ้ำซากเพราะแม้ว่าสิ่งสุดท้ายที่เราทำคือการเรียกคืนเด็กที่ถูกต้องก่อนที่เราจะทำสิ่งนั้น
ในความเป็นจริงลำดับที่เราเรียกว่า children และเพิ่มค่าของ node ปัจจุบันไม่สำคัญเลยเพราะการเพิ่มนั้นเป็น commutative
ตอนนี้เรามาดูการดำเนินการที่คำสั่งซื้อมีความสำคัญ เราจะใช้ต้นไม้ไบนารีของโหนด แต่คราวนี้ค่าที่เก็บไว้จะเป็นตัวอักษรไม่ใช่ตัวเลข
ต้นไม้ของเราจะมีคุณสมบัติพิเศษที่สำหรับโหนดใด ๆ ตัวละครของมันจะตามมา(ตามลำดับตัวอักษร) ตัวละครที่ถือโดยลูกซ้ายของมันและก่อน (ตามลำดับตัวอักษร) ตัวละครที่ถือโดยเด็กที่เหมาะสมของมัน
สิ่งที่เราต้องการทำคือพิมพ์ต้นไม้ตามลำดับตัวอักษร นั่นเป็นเรื่องง่ายที่จะทำให้ต้นไม้มีคุณสมบัติพิเศษ เราเพิ่งพิมพ์ลูกซ้ายแล้วตัวละครของโหนดแล้วลูกขวา
เราไม่เพียงแค่ต้องการพิมพ์วิลลี่เท่านั้นดังนั้นเราจะผ่านฟังก์ชั่นของเราเพื่อพิมพ์ นี่จะเป็นวัตถุที่มีฟังก์ชั่นการพิมพ์ (char) เราไม่จำเป็นต้องกังวลว่ามันจะทำงานได้อย่างไรเมื่อเรียกว่าการพิมพ์มันจะพิมพ์บางสิ่งบางอย่าง
เรามาดูกันว่าในรหัส:
struct node {
node* left;
node* right;
char value;
} ;
// don't worry about this code
class Printer {
private ostream& out;
Printer( ostream& o ) :out(o) {}
void print( char c ) { out << c; }
}
// worry about this code
int printNode( node* root, Printer& printer ) {
// if there is no tree, do nothing
if( root == null ) {
return ;
} else { // there is a tree
printNode( root->left, printer );
printer.print( value );
printNode( root->right, printer );
}
Printer printer( std::cout ) ;
node* root = makeTree() ; // this function returns a tree, somehow
printNode( root, printer );
นอกเหนือจากคำสั่งของการดำเนินงานที่มีความสำคัญตัวอย่างนี้แสดงให้เห็นว่าเราสามารถส่งสิ่งต่าง ๆ ไปยังฟังก์ชันแบบเรียกซ้ำ สิ่งเดียวที่เราต้องทำคือตรวจสอบให้แน่ใจว่าในการโทรซ้ำแต่ละครั้งเราจะส่งต่อไปเรื่อย ๆ เราส่งผ่านตัวชี้โหนดและเครื่องพิมพ์ไปยังฟังก์ชันและในการเรียกซ้ำแต่ละครั้งเราจะส่งต่อ "ลง"
ทีนี้ถ้าต้นไม้ของเราเป็นแบบนี้:
k
/ \
h n
/\ /\
a j @ @
/\ /\
@@ i@
/\
@@
เราจะพิมพ์อะไร
From k, we go left to
h, where we go left to
a, where we go left to
null, where we do nothing and so
we return to a, where we print 'a' and then go right to
null, where we do nothing and so
we return to a and are done, so
we return to h, where we print 'h' and then go right to
j, where we go left to
i, where we go left to
null, where we do nothing and so
we return to i, where we print 'i' and then go right to
null, where we do nothing and so
we return to i and are done, so
we return to j, where we print 'j' and then go right to
null, where we do nothing and so
we return to j and are done, so
we return to h and are done, so
we return to k, where we print 'k' and then go right to
n where we go left to
null, where we do nothing and so
we return to n, where we print 'n' and then go right to
null, where we do nothing and so
we return to n and are done, so
we return to k and are done, so we return to the caller
ดังนั้นถ้าเราดูที่บรรทัดที่เราพิมพ์:
we return to a, where we print 'a' and then go right to
we return to h, where we print 'h' and then go right to
we return to i, where we print 'i' and then go right to
we return to j, where we print 'j' and then go right to
we return to k, where we print 'k' and then go right to
we return to n, where we print 'n' and then go right to
เราเห็นว่าเราพิมพ์ "ahijkn" ซึ่งแน่นอนตามลำดับตัวอักษร
เราจัดการเพื่อพิมพ์ต้นไม้ทั้งหมดตามลำดับตัวอักษรเพียงแค่รู้วิธีการพิมพ์โหนดเดียวตามลำดับตัวอักษร ซึ่งเป็นเพียง (เพราะต้นไม้ของเรามีคุณสมบัติพิเศษของการสั่งซื้อค่าทางด้านซ้ายของค่าตัวอักษรในภายหลัง) รู้ที่จะพิมพ์ลูกซ้ายก่อนที่จะพิมพ์ค่าของโหนดและการพิมพ์เด็กที่ถูกต้องหลังจากพิมพ์ค่าของโหนด
และนั่นคือพลังของการเรียกซ้ำ: ความสามารถในการทำสิ่งต่าง ๆ ทั้งหมดโดยการรู้เพียงวิธีการทำส่วนหนึ่งของทั้งหมด (และรู้ว่าเมื่อใดที่จะหยุดการเรียกซ้ำ)
นึกถึงว่าในภาษาส่วนใหญ่ผู้ให้บริการ || ("หรือ") ลัดวงจรเมื่อตัวถูกดำเนินการแรกเป็นจริงฟังก์ชันเรียกซ้ำทั่วไปคือ:
void recurse() { doWeStop() || recurse(); }
ความคิดเห็น Luc M:
ดังนั้นควรสร้างตราสำหรับคำตอบประเภทนี้ ขอแสดงความยินดี!
ขอบคุณลัค! แต่ที่จริงแล้วเพราะฉันแก้ไขคำตอบนี้มากกว่าสี่ครั้ง (เพื่อเพิ่มตัวอย่างสุดท้าย แต่ส่วนใหญ่เพื่อแก้ไขข้อผิดพลาดและขัดมัน - การพิมพ์บนแป้นพิมพ์เน็ตบุ๊กขนาดเล็กยาก) ฉันไม่สามารถรับคะแนนเพิ่มเติมได้อีก . ซึ่งค่อนข้างกีดกันฉันจากการใช้ความพยายามมากในคำตอบในอนาคต
ดูความคิดเห็นของฉันที่นี่: /programming/128434/what-are-community-wiki-posts-in-stackoverflow/718699#718699