किन परिस्थितियों में लिंक की गई सूची उपयोगी है?


110

ज्यादातर बार मैं देखता हूं कि लोग लिंक की गई सूचियों का उपयोग करने की कोशिश करते हैं, यह मुझे एक गरीब (या बहुत गरीब) विकल्प की तरह लगता है। शायद यह उन परिस्थितियों का पता लगाने के लिए उपयोगी होगा जिनके तहत एक लिंक की गई सूची डेटा संरचना का अच्छा विकल्प नहीं है या नहीं है।

आदर्श रूप से, जवाब एक डेटा संरचना का चयन करने के लिए मानदंड पर विस्तृत होगा, और निर्दिष्ट परिस्थितियों में कौन से डेटा संरचनाएं सबसे अच्छा काम करने की संभावना है।

संपादित करें: मुझे कहना होगा, मैं न केवल संख्या, बल्कि उत्तरों की गुणवत्ता से काफी प्रभावित हूं। मैं केवल एक को स्वीकार कर सकता हूं, लेकिन दो या तीन और मैं कहना चाहूंगा कि अगर कुछ बेहतर नहीं होता तो मैं स्वीकार करने लायक होता। केवल एक युगल (विशेष रूप से जिसे मैंने स्वीकार किया था) उन स्थितियों की ओर इशारा किया जहां एक लिंक की गई सूची ने वास्तविक लाभ प्रदान किया। मुझे लगता है कि स्टीव जेसोप न केवल एक, बल्कि तीन अलग-अलग उत्तरों के साथ आने के लिए किसी तरह के सम्मानजनक उल्लेख के हकदार हैं, जिनमें से सभी मुझे काफी प्रभावशाली लगे। बेशक, भले ही यह केवल एक टिप्पणी के रूप में पोस्ट किया गया था, एक जवाब नहीं, मुझे लगता है कि नील की ब्लॉग प्रविष्टि अच्छी तरह से पढ़ने लायक है - न केवल जानकारीपूर्ण, बल्कि काफी मनोरंजक भी।


34
आपके दूसरे पैराग्राफ का उत्तर एक सेमेस्टर के बारे में है।
सेवा अलेक्सेयेव

2
मेरी राय के लिए, punchlet.wordpress.com/2009/12/27/letter-the-fourth देखें । और जैसा कि यह एक सर्वेक्षण लगता है, यह शायद सीडब्ल्यू होना चाहिए।

1
@ नील, अच्छा, हालांकि मुझे संदेह है कि सीएस लुईस अनुमोदन करेंगे।
टॉम

@ नील: मैं एक सर्वेक्षण की तरह लगता है। अधिकतर यह देखने का एक प्रयास है कि क्या कोई ऐसा उत्तर दे सकता है जिसका कोई आधार हो जो मैं उचित होने के नाते कम से कम खरीद सकता हूं। @Seva: हाँ, इसे फिर से पढ़ना, मैंने पिछले वाक्य को मूल रूप से इच्छित उद्देश्य से थोड़ा अधिक सामान्य बना दिया।
जेरी कॉफिन

2
@ यार लोग (मेरे सहित, मुझे यह कहने के लिए खेद है) फोरट्रान IV (जिसमें पॉइंटर्स की कोई धारणा नहीं थी) जैसी भाषाओं में बिना पॉइंटर्स के लिंक्ड लिस्ट को लागू करने के लिए उपयोग किया जाता था, जितना कि उन्होंने पेड़ से किया था। आपने "वास्तविक" मेमोरी के बजाय सरणियों का उपयोग किया।

जवाबों:


40

वे समवर्ती डेटा संरचनाओं के लिए उपयोगी हो सकते हैं। (अब नीचे एक गैर-समवर्ती वास्तविक-विश्व उपयोग नमूना है - कि @Neil FORTRAN ;-) नहीं होता तो वहाँ नहीं होता ।

उदाहरण के लिए, ConcurrentDictionary<TKey, TValue>.NET 4.0 RC में एक ही बाल्टी में हैश करने वाली चेन आइटम्स से लिंक्ड लिस्ट का उपयोग करें।

अंतर्निहित डेटा संरचना ConcurrentStack<T>भी एक लिंक की गई सूची है।

ConcurrentStack<T>डेटा संरचनाओं में से एक है जो नए थ्रेड पूल के लिए नींव के रूप में काम करता है , (स्टैक के रूप में लागू स्थानीय "कतारों" के साथ अनिवार्य रूप से)। (अन्य मुख्य सहायक संरचना होने के नाते ConcurrentQueue<T>।)

बदले में नया थ्रेड पूल नई टास्क पैरेलल लाइब्रेरी के कार्य समयबद्धन के लिए आधार प्रदान करता है ।

इसलिए वे निश्चित रूप से उपयोगी हो सकते हैं - एक लिंक्ड सूची वर्तमान में कम से कम एक महान नई तकनीक के मुख्य सहायक संरचनाओं में से एक के रूप में सेवारत है।

(एक एकल-लिंक की गई सूची एक सम्मोहक लॉक-मुक्त बनाती है - लेकिन इन मामलों में प्रतीक्षा-मुक्त नहीं है, क्योंकि मुख्य संचालन एक ही कैस (+ रिट्रीज़) के साथ किया जा सकता है । आधुनिक जीसी-डी वातावरण में - जैसे Java और .NET - ABA समस्या से आसानी से बचा जा सकता है। बस उन वस्तुओं को लपेटें जिन्हें आप नए सिरे से बनाए गए नोड्स में जोड़ते हैं और उन नोड्स का पुन: उपयोग नहीं करते हैं - जीसी को अपना काम करने दें। ABA समस्या का पेज लॉक का कार्यान्वयन भी प्रदान करता है। फ्री स्टैक - जो वास्तव में .Net (और जावा) में काम करता है (जीसी-एड) नोड्स को पकड़े हुए है।)

संपादित करें : @Neil: वास्तव में, आपने फोरट्रान के बारे में जो उल्लेख किया है, उसने मुझे याद दिलाया है कि उसी तरह की लिंक की गई सूचियाँ शायद .NET में सबसे अधिक उपयोग की जाने वाली और दुरुपयोग की गई डेटा संरचना में पाई जा सकती हैं: प्लेन .NET जेनेरिक Dictionary<TKey, TValue>

एक नहीं, बल्कि कई लिंक की गई सूचियों को एक सरणी में संग्रहीत किया जाता है।

  • यह आवेषण / हटाने पर कई छोटे (डी) आवंटन करने से बचता है।
  • हैश तालिका का प्रारंभिक लोडिंग बहुत तेज़ है, क्योंकि सरणी क्रमिक रूप से भरी हुई है (सीपीयू कैश के साथ बहुत अच्छा खेलती है)।
  • यह उल्लेख करने के लिए नहीं कि मेमोरी के संदर्भ में एक चेनिंग हैश टेबल महंगी है - और यह "ट्रिक" एक्स 64 के आधे में "पॉइंटर साइज" काटती है।

अनिवार्य रूप से, कई लिंक किए गए सूचियों को एक सरणी में संग्रहीत किया जाता है। (उपयोग की गई प्रत्येक बाल्टी के लिए एक।) पुन: प्रयोज्य नोड्स की एक मुफ्त सूची उनके बीच "इंटरवॉवन" है (यदि हटाए गए थे)। एक सरणी प्रारंभ में / पुनःशः पर आवंटित की जाती है और इसमें जंजीरों के नोड रखे जाते हैं। एक फ्री पॉइंटर भी है - एरे में एक इंडेक्स - जो डिलीट करता है। ;-) सो - मानो या न मानो - FORTRAN तकनीक अभी भी जीवित है। (... और कहीं नहीं, सबसे अधिक इस्तेमाल किया जाने वाले .NET डेटा संरचनाओं में से एक में ;-)।


2
यदि आप चूक गए हैं, तो यहां नील की टिप्पणी है: "लोग (मुझे सहित, मुझे यह कहते हुए खेद है) फोरट्रान चतुर्थ (जो कि संकेत देने वालों की कोई धारणा नहीं थी) जैसी भाषाओं में पॉइंटर्स के बिना लिंक की गई सूचियों को लागू करने के लिए उपयोग किया जाता है, जितना कि उन्होंने पेड़ किया था। । आपने "वास्तविक" मेमोरी के बजाय सरणियों का उपयोग किया।
एंड्रास वास

मुझे लगता है कि Dictionary.NET में काफी अधिक बचत के मामले में "लिंक की गई सूचियों को एक सरणी में" दृष्टिकोण में जोड़ना चाहिए : अन्यथा प्रत्येक नोड को ढेर पर एक अलग ऑब्जेक्ट की आवश्यकता होगी - और हीप पर आवंटित प्रत्येक ऑब्जेक्ट में कुछ ओवरहेड होता है। ( en.csharp-online.net/Common_Type_System%E2%80%94Object_Layout )
एंड्रस वास

यह जानना भी अच्छा है कि C ++ का डिफ़ॉल्ट std::listताले के बिना बहुपरत संदर्भ में सुरक्षित नहीं है।
मूविंग डक

49

लिंक की गई सूचियाँ बहुत उपयोगी होती हैं जब आपको बहुत सारे सम्मिलन और निष्कासन करने की आवश्यकता होती है, लेकिन बहुत अधिक खोज नहीं होती है, मनमानी (संकलन-समय पर अज्ञात) की सूची में।

विभाजित करना और जुड़ना (द्विदिश-रूप से जुड़ा हुआ) सूचियाँ बहुत ही कुशल हैं।

आप लिंक की गई सूचियों को भी जोड़ सकते हैं - जैसे कि पेड़ की संरचनाओं को "ऊर्ध्वाधर" से जुड़ी सूचियों (माता-पिता / बच्चे के रिश्तों) के रूप में लागू किया जा सकता है, जो एक साथ जुड़ी हुई क्षैतिज सूचियों (भाई-बहन) को जोड़ते हैं।

इन उद्देश्यों के लिए एक सरणी आधारित सूची का उपयोग करने की गंभीर सीमाएँ हैं:

  • एक नया आइटम जोड़ने का मतलब है कि सरणी को पुनः लोड किया जाना चाहिए (या आपको भविष्य की वृद्धि की अनुमति देने और वास्तविकताओं की संख्या को कम करने की आवश्यकता से अधिक स्थान आवंटित करना चाहिए)
  • आइटम को हटाने से व्यर्थ जगह निकल जाती है या फिर एक वास्तविक स्थान की आवश्यकता होती है
  • अंत को छोड़कर कहीं भी आइटम सम्मिलित करना (संभवत: पुनः प्राप्ति और) एक स्थिति में बहुत सारे डेटा की प्रतिलिपि बनाना शामिल है

5
इसलिए यह सवाल कम हो जाता है कि आपको अनुक्रम के मध्य में बहुत सारे सम्मिलन और निष्कासन करने की आवश्यकता नहीं है, लेकिन क्रमिक द्वारा सूची में बहुत सारे लुकअप नहीं हैं? किसी लिंक की गई सूची को कॉपी करना किसी सरणी को कॉपी करने की तुलना में आम तौर पर अधिक या महंगा होता है, इसलिए आपके द्वारा सरणियों में आइटम निकालने और सम्मिलित करने के बारे में जो कुछ भी कहा जाता है, वह सूचियों में रैंडम-एक्सेस के लिए उतना ही बुरा है। LRU कैश एक उदाहरण है जिसके बारे में मैं सोच सकता हूं, आपको बीच में बहुत दूर करने की आवश्यकता है, लेकिन आपको कभी भी सूची को चलाने की आवश्यकता नहीं है।
स्टीव जेसप

2
सूची में जोड़ने से आपके द्वारा जोड़े जाने वाले प्रत्येक तत्व के लिए मेमोरी आवंटन शामिल है। इसमें एक सिस्टम कॉल शामिल हो सकता है जो बहुत महंगा होगा। किसी सरणी में जोड़ने पर केवल ऐसी कॉल की आवश्यकता होती है यदि सरणी को बड़ा किया जाना चाहिए। वास्तव में, अधिकांश भाषाओं में (इन कारणों से) सरणी पसंदीदा डेटा संरचना है और सूचियों का उपयोग शायद ही कभी किया जाता है।

1
कौन सा मान लें? यह आवंटन आश्चर्यजनक रूप से तेजी से स्पष्ट है - आमतौर पर एक संकेतक में ऑब्जेक्ट आकार को जोड़ने की आवश्यकता होती है। जीसी के लिए कुल ओवरहेड कम है? पिछली बार जब मैंने इसे एक वास्तविक ऐप पर मापने की कोशिश की थी, तो मुख्य बिंदु यह था कि जब प्रोसेसर अन्यथा निष्क्रिय था, तो जावा सभी काम कर रहा था, इसलिए स्वाभाविक रूप से यह दृश्य प्रदर्शन को बहुत प्रभावित नहीं करता था। एक व्यस्त-सीपीयू बेंचमार्क में जावा को परेशान करना आसान था, और बहुत खराब स्थिति आवंटन समय प्राप्त करना। हालांकि, यह कई साल पहले था, और जेनेरिक कचरा संग्रह ने स्पष्ट रूप से जीसी की कुल लागत को कम कर दिया है।
स्टीव जेसप

1
@ साइट: आप सूची और सरणियों के बीच "समान" आवंटन के बारे में गलत हैं। हर बार जब आपको सूची के लिए मेमोरी आवंटित करने की आवश्यकता होती है, तो आप बस एक छोटा सा ब्लॉक - O (1) आवंटित करते हैं। एक सरणी के लिए आपको पूरी सूची के लिए पर्याप्त रूप से एक नया ब्लॉक आवंटित करना होगा, और फिर पूरी सूची की प्रतिलिपि बनाएँ - O (n)। किसी सूची में एक ज्ञात स्थान में सम्मिलित करने के लिए आप एक निश्चित संख्या में बिंदुओं को अपडेट करते हैं - O (1), लेकिन एक सरणी में सम्मिलित करने के लिए और सम्मिलन के लिए जगह बनाने के लिए किसी भी बाद की वस्तुओं को कॉपी करने के लिए - O (n)। ऐसे कई मामले हैं जहां सरणियाँ एलएल की तुलना में बहुत कम कुशल हैं।
जेसन विलियम्स

1
@ जेरी: मुझे समझ में आया। मेरा कहना है कि सरणी को फिर से संगठित करने की लागत का एक बड़ा हिस्सा मेमोरी आवंटित नहीं कर रहा है, यह पूरी सरणी सामग्री को नई मेमोरी में कॉपी करने की आवश्यकता है । किसी सरणी के आइटम 0 में सम्मिलित करने के लिए आपको संपूर्ण सरणी सामग्री को स्मृति में एक स्थान तक कॉपी करना होगा । मैं यह नहीं कह रहा हूं कि सरणियाँ खराब हैं; बस ऐसी परिस्थितियाँ होती हैं जहाँ यादृच्छिक पहुँच की आवश्यकता नहीं होती है, और जहाँ एलएल की वास्तविक निरंतरता सम्मिलित करना / हटाना / राहत देना बेहतर होता है।
जेसन विलियम्स

20

लिंक्ड सूची बहुत लचीली हैं: एक पॉइंटर के संशोधन के साथ, आप एक बड़े पैमाने पर बदलाव कर सकते हैं, जहां एक ही सूची में एक ही ऑपरेशन बहुत अक्षम होगा।


क्या एक सूची या एक सेट या मानचित्र का उपयोग करना प्रेरित करना संभव होगा?
पितृपक्ष

14

Arrays डेटा संरचनाएं हैं जिनसे लिंक्ड सूचियों की आमतौर पर तुलना की जाती है।

सामान्य रूप से लिंक की गई सूचियाँ तब उपयोगी होती हैं, जब आपको सूची में बहुत अधिक संशोधन करना पड़ता है, जबकि सरणियाँ प्रत्यक्ष तत्व अभिगम पर सूचियों से बेहतर प्रदर्शन करती हैं।

रिश्तेदार संचालन लागत (n = सूची / सरणी लंबाई) की तुलना में सूचियों और सरणियों पर किए जा सकने वाले कार्यों की सूची यहां दी गई है:

  • एक तत्व जोड़ना:
    • सूचियों पर आपको बस नए तत्व और पुनर्निर्देशन बिंदुओं के लिए मेमोरी आवंटित करने की आवश्यकता है। हे (1)
    • सरणी पर आपको सरणी स्थानांतरित करना होगा। पर)
  • किसी तत्व को निकालना
    • सूचियों पर आप सिर्फ अनुप्रेषित संकेत देते हैं। हे (1)।
    • सरणी पर आप सरणी को स्थानांतरित करने के लिए O (n) समय बिताते हैं यदि निकालने का तत्व सरणी का पहला या अंतिम तत्व नहीं है; अन्यथा आप केवल पॉइंटर को आरो की शुरुआत में स्थानांतरित कर सकते हैं या ऐरे की लंबाई को कम कर सकते हैं
  • एक ज्ञात स्थिति में एक तत्व प्राप्त करना:
    • सूचियों पर आपको सूची को पहले तत्व से तत्व तक विशिष्ट स्थिति में चलना होगा। सबसे खराब स्थिति: O (n)
    • सरणियों पर आप तत्व को तुरंत एक्सेस कर सकते हैं। हे (1)

यह इन दो लोकप्रिय और बुनियादी डेटा संरचनाओं की एक बहुत ही निम्न-स्तरीय तुलना है और आप देख सकते हैं कि सूचियाँ उन परिस्थितियों में बेहतर प्रदर्शन करती हैं, जहाँ आपको सूची को स्वयं बनाने (तत्वों को हटाने या जोड़ने) के लिए बहुत सारे संशोधन करने होंगे। दूसरी ओर सरणियाँ सूचियों से बेहतर प्रदर्शन करती हैं, जब आपको सीधे एरे के तत्वों का उपयोग करना होता है।

मेमोरी आवंटन के दृष्टिकोण से, सूचियाँ बेहतर हैं क्योंकि एक दूसरे के बगल में सभी तत्वों को रखने की कोई आवश्यकता नहीं है। दूसरी ओर अगले (या पिछले) तत्व की ओर संकेत करने के लिए (थोड़ा) ओवरहेड है।

इन अंतरों को जानना डेवलपर्स के लिए उनके कार्यान्वयन में सूचियों और सरणियों के बीच चयन करना महत्वपूर्ण है।

ध्यान दें कि यह सूचियों और सरणियों की तुलना है। यहां बताई गई समस्याओं के अच्छे समाधान हैं (जैसे: SkipLists, डायनामिक एरे, आदि ...)। इस उत्तर में मैंने उन बुनियादी डेटा संरचना को ध्यान में रखा है जिनके बारे में प्रत्येक प्रोग्रामर को पता होना चाहिए।


यह सूचियों के अच्छे कार्यान्वयन और सरणियों के एक भयानक कार्यान्वयन के लिए कुछ हद तक सही है। अधिकांश सरणी कार्यान्वयन कहीं अधिक परिष्कृत हैं जितना आप उन्हें क्रेडिट देते हैं। और मुझे नहीं लगता कि आप समझते हैं कि महंगी गतिशील मेमोरी आवंटन कैसे हो सकता है।

यह उत्तर डेटा संरचना विश्वविद्यालय पाठ्यक्रम के कार्यक्रम को कवर करने के लिए नहीं है। यह एक तुलना से जुड़ी सूचियों और सरणियों को ध्यान में रखते हुए लिखी गई है, जिसे आप, मैं और अधिकांश लोग जानते हैं। ज्यामितीय रूप से विस्तार करने वाले एरे, लिस्ट लिस्ट इत्यादि ... ऐसे समाधान हैं जो मुझे पता हैं, मैं उपयोग करता हूं और मैं अध्ययन करता हूं लेकिन इसके लिए गहन स्पष्टीकरण की आवश्यकता होती है और यह स्टैकओवरफ्लो उत्तर में फिट नहीं होगा।
एंड्रिया जिलियो

1
"स्मृति आवंटन के दृष्टिकोण से, सूचियाँ बेहतर हैं क्योंकि सभी तत्वों को एक-दूसरे के बगल में रखने की कोई आवश्यकता नहीं है।" इसके विपरीत, सन्निहित कंटेनर बेहतर होते हैं क्योंकि वे तत्वों को एक दूसरे के बगल में रखते हैं। आधुनिक कंप्यूटर पर, डेटा स्थानीयता राजा है। स्मृति में इधर-उधर कूदने से आपके कैश का प्रदर्शन समाप्त हो जाता है, और ऐसे प्रोग्रामों की ओर जाता है जो एक गतिशील सरणी जैसे C ++ std::vectorजैसे लिंक किए गए सूची जैसे C ++ की तुलना में एक गतिशील सरणी के साथ तेजी से प्रदर्शन करने वाले (प्रभावी) यादृच्छिक स्थान पर एक तत्व डालते हैं std::list, बस सूची इतनी महंगी है।
डेविड स्टोन

@DavidStone शायद मैं पर्याप्त स्पष्ट नहीं था, लेकिन उस वाक्य के साथ मैं इस तथ्य का उल्लेख कर रहा था कि आपको अपने तत्वों को संग्रहीत करने के लिए सन्निहित स्थान की आवश्यकता नहीं है। विशेष रूप से अगर आप कुछ छोटा स्टोर नहीं करना चाहते हैं और आपके पास सीमित उपलब्ध मेमोरी है तो आपके पास अपने डेटा को स्टोर करने के लिए पर्याप्त सन्निहित खाली स्थान नहीं हो सकता है , लेकिन आप संभवतः इसके बजाय एक सूची का उपयोग करके अपने डेटा को फिट कर सकते हैं (भले ही आपके पास पॉइंटर्स का ओवरहेड होगा ... दोनों अंतरिक्ष के कारण वे लेते हैं और आपके द्वारा उल्लिखित प्रदर्शन समस्याएं)। मुझे संभवतः इसे स्पष्ट करने के लिए अपने उत्तर को अपडेट करना चाहिए।
एंड्रिया ज़िलियो

4

एकल-आबंटनकर्ता या ऑब्जेक्ट पूल में नि: शुल्क सूची के लिए सिंगली-लिंक्ड सूची एक अच्छा विकल्प है:

  1. आपको केवल एक स्टैक की आवश्यकता है, इसलिए एक एकल-लिंक की गई सूची पर्याप्त है।
  2. सब कुछ पहले से ही नोड्स में विभाजित है। घुसपैठ सूची नोड के लिए कोई आवंटन ओवरहेड नहीं है, बशर्ते कि कोशिकाओं में एक सूचक शामिल करने के लिए पर्याप्त बड़ा हो।
  3. एक वेक्टर या एक ब्लॉक प्रति एक पॉइंटर के ओवरहेड को लगाएगा। यह महत्वपूर्ण है कि जब आप पहली बार ढेर बनाते हैं, तो सभी कोशिकाएं स्वतंत्र होती हैं, इसलिए यह एक अप-फ्रंट लागत है। सबसे खराब स्थिति में यह प्रति सेल मेमोरी आवश्यकता को दोगुना कर देता है।

खैर, मान गए। लेकिन वास्तव में कितने प्रोग्रामर ऐसी चीजें बना रहे हैं? अधिकांश बस फिर से लागू कर रहे हैं जो std :: सूची आदि आपको देते हैं। और वास्तव में "घुसपैठ" सामान्य रूप से आपके द्वारा दिए गए से थोड़ा अलग अर्थ है - कि प्रत्येक संभावित सूची तत्व में डेटा से अलग एक सूचक होता है।

1
कितने? 0 से अधिक, एक लाख से भी कम ;-) जेरी का सवाल था "सूचियों का अच्छा उपयोग करें", या "उन सूचियों का अच्छा उपयोग करें जो हर प्रोग्रामर दैनिक आधार पर उपयोग करता है", या बीच में कुछ? मुझे किसी सूची नोड के लिए "घुसपैठ" के अलावा कोई अन्य नाम नहीं पता है जो कि उस सूची तत्व में निहित है - चाहे वह संघ के हिस्से के रूप में हो (सी शब्दों में) या नहीं। बिंदु 3 केवल उन भाषाओं में लागू होता है जो आपको इसे करने देती हैं - सी, सी ++, कोडांतरक अच्छा। जावा खराब।
स्टीव जेसप

4

संदेह से जुड़ी सूची एक हैशमप के आदेश को परिभाषित करने के लिए एक अच्छा विकल्प है जो तत्वों पर एक आदेश को भी परिभाषित करता है (विशेषकर जावा में लिंक्डहैश मैप), जब अंतिम पहुंच का आदेश दिया जाता है:

  1. एक संबंधित वेक्टर या डेक्स (1 के बजाय 2 पॉइंटर्स) की तुलना में अधिक मेमोरी ओवरहेड, लेकिन बेहतर सम्मिलित / प्रदर्शन को हटा दें।
  2. कोई आवंटन ओवरहेड नहीं है, क्योंकि आपको किसी भी तरह से हैश प्रविष्टि के लिए नोड की आवश्यकता है।
  3. सदिश या बिंदु के बिंदु के साथ तुलना में संदर्भ की स्थानीयता कोई अतिरिक्त समस्या नहीं है, क्योंकि आपको प्रत्येक वस्तु को किसी भी तरह से मेमोरी में खींचना होगा।

निश्चित रूप से, आप इस बारे में बहस कर सकते हैं कि क्या LRU कैश पहली बार में एक अच्छा विचार है, जिसकी तुलना में कुछ अधिक परिष्कृत और ट्यून करने योग्य है, लेकिन यदि आप एक होने जा रहे हैं, तो यह काफी सभ्य कार्यान्वयन है। आप हर रीड एक्सेस में वेक्टर या डीके पर एक डिलीट-से-बीच-और-ऐड-टू-एंड करना नहीं चाहते हैं, लेकिन टेल तक नोड को स्थानांतरित करना आमतौर पर ठीक है।


4

लिंक की गई सूचियाँ प्राकृतिक विकल्पों में से एक हैं जब आप नियंत्रित नहीं कर सकते हैं कि आपका डेटा कहाँ संग्रहीत है, लेकिन आपको अभी भी किसी एक वस्तु से दूसरी वस्तु प्राप्त करने की आवश्यकता है।

उदाहरण के लिए, C ++ (नया / डिलीट रिप्लेसमेंट) में मेमोरी ट्रैकिंग लागू करने के लिए आपको या तो कुछ नियंत्रण डेटा संरचना की आवश्यकता होती है, जो इस बात पर नज़र रखती है कि किन बिंदुओं को मुक्त किया गया है, जिन्हें आपको अपने आप को लागू करने की आवश्यकता है। इसका विकल्प यह है कि प्रत्येक डेटा चंक की शुरुआत में समग्र सूची को जोड़ा जाए।

क्योंकि आपको हमेशा पता चलता है कि आप लिस्ट में हैं, जब डिलीट कहते हैं, तो आप O (1) में आसानी से मेमोरी छोड़ सकते हैं। इसके अलावा एक नया हिस्सा जो सिर्फ मैलोकेड किया गया है, को ओ (1) में जोड़ना है। इस मामले में सूची का चलना बहुत ही कम आवश्यक है, इसलिए O (n) लागत यहाँ कोई समस्या नहीं है (किसी संरचना का चलना वैसे भी O (n) है)।


3

जब आपको हाई-स्पीड पुश, पॉप और रोटेट की आवश्यकता हो, और O (n) इंडेक्सिंग को बुरा न मानें तो वे उपयोगी होते हैं।


क्या आपने कभी (सी) के साथ तुलना में सी ++ से जुड़ी सूचियों को समय के लिए परेशान किया है?

@ नील: यह नहीं कह सकता कि मेरे पास है।
इग्नासियो वाज़क्वेज़-अब्राम्स

@ नील: यदि C ++ ने किसी अन्य कंटेनर (जो सत्य से बहुत दूर नहीं है) की तुलना में इसे धीमा करने के लिए अपने लिंक किए गए सूची वर्ग को जानबूझकर तोड़फोड़ किया है, तो इसका भाषा-अज्ञेय के साथ क्या करना है? एक घुसपैठ से जुड़ी सूची अभी भी एक लिंक की गई सूची है।
स्टीव जेसोप

@Steve C ++ एक भाषा है। मैं यह नहीं देख सकता कि यह कैसे महत्वाकांक्षा रख सकता है। यदि आप सुझाव दे रहे हैं कि C ++ कमेटी के सदस्य किसी तरह लिंक्ड लिस्ट (जो तार्किक रूप से कई कार्यों के लिए धीमी होनी चाहिए) को तोड़फोड़ करते हैं, तो दोषी पुरुषों का नाम दें!

3
यह वास्तव में तोड़फोड़ नहीं है - बाहरी सूची नोड्स के अपने फायदे हैं, लेकिन प्रदर्शन उनमें से एक नहीं है। हालाँकि, निश्चित रूप से हर कोई जागरूक था जब आप उसी चीज़ के व्यापार को बंद करते थे जिसके बारे में आप जानते हैं, जो यह है कि इसके लिए एक अच्छे उपयोग के साथ आना काफी मुश्किल है std::list। एक घुसपैठ की सूची सिर्फ कंटेनर तत्वों पर न्यूनतम आवश्यकताओं के C ++ दर्शन के साथ फिट नहीं है।
स्टीव जेसोप

3

एकल-लिंक्ड सूची कार्यात्मक प्रोग्रामिंग भाषाओं में सामान्य "सूची" डेटा प्रकार का स्पष्ट कार्यान्वयन है:

  1. सिर को जोड़ना तेज है, और (append (list x) (L))और (append (list y) (L))अपने डेटा के लगभग सभी साझा कर सकते हैं। बिना लिखाई वाली भाषा में कॉपी-ऑन-राइट की जरूरत नहीं। कार्यात्मक प्रोग्रामर जानते हैं कि इसका लाभ कैसे उठाया जाए।
  2. पूंछ को जोड़ना दुर्भाग्य से धीमा है, लेकिन इसलिए कोई अन्य कार्यान्वयन होगा।

तुलनात्मक रूप से, एक वेक्टर या डेक्स आमतौर पर या तो अंत में जोड़ने के लिए धीमा होगा, आवश्यकता होती है (कम से कम मेरे दो अलग-अलग अपीलों के उदाहरण में) कि पूरी सूची (वेक्टर), या सूचकांक ब्लॉक और डेटा ब्लॉक की एक प्रति ली जाए। (छल) में संलग्न किया जा रहा है। असल में, बड़ी सूचियों के लिए वहाँ कुछ कहा जा सकता है, जिन्हें किसी कारण से पूंछने की ज़रूरत होती है, मुझे जज को कार्यात्मक प्रोग्रामिंग के बारे में पर्याप्त जानकारी नहीं है।


3

लिंक की गई सूची के लिए अच्छे उपयोग का एक उदाहरण वह है जहाँ सूची तत्व बहुत बड़े हैं। इतना बड़ा कि एक ही समय में केवल एक या दो ही सीपीयू कैश में फिट हो सकते हैं। इस बिंदु पर कि लाभकारी खंड कंटेनर जैसे वैक्टर या सरणियों के लिए पुनरावृत्ति के लिए अधिक या कम अशक्त है, और एक प्रदर्शन लाभ संभव हो सकता है अगर कई सम्मिलन और निष्कासन वास्तविक समय में हो रहे हैं।


2

मेरे अनुभव से, विरल-मैट्रिस और रिट्रेसमेंट ढेर को लागू करना। इस तरह की डेटा संरचनाओं के लिए लिंक की गई सूचियाँ आपको समग्र संरचना पर अधिक नियंत्रण प्रदान करती हैं। हालांकि मुझे यकीन नहीं है कि लिंक्ड लिस्ट का उपयोग करके स्पार्स-मैट्रिसेस को सबसे अच्छी तरह से लागू किया जाता है - शायद एक बेहतर तरीका है, लेकिन इसने अंडरग्राउंड सीएस में लिंक्ड लिस्ट का उपयोग करके विरल मैट्रिस के इन-आउट्स को सीखने में मदद की :)


1

दो पूरक संचालन हैं जो सूचियों पर तुच्छ रूप से O (1) हैं और अन्य डेटा संरचनाओं में O (1) में लागू करने के लिए बहुत कठिन हैं - मनमाना स्थिति से एक तत्व को निकालना और सम्मिलित करना, यह मानते हुए कि आपको तत्वों के क्रम को बनाए रखने की आवश्यकता है।

हैश मानचित्र स्पष्ट रूप से ओ (1) में सम्मिलन और विलोपन कर सकते हैं, लेकिन तब आप क्रम में तत्वों पर पुनरावृति नहीं कर सकते।

ऊपर दिए गए तथ्य को देखते हुए, हैश मैप को निफ्टी LRU कैश बनाने के लिए एक लिंक की गई सूची के साथ जोड़ा जा सकता है: एक मैप जो कि निश्चित-मूल्य वाले जोड़े की एक निश्चित संख्या को संग्रहीत करता है और नए लोगों के लिए जगह बनाने के लिए कम से कम हाल ही में एक्सेस की गई कुंजी को ड्रॉप करता है।

हैश मैप में प्रविष्टियों को लिंक किए गए लिस्ट नोड्स में संकेत देने की आवश्यकता होती है। जब हैश मानचित्र पर पहुंचते हैं, तो लिंक की गई सूची नोड को उसकी वर्तमान स्थिति से हटा दिया जाता है और सूची के प्रमुख (O (1), yay से लिंक की गई सूचियों के लिए) ले जाया जाता है। जब कम से कम हाल ही में उपयोग किए गए तत्व को निकालने की आवश्यकता होती है, तो सूची की पूंछ से एक को गिरा दिया जाना चाहिए (फिर से हे (1) यह मानते हुए कि आप पॉइंटर नोड को पूंछ के साथ रखते हैं) एक साथ संबंधित हैश मानचित्र प्रविष्टि (ताकि बैकलिंक से) हैश मानचित्र की सूची आवश्यक है।)


1

इस बात पर विचार करें कि लिंक की गई सूची एक डोमेन ड्रिवेन डिज़ाइन शैली में एक प्रणाली के लिए बहुत उपयोगी हो सकती है जिसमें पुनरावृत्ति के साथ इंटरलॉक करने वाले हिस्से शामिल हैं।

एक उदाहरण जो दिमाग में आता है यदि आप एक लटकती श्रृंखला के लिए मॉडलिंग कर रहे थे। यदि आप यह जानना चाहते हैं कि किसी विशेष लिंक पर तनाव क्या था, तो आपके इंटरफ़ेस में "स्पष्ट" वजन के लिए एक गेट्टर शामिल हो सकता है। जिसके कार्यान्वयन में एक लिंक शामिल होगा जो अपने स्पष्ट वजन के लिए अगला लिंक पूछेगा, फिर परिणाम में अपना वजन जोड़ देगा। इस तरह, नीचे से नीचे तक की पूरी लंबाई का मूल्यांकन चेन के ग्राहक के सिंगल कॉल से किया जाएगा।

कोड का एक प्रस्तावक होने के नाते जो प्राकृतिक भाषा की तरह पढ़ता है, मुझे पसंद है कि यह कैसे प्रोग्रामर को एक चेन लिंक से पूछेगा कि यह कितना भार ले जा रहा है। यह लिंक कार्यान्वयन की सीमा के भीतर गुणों के इन बच्चों की गणना करने की चिंता भी रखता है, एक श्रृंखला वजन गणना सेवा की आवश्यकता को हटा देता है "।


1

सबसे उपयोगी मामलों में से एक मुझे जाल और छवि प्रसंस्करण, भौतिकी इंजन और प्रदर्शन जैसे महत्वपूर्ण क्षेत्रों में काम करने वाली लिंक्डइन सूचियों के लिए मिलता है, जब लिंक की गई सूचियों का उपयोग वास्तव में संदर्भ के स्थानीयता में सुधार करता है और ढेर आवंटन को कम करता है और कभी-कभी इसकी तुलना में मेमोरी का उपयोग भी कम कर देता है सीधा विकल्प।

अब यह एक पूर्ण ऑक्सीमोरोन की तरह लग सकता है जो लिंक्ड लिस्ट सभी कर सकता है क्योंकि वे अक्सर विपरीत करने के लिए कुख्यात होते हैं, लेकिन उनके पास एक अद्वितीय संपत्ति होती है जिसमें प्रत्येक सूची नोड का एक निश्चित आकार और संरेखण आवश्यकताएं होती हैं जिन्हें हम अनुमति देने के लिए शोषण कर सकते हैं उन्हें संचरित रूप से संग्रहीत किया जाना चाहिए और निरंतर-समय पर उन तरीकों से हटाया जाना चाहिए जो चर-आकार की चीजें नहीं कर सकते हैं।

परिणामस्वरूप, एक ऐसा मामला लेते हैं जहां हम एक चर-लंबाई अनुक्रम को संग्रहीत करने के अनुरूप समतुल्य करना चाहते हैं जिसमें एक लाख नेस्टेड चर-लंबाई उप-अनुक्रम होते हैं। एक ठोस उदाहरण एक अनुक्रमित जाल है जिसमें एक लाख बहुभुज (कुछ त्रिकोण, कुछ क्वाड्स, कुछ पेंटागन, कुछ हेक्सागोन्स, आदि) और कभी-कभी बहुभुज को जाल में कहीं से हटा दिया जाता है और कभी-कभी बहुभुज को एक मौजूदा बहुभुज या एक वर्टेक्स डालने के लिए फिर से बनाया जाता है। एक हटाओ। उस मामले में, अगर हम एक लाख छोटे स्टोर करते हैं std::vectors, तो हम हर एक वेक्टर के लिए ढेर के आवंटन के साथ-साथ संभावित विस्फोटक मेमोरी उपयोग का सामना करते हैं। एक लाख छोटे SmallVectorsको इस समस्या का सामना नहीं करना पड़ सकता है क्योंकि आम मामलों में, लेकिन फिर उनके प्रचारित बफर जो अलग से ढेर-आवंटित नहीं होते हैं, वे अभी भी विस्फोटक स्मृति उपयोग का कारण बन सकते हैं।

यहां समस्या यह है कि एक लाख std::vectorउदाहरण एक लाख चर-लंबाई वाली चीजों को संग्रहीत करने की कोशिश कर रहे हैं। परिवर्तनीय-लंबाई की चीजें एक ढेर आवंटन चाहती हैं क्योंकि वे बहुत प्रभावी ढंग से संचित रूप से संग्रहीत नहीं किए जा सकते हैं और निरंतर-समय में (बहुत जटिल आवंटनकर्ता के बिना कम से कम सीधे तरीके से) हटा दिए जाते हैं यदि वे अपनी सामग्री को कहीं और संग्रहीत नहीं करते हैं।

यदि, इसके बजाय, हम यह करते हैं:

struct FaceVertex
{
    // Points to next vertex in polygon or -1
    // if we're at the end of the polygon.
    int next;
    ...
};

struct Polygon
{
     // Points to first vertex in polygon.
    int first_vertex;
    ...
};

struct Mesh
{
    // Stores all the face vertices for all polygons.
    std::vector<FaceVertex> fvs;

    // Stores all the polygons.
    std::vector<Polygon> polys;
};

... फिर हमने नाटकीय रूप से ढेर आवंटन और कैश मिस की संख्या को कम कर दिया है। हमारे द्वारा उपयोग किए जाने वाले हर एक बहुभुज के लिए ढेर आवंटन और संभावित अनिवार्य कैश की आवश्यकता के बजाय, हमें अब केवल उस ही आवंटन की आवश्यकता है जब पूरे जाल में संग्रहीत दो वैक्टर में से एक उनकी क्षमता (एक परिशोधित लागत) से अधिक हो। जबकि स्ट्राइड एक वर्टेक्स से अगले तक जाने के लिए अभी भी कैश मिक्स के अपने हिस्से का कारण हो सकता है, यह अभी भी अक्सर की तुलना में कम है अगर प्रत्येक एकल बहुभुज एक अलग डायनामिक सरणी संग्रहीत करता है क्योंकि नोड्स को आकस्मिक रूप से संग्रहीत किया जाता है और एक संभावना है कि एक पड़ोसी वर्टेक्स हो सकता है बेदखली से पहले पहुँचा जा सकता है (विशेष रूप से यह देखते हुए कि कई बहुभुज एक ही बार में अपने वर्टीकल जोड़ देंगे जो कि बहुभुज के सिंह के हिस्से को पूरी तरह से सन्निहित बनाता है)।

यहाँ एक और उदाहरण है:

यहां छवि विवरण दर्ज करें

... जहां ग्रिड कोशिकाओं का उपयोग कण-कण की टक्कर को तेज करने के लिए किया जाता है, कहते हैं, 16 मिलियन कण हर एक फ्रेम को आगे बढ़ाते हैं। उस कण ग्रिड उदाहरण में, लिंक की गई सूचियों का उपयोग करके हम केवल 3 सूचकांकों को बदलकर एक कण को ​​एक ग्रिड कोशिका से दूसरे में स्थानांतरित कर सकते हैं। एक वेक्टर से मिटाकर और दूसरे को पीछे धकेलना काफी महंगा हो सकता है और अधिक ढेर आवंटन पेश कर सकता है। लिंक की गई सूचियाँ किसी सेल की मेमोरी को 32-बिट तक कम कर देती हैं। कार्यान्वयन के आधार पर एक वेक्टर, अपने गतिशील सरणी को उस बिंदु तक प्रचारित कर सकता है जहां यह एक खाली वेक्टर के लिए 32 बाइट्स ले सकता है। यदि हमारे पास लगभग एक लाख ग्रिड सेल हैं, तो यह काफी अंतर है।

... और यह वह जगह है जहाँ मुझे इन दिनों सबसे अधिक उपयोगी लिस्ट मिली हुई है, और मैं विशेष रूप से "इंडिकेटेड लिस्टेड लिस्ट" किस्म को उपयोगी मानता हूँ क्योंकि 32-बिट इंडेक्स 64-बिट मशीनों पर लिंक की मेमोरी आवश्यकताओं को आधा कर देता है और वे इसका मतलब है कि नोड्स को एक सरणी में आकस्मिक रूप से संग्रहीत किया जाता है।

अक्सर मैं उन्हें अनुक्रमित मुफ्त सूचियों के साथ जोड़ देता हूं ताकि कहीं भी निरंतर-समय निष्कासन और सम्मिलन की अनुमति मिल सके:

यहां छवि विवरण दर्ज करें

उस स्थिति में, nextइंडेक्स या तो अगले फ्री इंडेक्स की ओर इशारा करता है यदि नोड हटा दिया गया है या अगला उपयोग किया गया इंडेक्स यदि नोड को हटाया नहीं गया है।

और यह नंबर एक उपयोग मामला है जो मुझे इन दिनों लिंक की गई सूचियों के लिए मिला है। जब हम एक लाख परिवर्तनीय-लंबाई वाले उप-क्रमों को संग्रहीत करना, कहना चाहते हैं, तो 4 तत्व प्रत्येक, (लेकिन कभी-कभी तत्वों को हटा दिया जाता है और इन उप-अनुक्रमों में से एक में जोड़ा जाता है), लिंक की गई सूची हमें 4 मिलियन संग्रहीत करने की अनुमति देती है लिंक की गई सूची 1 मिलियन कंटेनरों के बजाय संक्रामक रूप से है जो प्रत्येक व्यक्तिगत रूप से ढेर-आबंटित हैं: एक विशाल वेक्टर, यानी एक मिलियन लोग नहीं हैं।


0

मैंने पूर्व में C / C ++ एप्लिकेशन में लिंक की गई सूचियों (यहां तक ​​कि दोगुनी लिंक्ड सूचियों) का उपयोग किया है। यह .NET से पहले था और यहां तक ​​कि stl भी था।

मैं शायद अभी एक .NET भाषा में लिंक की गई सूची का उपयोग नहीं करूंगा क्योंकि आपके लिए आवश्यक सभी ट्रैवर्सल कोड Linq एक्सटेंशन विधियों के माध्यम से आपके लिए प्रदान किए गए हैं।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.