सबक के लिए समय पर वापस जाने का समय। जबकि हम आज अपनी फैंसी प्रबंधित भाषाओं में इन चीजों के बारे में ज्यादा नहीं सोचते हैं, वे एक ही आधार पर बनाए गए हैं, तो आइए देखें कि सी में मेमोरी कैसे प्रबंधित की जाती है।
इससे पहले कि मैं गोता लगाऊं, शब्द " पॉइंटर " का एक त्वरित स्पष्टीकरण का मतलब है। एक पॉइंटर बस एक वैरिएबल है जो मेमोरी में किसी लोकेशन को "पॉइंट" करता है। यह स्मृति के इस क्षेत्र में वास्तविक मूल्य नहीं रखता है, इसमें इसके लिए स्मृति पता है। एक मेलबॉक्स के रूप में स्मृति के एक ब्लॉक के बारे में सोचो। पॉइंटर उस मेलबॉक्स का पता होगा।
सी में, एक सरणी एक ऑफसेट के साथ बस एक संकेतक है, ऑफसेट निर्दिष्ट करता है कि स्मृति में कितनी दूर देखना है। यह O (1) पहुंच का समय प्रदान करता है।
MyArray [5]
^ ^
Pointer Offset
अन्य सभी डेटा संरचनाएं या तो इस पर निर्माण करती हैं, या भंडारण के लिए आसन्न मेमोरी का उपयोग नहीं करती हैं, जिसके परिणामस्वरूप खराब रैंडम एक्सेस टाइम दिखता है (हालांकि अनुक्रमिक मेमोरी का उपयोग नहीं करने के अन्य लाभ हैं)।
उदाहरण के लिए, मान लें कि हमारे पास 6 संख्याओं (6,4,2,3,1,5) के साथ एक सरणी है, स्मृति में यह इस तरह दिखेगा:
=====================================
| 6 | 4 | 2 | 3 | 1 | 5 |
=====================================
एक सरणी में, हम जानते हैं कि प्रत्येक तत्व स्मृति में एक दूसरे के बगल में है। एसी एरे ( MyArray
यहाँ कहा जाता है) केवल पहले तत्व के लिए एक संकेतक है:
=====================================
| 6 | 4 | 2 | 3 | 1 | 5 |
=====================================
^
MyArray
अगर हम ऊपर देखना चाहते हैं MyArray[4]
, तो आंतरिक रूप से इसे इस तरह एक्सेस किया जाएगा:
0 1 2 3 4
=====================================
| 6 | 4 | 2 | 3 | 1 | 5 |
=====================================
^
MyArray + 4 ---------------/
(Pointer + Offset)
क्योंकि हम सूचक में ऑफसेट को जोड़कर सरणी में किसी भी तत्व को सीधे एक्सेस कर सकते हैं, हम सरणी के आकार की परवाह किए बिना किसी भी तत्व को उसी समय में देख सकते हैं। इसका मतलब है कि मिलने MyArray[1000]
में उतना ही समय लगेगा जितना कि मिलने में MyArray[5]
।
एक वैकल्पिक डेटा संरचना एक लिंक की गई सूची है। यह बिंदुओं की एक रैखिक सूची है, प्रत्येक अगले नोड की ओर इशारा करता है
======== ======== ======== ======== ========
| Data | | Data | | Data | | Data | | Data |
| | -> | | -> | | -> | | -> | |
| P1 | | P2 | | P3 | | P4 | | P5 |
======== ======== ======== ======== ========
P(X) stands for Pointer to next node.
ध्यान दें कि मैंने प्रत्येक "नोड" को अपने ब्लॉक में बनाया है। ऐसा इसलिए है क्योंकि वे स्मृति में आसन्न होने की गारंटी नहीं हैं (और सबसे अधिक संभावना नहीं होगी)।
अगर मैं P3 को एक्सेस करना चाहता हूं, तो मैं इसे सीधे एक्सेस नहीं कर सकता, क्योंकि मुझे नहीं पता कि यह मेमोरी में कहां है। मुझे पता है कि रूट (P1) कहां है, इसलिए इसके बजाय मुझे P1 पर शुरू करना है, और प्रत्येक पॉइंटर को वांछित नोड पर फॉलो करना है।
यह एक ओ (एन) लुक अप टाइम है (जैसा कि प्रत्येक तत्व जोड़ा जाता है, लुक अप लागत बढ़ जाती है)। P4 की तुलना में P1000 प्राप्त करना बहुत अधिक महंगा है।
उच्च स्तरीय डेटा संरचनाएं, जैसे कि हैशटेबल्स, स्टैक्स और कतारें, सभी आंतरिक रूप से एक सरणी (या कई सरणियों) का उपयोग कर सकते हैं, जबकि लिंक्ड सूची और बाइनरी ट्रीड आमतौर पर नोड्स और पॉइंटर्स का उपयोग करते हैं।
आप आश्चर्यचकित हो सकते हैं कि कोई भी एक डेटा संरचना का उपयोग क्यों करेगा, जिसमें एक सरणी का उपयोग करने के बजाय एक मूल्य को देखने के लिए रैखिक ट्रैवर्सल की आवश्यकता होती है, लेकिन उनके पास इसके उपयोग नहीं होते हैं।
हमारी सरणी फिर से ले लो। इस बार, मैं '5' मान रखने वाले सरणी तत्व को खोजना चाहता हूँ।
=====================================
| 6 | 4 | 2 | 3 | 1 | 5 |
=====================================
^ ^ ^ ^ ^ FOUND!
इस स्थिति में, मुझे नहीं पता कि इसे खोजने के लिए पॉइंटर को क्या जोड़ना है, इसलिए मुझे 0 पर शुरू करना होगा, और अपना रास्ता तब तक काम करना होगा जब तक कि मैं इसे ढूंढ न लूं। इसका मतलब है कि मुझे 6 चेक करने होंगे।
इस वजह से, किसी सरणी में मान की खोज करना O (N) माना जाता है। जैसे-जैसे सरणी बड़ी होती जाती है, खोज की लागत बढ़ती जाती है।
ऊपर याद रखें जहां मैंने कहा था कि कभी-कभी गैर अनुक्रमिक डेटा संरचना का उपयोग करने के फायदे हो सकते हैं? डेटा की खोज इन फायदों में से एक है और सबसे अच्छा उदाहरण बाइनरी ट्री है।
एक बाइनरी ट्री एक लिंक की गई सूची के समान एक डेटा संरचना है, हालांकि एक नोड से लिंक करने के बजाय, प्रत्येक नोड दो बच्चों के नोड से जुड़ सकता है।
==========
| Root |
==========
/ \
========= =========
| Child | | Child |
========= =========
/ \
========= =========
| Child | | Child |
========= =========
Assume that each connector is really a Pointer
जब डेटा को एक बाइनरी ट्री में डाला जाता है, तो यह तय करने के लिए कई नियमों का उपयोग करता है कि नए नोड को कहां रखा जाए। मूल अवधारणा यह है कि यदि नया मूल्य माता-पिता से अधिक है, तो वह इसे बाईं ओर सम्मिलित करता है, यदि यह कम है, तो इसे दाईं ओर सम्मिलित करता है।
इसका मतलब है कि एक बाइनरी ट्री में मान इस तरह दिख सकते हैं:
==========
| 100 |
==========
/ \
========= =========
| 200 | | 50 |
========= =========
/ \
========= =========
| 75 | | 25 |
========= =========
75 के मूल्य के लिए एक द्विआधारी पेड़ की खोज करते समय, हमें केवल इस संरचना के कारण 3 नोड्स (O (लॉग एन)) पर जाने की आवश्यकता है:
- क्या 75 100 से कम है? राइट नोड पर देखें
- क्या 75 50 से अधिक है? लेफ्ट नोड को देखें
- वहाँ 75 है!
हालांकि हमारे पेड़ में 5 नोड्स हैं, हमें शेष दो को देखने की आवश्यकता नहीं थी, क्योंकि हम जानते थे कि वे (और उनके बच्चे) संभवतः उस मूल्य को शामिल नहीं कर सकते हैं जिसकी हम तलाश कर रहे थे। यह हमें एक खोज समय देता है कि सबसे खराब स्थिति का मतलब है कि हमें प्रत्येक नोड पर जाना है, लेकिन सबसे अच्छी स्थिति में हमें केवल नोड्स के एक छोटे हिस्से का दौरा करना होगा।
यह वह जगह है जहाँ सरणियों को हराया जाता है, वे ओ (1) एक्सेस समय के बावजूद एक रैखिक ओ (एन) खोज समय प्रदान करते हैं।
यह मेमोरी में डेटा संरचनाओं पर एक अविश्वसनीय रूप से उच्च स्तर का अवलोकन है, बहुत सारे विवरणों पर लंघन है, लेकिन उम्मीद है कि यह अन्य डेटा संरचनाओं की तुलना में एक सरणी की ताकत और कमजोरी को दिखाता है।