LIFO बनाम FIFO
LIFO का मतलब लास्ट इन, फर्स्ट आउट है। जैसा कि, स्टैक में रखा अंतिम आइटम स्टैक से निकाला गया पहला आइटम है।
आपने अपने व्यंजनों की उपमा ( पहले संशोधन में ) के साथ जो वर्णन किया है , वह एक कतार या फीफो, फर्स्ट इन, फर्स्ट आउट है।
दोनों के बीच प्रमुख अंतर यह है कि LIFO / स्टैक एक ही छोर से (आवेषण) और चबूतरे (हटाता है), और एक FIFO / कतार विपरीत छोरों से ऐसा करता है।
// Both:
Push(a)
-> [a]
Push(b)
-> [a, b]
Push(c)
-> [a, b, c]
// Stack // Queue
Pop() Pop()
-> [a, b] -> [b, c]
ढेर सूचक
आइए एक नज़र डालते हैं कि स्टैक के हुड के नीचे क्या हो रहा है। यहाँ कुछ मेमोरी है, प्रत्येक बॉक्स एक पता है:
...[ ][ ][ ][ ]... char* sp;
^- Stack Pointer (SP)
और वर्तमान में खाली स्टैक के तल पर एक स्टैक पॉइंटर है (चाहे स्टैक बढ़ता है या नीचे बढ़ता है यह विशेष रूप से प्रासंगिक नहीं है इसलिए हम इसे अनदेखा करेंगे, लेकिन निश्चित रूप से वास्तविक दुनिया में, यह निर्धारित करता है कि कौन सा ऑपरेशन जोड़ता है , और जो सपा से घटता है)।
तो चलो a, b, and c
फिर से धक्का । बाईं ओर ग्राफिक्स, मध्य में "उच्च स्तर" ऑपरेशन, दाईं ओर C-ish छद्म कोड:
...[a][ ][ ][ ]... Push('a') *sp = 'a';
^- SP
...[a][ ][ ][ ]... ++sp;
^- SP
...[a][b][ ][ ]... Push('b') *sp = 'b';
^- SP
...[a][b][ ][ ]... ++sp;
^- SP
...[a][b][c][ ]... Push('c') *sp = 'c';
^- SP
...[a][b][c][ ]... ++sp;
^- SP
जैसा कि आप देख सकते हैं, हर बार जब हम push
उस स्थान में तर्क डालते हैं, जो स्टैक पॉइंटर वर्तमान में इंगित कर रहा है, और अगले स्थान पर इंगित करने के लिए स्टैक पॉइंटर को समायोजित करता है।
अब चलो पॉप:
...[a][b][c][ ]... Pop() --sp;
^- SP
...[a][b][c][ ]... return *sp; // returns 'c'
^- SP
...[a][b][c][ ]... Pop() --sp;
^- SP
...[a][b][c][ ]... return *sp; // returns 'b'
^- SP
Pop
के विपरीत है push
, यह पिछले स्थान पर इंगित करने के लिए स्टैक पॉइंटर को समायोजित करता है और उस आइटम को हटाता है जो वहां था (आमतौर पर इसे जिसे वापस बुलाया जाता है pop
)।
आपने शायद गौर किया b
और c
अभी भी याद में हैं। मैं आपको केवल यह आश्वासन देना चाहता हूं कि वे टाइपोस नहीं हैं। हम जल्द ही उस पर लौटेंगे।
स्टैक पॉइंटर के बिना जीवन
आइए देखें कि क्या होता है अगर हमारे पास स्टैक पॉइंटर नहीं है। फिर से धक्का देने के साथ शुरू:
...[ ][ ][ ][ ]...
...[ ][ ][ ][ ]... Push(a) ? = 'a';
एर, हम्म ... अगर हमारे पास स्टैक पॉइंटर नहीं है, तो हम उस पते पर कुछ स्थानांतरित नहीं कर सकते हैं जो इसे इंगित करता है। शायद हम एक पॉइंटर का उपयोग कर सकते हैं जो शीर्ष के बजाय आधार को इंगित करता है।
...[ ][ ][ ][ ]... char* bp; // "base pointer"
^- bp bp = malloc(...);
...[a][ ][ ][ ]... Push(a) *bp = 'a';
^- bp
// No stack pointer, so no need to update it.
...[b][ ][ ][ ]... Push(b) *bp = 'b';
^- bp
उह ओह। चूंकि हम स्टैक के आधार के निश्चित मूल्य को बदल नहीं सकते हैं, हम केवल उसी स्थान पर a
धक्का देकर ओवरवोट करते हैं b
।
ठीक है, हम क्यों नहीं ट्रैक करते हैं कि हमने कितनी बार धक्का दिया है। और हमें उस समय का भी ट्रैक रखना होगा जो हमने पॉपप किए हैं।
...[ ][ ][ ][ ]... char* bp; // "base pointer"
^- bp bp = malloc(...);
int count = 0;
...[a][ ][ ][ ]... Push(a) bp[count] = 'a';
^- bp
...[a][ ][ ][ ]... ++count;
^- bp
...[a][b][ ][ ]... Push(a) bp[count] = 'b';
^- bp
...[a][b][ ][ ]... ++count;
^- bp
...[a][b][ ][ ]... Pop() --count;
^- bp
...[a][b][ ][ ]... return bp[count]; //returns b
^- bp
वैसे यह काम करता है, लेकिन यह वास्तव में पहले जैसा ही है, इसके अलावा (कोई अतिरिक्त अंकगणित) की *pointer
तुलना में सस्ता है pointer[offset]
, यह उल्लेख करने के लिए नहीं है कि यह टाइप करने के लिए कम है। यह मुझे एक नुकसान की तरह लगता है।
चलो फिर से कोशिश करें। सरणी-आधारित संग्रह के अंत को खोजने के पास्कल स्ट्रिंग शैली का उपयोग करने के बजाय (संग्रह में कितने आइटम हैं, इस पर नज़र रखना), आइए C स्ट्रिंग शैली (शुरुआत से अंत तक स्कैन करें) आज़माएँ:
...[ ][ ][ ][ ]... char* bp; // "base pointer"
^- bp bp = malloc(...);
...[ ][ ][ ][ ]... Push(a) char* top = bp;
^- bp, top
while(*top != 0) { ++top; }
...[ ][ ][ ][a]... *top = 'a';
^- bp ^- top
...[ ][ ][ ][ ]... Pop() char* top = bp;
^- bp, top
while(*top != 0) { ++top; }
...[ ][ ][ ][a]... --top;
^- bp ^- top return *top; // returns '('
आप पहले से ही यहाँ समस्या का अनुमान लगा सकते हैं। Uninitialized मेमोरी 0. होने की गारंटी नहीं है। इसलिए जब हम शीर्ष स्थान की तलाश करते हैं a
, तो हम अप्रयुक्त मेमोरी लोकेशन के एक समूह पर स्किप कर देते हैं, जिसमें बेतरतीब कचरा होता है। इसी तरह, जब हम शीर्ष पर स्कैन करते हैं, तो हम अंत तक अच्छी तरह से छोड़ a
दिया जाता है जब तक कि हम धक्का नहीं देते हैं जब तक कि हम अंत में एक और मेमोरी लोकेशन नहीं पाते हैं जो कि बस होता है 0
, और पीछे हटें और यादृच्छिक कचरा वापस लौटा दें।
यह तय करना काफी आसान है, हमें बस परिचालन को जोड़ना है Push
और Pop
यह सुनिश्चित करने के लिए कि स्टैक के शीर्ष को हमेशा एक के साथ चिह्नित करने के लिए अपडेट किया जाता है 0
, और हमें ऐसे टर्मिनेटर के साथ स्टैक को इनिशियलाइज़ करना होगा। बेशक इसका मतलब यह भी है कि 0
स्टैक में वास्तव में मूल्य के रूप में हमारे पास (या जो भी मूल्य हम टर्मिनेटर के रूप में उठाते हैं) नहीं हो सकते हैं ।
उसके शीर्ष पर, हमने O (n) संचालन में O (1) ऑपरेशन क्या थे, इसे भी बदल दिया है।
टी एल; डॉ
स्टैक पॉइंटर स्टैक के शीर्ष पर नज़र रखता है, जहां सभी कार्रवाई होती है। वहाँ से छुटकारा पाने के तरीके हैं ( bp[count]
और top
अनिवार्य रूप से अभी भी स्टैक पॉइंटर हैं), लेकिन वे दोनों अंत में स्टैक पॉइंटर होने की तुलना में अधिक जटिल और धीमे होते हैं। और यह नहीं पता है कि स्टैक के शीर्ष का मतलब है कि आप स्टैक का उपयोग नहीं कर सकते हैं।
नोट: x86 में रनटाइम स्टैक के "नीचे" की ओर इशारा करते हुए स्टैक पॉइंटर पूरे रनटाइम स्टैक से संबंधित गलत धारणा हो सकती है। दूसरे शब्दों में, स्टैक का आधार एक उच्च मेमोरी पते पर रखा गया है, और स्टैक की नोक निचले मेमोरी एड्रेस में बढ़ती है। स्टैक पॉइंटर स्टैक की नोक पर इंगित करता है जहां सभी क्रिया होती है, बस स्टैक के आधार से कम स्मृति पते पर वह टिप होती है।