C రికార్షన్ (Recursion) & కాల్ స్టాక్ మెకానిక్స్
రష్యా దేశపు ప్రసిద్ధ మాట్రియోష్కా బొమ్మలను (Russian Matryoshka Nesting Dolls) మీరు ఎప్పుడైనా చూశారా? ఒక అందమైన పెద్ద చెక్క బొమ్మ ఉంటుంది. దాని మూత తీయగానే లోపల సరిగ్గా అలాంటిదే ఇంకొక చిన్న బొమ్మ ఉంటుంది! దానిని తెరిస్తే లోపల మరొక చిన్న బొమ్మ... ఇలా తెరుచుకుంటూ వెళ్తే చిట్టచివరగా ఒక ఘనమైన, తెరవడానికి వీల్లేని అతిచిన్న బొమ్మ (Smallest Solid Doll) కనిపిస్తుంది. అక్కడితో బొమ్మలను తెరవడం ఆగిపోతుంది. ఆ తర్వాత మళ్లీ అన్నింటినీ ఒకదానిలో ఒకటి పెట్టి మూసివేస్తారు.
కంప్యూటర్ సైన్స్ చరిత్రలోనే అత్యంత మంత్రముగ్ధులను చేసే అల్గారిథమిక్ భావన రికార్షన్ (Recursion) సరిగ్గా ఇలాంటిదే! ఒక పెద్ద సమస్యను పరిష్కరించడానికి, ఒక ఫంక్షన్ తన లాంటిదే కొద్దిగా చిన్నదైన సమస్యతో తనను తాను పదేపదే పిలుచుకోవడాన్ని (A function calling itself) రికార్షన్ అంటారు. చివరగా వచ్చిన అతిచిన్న సమస్యను బేస్ కండిషన్ (Base Case) అంటారు. బేస్ కండిషన్ తగలగానే పిలుపులు ఆగిపోయి, ఫలితాలు వెనక్కి రావడం మొదలవుతాయి!
ఈ పన్నెండవ అధ్యాయంతో మనం Phase 03 (ఫంక్షన్స్, స్కోప్ & రికర్షన్) ను పూర్తి చేయబోతున్నాం. ఇందులో రికార్షన్ యొక్క అంతర్గత RAM కాల్ స్టాక్ వైండింగ్ మరియు అన్వైండింగ్ (Stack Winding & Unwinding) ప్రక్రియ, ఫ్యాక్టోరియల్, ఫిబోనాకి కాల్ ట్రీలు, ప్రాణాంతక స్టాక్ ఓవర్ఫ్లో (Stack Overflow) నివారణ, మరియు ప్రసిద్ధ టవర్స్ ఆఫ్ హనోయి (Towers of Hanoi) అల్గారిథమ్ను సమగ్రంగా అన్వేషిద్దాం!
డేటా స్ట్రక్చర్స్, ట్రీస్, గ్రాఫ్స్ మరియు కాంప్లెక్స్ అల్గారిథమ్స్ నేర్చుకోవడానికి రికార్షన్ అనేది గేట్వే వంటిది. ఈ అధ్యాయంలో కవర్ చేయబడే లోతైన భావనలు:
- రికార్షన్ ప్రాథమిక నిర్వచనం: విభజించు మరియు పాలించు (Divide and Conquer) ఫిలాసఫీ.
- రికార్షన్ యొక్క 2 పవిత్ర నియమాలు: బేస్ కండిషన్ (Base Condition) మరియు రికర్సివ్ స్టెప్ (Recursive Step).
- కాల్ స్టాక్ వైండింగ్ & అన్వైండింగ్: RAM లో స్టాక్ ఫ్రేమ్లు ఎలా పేరుకుపోతాయి? ఫలితాలు ఎలా గుణించబడతాయి?
- ఫ్యాక్టోరియల్ అల్గారిథమ్ పోస్ట్మార్టం: $N! = N imes (N-1)!$ గణితాన్ని C కోడ్గా మార్చే విధానం.
- ఫిబోనాకి సంఖ్యలు & రికర్సివ్ కాల్ ట్రీ: బైనరీ రికార్షన్, డూప్లికేట్ సబ్ప్రాబ్లమ్స్ మరియు $O(2^N)$ టైమ్ కాంప్లెక్సిటీ.
- సమగ్ర తులనాత్మక పట్టిక: రికార్షన్ (Recursion) vs ఇటరేషన్ / లూప్స్ (Iteration / Loops).
- ప్రాణాంతక స్టాక్ ఓవర్ఫ్లో (Stack Overflow Exception): 8 MB స్టాక్ లిమిట్ దాటి ప్రోగ్రామ్ ఎందుకు క్రాష్ అవుతుంది?
- డైరెక్ట్ రికార్షన్ vs ఇన్డైరెక్ట్ రికార్షన్: మ్యూచువల్ రికార్షన్ (Mutual Recursive Function Calls).
- టెయిల్ రికార్షన్ (Tail Recursion) & TCO ఆప్టిమైజేషన్: కంపైలర్ రికార్షన్ను లూప్గా మార్చే ఆధునిక టెక్నిక్.
- ది టవర్స్ ఆఫ్ హనోయి (Towers of Hanoi Puzzle): $2^N - 1$ మూవ్స్తో రింగ్స్ మార్చే క్లాసిక్ ప్రోగ్రామ్.
- సమగ్ర రియల్ వరల్డ్ ప్రాజెక్ట్: నెస్టెడ్ ఫైల్ సిస్టమ్ డైరెక్టరీ సైజ్ కాలిక్యులేటర్ సిమ్యులేషన్.
- బిగినర్స్ చేసే టాప్ 5 రికార్షన్ తప్పులు: మిస్సింగ్ బేస్ కండిషన్, $n++$ వర్సెస్ $n-1$ ట్రాప్స్.
- స్వీయ మూల్యాంకన క్విజ్: 5 లోతైన విశ్లేషణాత్మక ప్రశ్నలు & ఎగ్జామ్ కాన్సెప్ట్స్.
నిజ జీవితంలో ఒక పెద్ద కష్టమైన సమస్య ఎదురైనప్పుడు, దానిని ఒకేసారి పరిష్కరించడం కష్టం. కానీ ఆ సమస్యను కొద్దిగా చిన్నదైన ఉప-సమస్యగా (Sub-problem) విభజించగలిగితే — అదే లాజిక్ను పదేపదే వర్తింపజేసి చివరకు అతిచిన్న సులభమైన స్థితికి చేరుకోవచ్చు. దీనినే విభజించు మరియు జయించు (Divide and Conquer) అంటారు.
రికార్షన్ ఎలా పనిచేస్తుంది?
ఒక సాధారణ ఫంక్షన్ వేరొక ఫంక్షన్ను పిలుస్తుంది. కానీ ఒక రికర్సివ్ ఫంక్షన్ తనను తానే మళ్లీ పిలుచుకుంటుంది! అయితే ప్రతి పిలుపులోనూ ఇన్పుట్ విలువ చిన్నదిగా మారుతూ బేస్ కండిషన్ వైపుకు అడుగులు వేయాలి. అలా జరగకపోతే అది అంతులేని ఇన్ఫినిట్ రికార్షన్ (Infinite Recursion) గా మారి కంప్యూటర్ మెమరీని నింపేస్తుంది!
C లో మీరు ఏ రికర్సివ్ ఫంక్షన్ రాసినా, అందులో కచ్చితంగా ఈ రెండు భాగాలు ఉండాల్సిందే:
1. బేస్ కండిషన్ (Base Condition)
రికార్షన్ ఎక్కడ ఆగిపోవాలో నిర్దేశించే అత్యవసర బ్రేక్! ఇది తగలగానే ఫంక్షన్ తనను తాను పిలవడం ఆపేసి, ఒక ప్రత్యక్ష విలువను రిటర్న్ చేస్తుంది. ఇది లేకపోతే స్టాక్ ఓవర్ఫ్లో తప్పదు!
2. రికర్సివ్ స్టెప్ (Recursive Step)
ఫంక్షన్ తనను తాను చిన్న ఇన్పుట్తో (ఉదా: n - 1) పిలుచుకునే భాగం. ఇది సమస్యను క్రమంగా బేస్ కండిషన్ వైపుకు నడిపిస్తుంది.
గణితంలో ఫ్యాక్టోరియల్ సమీకరణాన్ని పరిశీలిద్దాం:
$$N! = N imes (N-1)! quad ext{మరియు} quad 0! = 1, quad 1! = 1$$దీనిని C లో రికార్షన్ ద్వారా కోడ్ చేయడం ఎంతో సహజంగా, ఎలిగాంట్గా ఉంటుంది:
#include <stdio.h>
// రికర్సివ్ ఫ్యాక్టోరియల్ ఫంక్షన్
long long factorial(int n) {
// 1. బేస్ కండిషన్ (ఆగే నియమం)
if (n <= 1) {
printf(" -> బేస్ కండిషన్ చేరుకుంది! factorial(%d) = 1 రిటర్న్ చేస్తున్నాం.\n", n);
return 1;
}
// 2. రికర్సివ్ స్టెప్ (Winding Phase)
printf(" [PUSH] factorial(%d) లెక్కించడానికి factorial(%d) ని పిలుస్తున్నాం...\n", n, n - 1);
long long result = n * factorial(n - 1);
// 3. అన్వైండింగ్ ఫేజ్ (Unwinding Phase)
printf(" [POP] factorial(%d) పూర్తయింది = %lld\n", n, result);
return result;
}
int main() {
int num = 4;
printf("=== 4 యొక్క ఫ్యాక్టోరియల్ గణన (Call Stack Lifecycle) ===\n");
long long ans = factorial(num);
printf("తుది ఫలితం: %d! = %lld\n", num, ans);
return 0;
}
RAM కాల్ స్టాక్ యొక్క 2 దశలు (Winding vs Unwinding):
factorial(4) ➔ 4 * factorial(3)
factorial(3) ➔ 3 * factorial(2)
factorial(2) ➔ 2 * factorial(1)
factorial(1) ➔ బేస్ కండిషన్ తగిలింది! 1 రిటర్న్ చేస్తుంది.
[దశ 2: అన్వైండింగ్ - ఫలితాల గుణకారం & మెమరీ విడుదల (POP)]:
factorial(1) = 1 రిటర్న్ అయింది (Frame Popped)
factorial(2) = 2 * 1 = 2 (Frame Popped)
factorial(3) = 3 * 2 = 6 (Frame Popped)
factorial(4) = 4 * 6 = 24 (తుది సమాధానం main కు చేరింది!)
ఫిబోనాకి క్రమం (0, 1, 1, 2, 3, 5, 8, 13...) లో ప్రతి సంఖ్య దాని మునుపటి రెండు సంఖ్యల మొత్తం. దీనిని బైనరీ రికార్షన్ (Binary Tree Recursion) అంటారు, ఎందుకంటే ప్రతి ఒక్క కాల్ మరొక రెండు కాల్స్ను పుట్టిస్తుంది:
$$F(n) = F(n-1) + F(n-2) quad ext{మరియు} quad F(0) = 0, quad F(1) = 1$$#include <stdio.h>
int fibonacci(int n) {
if (n == 0) return 0; // బేస్ కండిషన్ 1
if (n == 1) return 1; // బేస్ కండిషన్ 2
return fibonacci(n - 1) + fibonacci(n - 2); // రెండు రికర్సివ్ కాల్స్!
}
int main() {
int terms = 7;
printf("మొదటి %d ఫిబోనాకి సంఖ్యలు:\n", terms);
for (int i = 0; i < terms; i++) {
printf("%d ", fibonacci(i));
}
printf("\n");
return 0;
}
fibonacci(5) లెక్కించడానికి fibonacci(3) ను రెండుసార్లు, fibonacci(2) ను మూడుసార్లు అనవసరంగా మళ్లీ మళ్లీ లెక్కిస్తుంది. $N = 50$ ఇస్తే మీ కంప్యూటర్ కొన్ని గంటల పాటు నిలిచిపోతుంది! దీనిని అధిగమించడానికే భవిష్యత్తులో మనం డైనమిక్ ప్రోగ్రామింగ్ (Memoization / DP) ఉపయోగిస్తాం!
రికార్షన్ మరియు సాధారణ లూప్స్ (Iteration) మధ్య ఉన్న సమగ్ర ఇంజనీరింగ్ వ్యత్యాసాలను ఒకే పట్టికలో పరిశీలిద్దాం:
| అంశం / ఫీచర్ | రికార్షన్ (Recursion) | ఇటరేషన్ / లూప్స్ (Iteration - for/while) |
|---|---|---|
| ప్రాథమిక మెకానిజం | ఫంక్షన్ తనను తాను పదేపదే కాల్ చేసుకుంటుంది. | స్టేట్మెంట్ల బ్లాక్ లూప్ కండిషన్ ఫాల్స్ అయ్యే వరకు పునరావృతమవుతుంది. |
| టెర్మినేషన్ నిబంధన | బేస్ కండిషన్ (Base Case) ద్వారా ముగుస్తుంది. | లూప్ కండిషన్ ఫాల్స్ (False) కావడం ద్వారా ముగుస్తుంది. |
| మెమరీ వినియోగం | ఎక్కువ: ప్రతి కాల్కి RAM కాల్ స్టాక్ పై కొత్త స్టాక్ ఫ్రేమ్ కేటాయించబడుతుంది ($O(N)$ space). | అతి తక్కువ: ఒకే స్టాక్ ఫ్రేమ్లో కౌంటర్ మాత్రమే మారుతుంది ($O(1)$ space). |
| ఎగ్జిక్యూషన్ వేగం | ఫంక్షన్ కాల్ ఓవర్హెడ్ (Push/Pop) వలన కొద్దిగా నెమ్మదిగా ఉంటుంది. | నేరుగా CPU జంప్స్ కాబట్టి అత్యంత వేగవంతమైనది. |
| కోడ్ రీడబిలిటీ & సైజు | ట్రీస్, గ్రాఫ్స్, టవర్స్ ఆఫ్ హనోయి వంటి వాటికి కోడ్ చాలా చిన్నగా, క్లీన్గా ఉంటుంది. | సంక్లిష్టమైన సమస్యలకు కోడ్ పెద్దదిగా, గజిబిజిగా మారవచ్చు. |
| ప్రమాదం | బేస్ కండిషన్ లేకపోతే Stack Overflow క్రాష్ అవుతుంది. | కండిషన్ అప్డేట్ లేకపోతే Infinite Loop (CPU 100% Hang) అవుతుంది. |
కంప్యూటర్ RAM లో ప్రతి ప్రోగ్రామ్కు కేటాయించే కాల్ స్టాక్ పరిమాణం అపరిమితమైనది కాదు. లైనక్స్ మరియు విండోస్లలో సాధారణంగా కాల్ స్టాక్ సైజు 1 MB నుండి 8 MB వరకు మాత్రమే ఉంటుంది.
స్టాక్ ఓవర్ఫ్లో ఎలా జరుగుతుంది?
ఒక రికర్సివ్ ఫంక్షన్లో బేస్ కండిషన్ రాయడం మర్చిపోయినా, లేదా బేస్ కండిషన్ ఎప్పటికీ చేరుకోని తప్పుడు లాజిక్ రాసినా — స్టాక్ ఫ్రేమ్లు ఒకదానిపై ఒకటి పేరుకుపోతూనే ఉంటాయి. స్టాక్ మెమరీ పరిమితి ముగిసిపోగానే, ఆపరేటింగ్ సిస్టమ్ ప్రోగ్రామ్ను Segmentation Fault (SIGSEGV / Stack Overflow) ఎర్రర్తో తక్షణమే చంపేస్తుంది!
// ❌ ప్రమాదకరమైన కోడ్: బేస్ కండిషన్ లేదు!
void causeCrash(int count) {
printf("కాల్ సంఖ్య: %d\n", count);
causeCrash(count + 1); // ఎప్పటికీ ఆగదు! లక్షలాది ఫ్రేమ్స్ స్టాక్ ని నింపేస్తాయి.
}
int main() {
// causeCrash(1); // ⚠️ ఇది రన్ చేస్తే సెగ్మెంటేషన్ ఫాల్ట్ తో ప్రోగ్రామ్ క్రాష్ అవుతుంది!
return 0;
}
రికార్షన్లో ప్రధానంగా రెండు రకాలు ఉంటాయి:
1. డైరెక్ట్ రికార్షన్ (Direct Recursion):
ఫంక్షన్ A() తన బాడీ లోపల నేరుగా A() నే పిలుచుకోవడం (మనం ఇప్పటివరకు చూసిన ఫ్యాక్టోరియల్ లాగా).
2. ఇన్డైరెక్ట్ రికార్షన్ (Indirect / Mutual Recursion):
ఫంక్షన్ A() ఫంక్షన్ B() ని పిలుస్తుంది, మరియు ఫంక్షన్ B() తిరిగి ఫంక్షన్ A() ని పిలుస్తుంది! ఈ చక్రీయ రిలేషన్షిప్ను మ్యూచువల్ రికార్షన్ అంటారు. ఉదాహరణకు ఒక సంఖ్య సరి సంఖ్యో (Even) బేసి సంఖ్యో (Odd) పరస్పరం నిర్ణయించడం:
#include <stdio.h>
// Forward Prototypes
int isEven(int n);
int isOdd(int n);
int isEven(int n) {
if (n == 0) return 1; // 0 అనేది సరి సంఖ్య
return isOdd(n - 1); // isOdd ని పిలుస్తుంది!
}
int isOdd(int n) {
if (n == 0) return 0; // 0 బేసి సంఖ్య కాదు
return isEven(n - 1); // isEven ని పిలుస్తుంది!
}
int main() {
int num = 7;
if (isEven(num)) {
printf("%d అనేది సరి సంఖ్య (Even Number)\n", num);
} else {
printf("%d అనేది బేసి సంఖ్య (Odd Number)\n", num);
}
return 0;
}
ఒక ఫంక్షన్లో జరిగే రికార్సివ్ కాల్ ఆ ఫంక్షన్ యొక్క చిట్టచివరి స్టేట్మెంట్ అయి ఉండి, ఆ కాల్ తర్వాత ఎటువంటి అదనపు ఆపరేషన్లు (కూడికలు, గుణకారాలు) లేకపోతే, దానిని టెయిల్ రికార్షన్ (Tail Recursion) అంటారు.
Tail Call Optimization (TCO):
ఆధునిక GCC/Clang కంపైలర్లు (-O2 లేదా -O3 ఫ్లాగ్స్ తో) టెయిల్ రికార్షన్ను గుర్తించినప్పుడు, స్టాక్ పై కొత్త స్టాక్ ఫ్రేమ్లను సృష్టించవు! పాత స్టాక్ ఫ్రేమ్నే రీయూజ్ చేసి దానిని ఒక సాధారణ లూప్లా మార్చేస్తాయి. దీనివలన రికార్షన్ వాడినప్పటికీ మెమరీ కాంప్లెక్సిటీ $O(N)$ నుండి $O(1)$ కి పడిపోయి స్టాక్ ఓవర్ఫ్లో ప్రమాదం పూర్తిగా తొలగిపోతుంది!
// 1. నాన్-టెయిల్ రికార్షన్: గుణకారం (*) చివర మిగిలి ఉంది కాబట్టి కాల్ స్టాక్ అవసరం
long long nonTailFactorial(int n) {
if (n <= 1) return 1;
return n * nonTailFactorial(n - 1); // '*' ఆపరేషన్ పెండింగ్ లో ఉంది!
}
// 2. టెయిల్ రికార్షన్: చివరి స్టేట్మెంట్ కేవలం ఫంక్షన్ కాల్ మాత్రమే (అక్యుమ్యులేటర్ ద్వారా)
long long tailFactorialHelper(int n, long long accumulator) {
if (n <= 1) return accumulator;
return tailFactorialHelper(n - 1, n * accumulator); // ప్యూర్ టెయిల్ కాల్!
}
long long tailFactorial(int n) {
return tailFactorialHelper(n, 1);
}
కంప్యూటర్ సైన్స్ చరిత్రలోనే రికార్షన్ యొక్క శక్తిని నిరూపించే అత్యంత ప్రసిద్ధ పజిల్ టవర్స్ ఆఫ్ హనోయి (Towers of Hanoi). ఇందులో మూడు స్తంభాలు (Source, Auxiliary, Destination) ఉంటాయి. Source పై వివిధ పరిమాణాల డిస్కులు (Disks) ఉంటాయి:
- ఒకేసారి కేవలం ఒక డిస్కును మాత్రమే కదపాలి.
- చిన్న డిస్కు కింద పెద్ద డిస్కును ఉంచకూడదు (పెద్దదాని పైనే చిన్నది ఉండాలి).
- $N$ డిస్కులను గమ్యస్థానానికి చేర్చడానికి కనీసం $2^N - 1$ మూవ్స్ పడతాయి.
#include <stdio.h>
// Towers of Hanoi రికర్సివ్ సొల్యూషన్
void towerOfHanoi(int n, char fromRod, char toRod, char auxRod) {
if (n == 1) {
printf("-> డిస్క్ 1 ని %c నుండి %c కి మార్చు\n", fromRod, toRod);
return;
}
// 1. (n-1) డిస్కులను Source నుండి Helper కి మార్చు
towerOfHanoi(n - 1, fromRod, auxRod, toRod);
// 2. అతిపెద్ద nవ డిస్కును Source నుండి Destination కి మార్చు
printf("-> డిస్క్ %d ని %c నుండి %c కి మార్చు\n", n, fromRod, toRod);
// 3. Helper లో ఉన్న (n-1) డిస్కులను Destination కి మార్చు
towerOfHanoi(n - 1, auxRod, toRod, fromRod);
}
int main() {
int totalDisks = 3; // 3 డిస్కులు = 2^3 - 1 = 7 మూవ్స్!
printf("=== టవర్స్ ఆఫ్ హనోయి పరిష్కారం (%d డిస్కులు) ===\n", totalDisks);
towerOfHanoi(totalDisks, 'A', 'C', 'B');
return 0;
}
నిజ జీవితంలో విండోస్ ఎక్స్ప్లోరర్ లేదా లైనక్స్ టెర్మినల్ ఒక ఫోల్డర్ సైజును ఎలా లెక్కిస్తుందో ఆలోచించారా? ఒక ఫోల్డర్ లోపల ఫైల్స్ ఉంటాయి, వాటితో పాటు సబ్-ఫోల్డర్స్ (Sub-folders) ఉంటాయి, వాటి లోపల మళ్లీ మరిన్ని సబ్-ఫోల్డర్స్ ఉంటాయి! ఫోల్డర్ల లోతు ఎంతైనా సరే, రికార్షన్ ద్వారా ప్రతి ఫోల్డర్ను స్వయంచాలకంగా స్కాన్ చేసి మొత్తం మెగాబైట్లను లెక్కించవచ్చు:
#include <stdio.h>
// డెప్త్ లెవల్ ఆధారంగా స్పేసెస్ ప్రింట్ చేసే హెల్పర్
void printIndent(int depth) {
for (int i = 0; i < depth; i++) {
printf(" ");
}
}
// రికర్సివ్ ఫోల్డర్ సైజ్ కాలిక్యులేటర్ (సిమ్యులేషన్)
double calculateFolderSize(const char* folderName, int subFolderCount, double directFileSizeMB, int depth) {
printIndent(depth);
printf("📁 ఫోల్డర్ స్కాన్ చేయబడుతోంది: [%s] (డైరెక్ట్ ఫైల్స్: %.1f MB)\n", folderName, directFileSizeMB);
// బేస్ కండిషన్: సబ్-ఫోల్డర్స్ ఏవీ లేకపోతే కేవలం డైరెక్ట్ సైజ్ రిటర్న్ చేయి
if (subFolderCount == 0) {
printIndent(depth);
printf(" └── ఏ సబ్-ఫోల్డర్స్ లేవు. ముగింపు.\n");
return directFileSizeMB;
}
double totalSize = directFileSizeMB;
// ప్రతి సబ్-ఫోల్డర్ కోసం రికర్సివ్గా సైజ్ లెక్కించు
for (int i = 1; i <= subFolderCount; i++) {
char childName[32];
sprintf(childName, "సబ్_ఫోల్డర్_%d", i);
// రికర్సివ్ కాల్ (లోపలి లోతుకి వెళ్తోంది)
double childSize = calculateFolderSize(childName, subFolderCount - 1, 15.0 * i, depth + 1);
totalSize += childSize;
}
printIndent(depth);
printf("✔️ [%s] మొత్తం సబ్-ట్రీ సైజు: %.1f MB\n", folderName, totalSize);
return totalSize;
}
int main() {
printf("====================================================\n");
printf(" మనా ఫైల్ సిస్టమ్ రికర్సివ్ స్టోరేజ్ స్కానర్\n");
printf("====================================================\n");
double grandTotal = calculateFolderSize("రూట్_డ్రైవ్_C:", 2, 50.0, 0);
printf("----------------------------------------------------\n");
printf("📊 మొత్తం C: డ్రైవ్ ఉపయోగించిన స్పేస్: %.2f MB\n", grandTotal);
printf("====================================================\n");
return 0;
}
ఇది స్టాక్ ఫ్రేమ్లను నింపేసి సెగ్మెంటేషన్ ఫాల్ట్ (Stack Overflow) తో ప్రోగ్రామ్ను వెంటనే క్రాష్ చేస్తుంది.
func(n--) అని రాస్తే ప్రస్తుత విలువ మాత్రమే పాస్ అవుతుంది (Post-decrement)! దీనివలన బేస్ కండిషన్ వైపు తగ్గక ఇన్ఫినిట్ లూప్ అవుతుంది. ఎల్లప్పుడూ func(n - 1) వాడాలి!
if (n == 0) మాత్రమే రాసి యూజర్ -5 ఇస్తే, అది సున్నాను ఎప్పటికీ చేరుకోక క్రాష్ అవుతుంది. అందుకే if (n <= 0) అని రాయాలి.
రికార్షన్ కాల్ ముందు
return రాయకుండా కేవలం func(n - 1); అని రాస్తే కాలర్ ఫంక్షన్కు గార్బేజ్ వాల్యూ వెళ్తుంది.
$N = 100000$ లాంటి పెద్ద సంఖ్యలకు సాధారణ రికార్షన్ రాస్తే స్టాక్ ఓవర్ఫ్లో అవుతుంది; అటువంటి వాటికి ఇటరేషన్ లేదా టెయిల్ రికార్షన్ వాడాలి.
ఈ పాఠంలోని లోతైన అంశాలను క్రింది 5 ప్రాక్టికల్ ప్రశ్నల ద్వారా పరీక్షించుకోండి:
❓ ప్రశ్న 1: రికార్షన్లో బేస్ కండిషన్ లేకపోతే ఏం జరుగుతుంది?
❓ ప్రశ్న 2: రికార్షన్ కాల్ స్టాక్ లో వైండింగ్ (Winding) మరియు అన్వైండింగ్ (Unwinding) అంటే ఏమిటి?
❓ ప్రశ్న 3: సాధారణ రికార్షన్తో పోలిస్తే టెయిల్ రికార్షన్ (Tail Recursion) ఎందుకు ఉత్తమమైనది?
❓ ప్రశ్న 4: 3 డిస్కులు ఉన్న టవర్స్ ఆఫ్ హనోయి సమస్యను పరిష్కరించడానికి మొత్తం ఎన్ని మూవ్స్ అవసరం?
❓ ప్రశ్న 5: func(n--) అని రికర్సివ్ కాల్ లో ఎందుకు రాయకూడదు?
n-- అనేది పోస్ట్-డిక్రిమెంట్. అంటే ప్రస్తుత పాత n విలువే మళ్లీ ఫంక్షన్కు వెళ్తుంది! దీనివలన విలువ తగ్గక శాశ్వతంగా ఇన్ఫినిట్ రికార్షన్ అయిపోతుంది. ఎల్లప్పుడూ func(n - 1) వాడాలి.
పైన వివరించిన ఫ్యాక్టోరియల్ కాల్ స్టాక్ ట్రేసర్ మరియు టవర్స్ ఆఫ్ హనోయి కోడ్లను మన ఆన్లైన్ C కంపైలర్లో ఓపెన్ చేసి స్వయంగా రన్ చేయండి. డిస్కుల సంఖ్యను మార్చి మూవ్స్ ఎలా జనరేట్ అవుతున్నాయో పరీక్షించండి!
📌 Phase 03 ముగింపు సారాంశం (Phase 03 Wrap-Up):
- మోడ్యులర్ ప్రోగ్రామింగ్ & మెమరీ విజయం: ఫంక్షన్స్, ప్రొటోటైప్స్ (Ch 9), Call by Value vs Reference & పాయింటర్లు (Ch 10), స్టోరేజ్ క్లాసెస్ & స్కోప్ (Ch 11), మరియు రికార్షన్ & కాల్ స్టాక్ (Ch 12) లపై పూర్తి పాండిత్యం సాధించారు!
- రికార్షన్: సంక్లిష్ట సమస్యలను తనలాంటి చిన్న సమస్యలుగా మార్చి పరిష్కరించే కళ.
- బేస్ కండిషన్: రికార్షన్ను ఆపే అత్యంత ముఖ్యమైన రక్షణ కవచం.
- స్టాక్ ఓవర్ఫ్లో: బేస్ కండిషన్ లేనప్పుడు స్టాక్ మెమరీ నిండిపోయి వచ్చే క్రాష్.