वास्तव में एसटीएल में एक धोखा क्या है?


192

(यानी डेटा संरचना करते थे) मैं एसटीएल कंटेनर देख रहा था और यह आंकड़ा करने की कोशिश कर वे वास्तव में क्या कर रहे हैं, और Deque मुझे रोक लिया: मैं पहली बार में सोचा था कि यह एक डबल लिंक्ड सूची, जिसमें दोनों सिरों से प्रविष्टि और विलोपन की अनुमति होगी था निरंतर समय, लेकिन मैं ऑपरेटर द्वारा किए गए वादे से परेशान हूं [] निरंतर समय में किया जाना चाहिए। एक लिंक की गई सूची में, मनमाना एक्सेस O (n) होना चाहिए, है ना?

और अगर यह एक गतिशील सरणी है, तो यह निरंतर समय में तत्वों को कैसे जोड़ सकता है ? यह उल्लेख किया जाना चाहिए कि वास्तविककरण हो सकता है, और यह कि ओ (1) एक परिशोधन लागत है, जैसे कि एक वेक्टर के लिए

तो मुझे आश्चर्य है कि यह संरचना क्या है जो निरंतर समय में मनमानी पहुंच की अनुमति देती है, और एक ही समय में एक नए बड़े स्थान पर स्थानांतरित करने की आवश्यकता नहीं है।



1
@Graham "dequeue" "deque" का दूसरा सामान्य नाम है। मैंने अभी भी संपादन को मंजूरी दे दी है क्योंकि "डिक्की" आमतौर पर विहित नाम है।
कोनराड रुडोल्फ

@ कोनराड धन्यवाद। प्रश्न विशेष रूप से C ++ STL deque के बारे में था, जो छोटी वर्तनी का उपयोग करता है।
ग्राहम बोरलैंड

2
dequeडबल एंडेड कतार के लिए खड़ा है , हालांकि स्पष्ट रूप से मध्य तत्वों के लिए ओ (1) पहुंच की सख्त आवश्यकता सी ++ के लिए विशेष रूप से है
मैथ्यू एम।

जवाबों:


181

एक Deque कुछ हद तक रिकर्सिवली परिभाषित किया गया है: आंतरिक रूप से इसके बारे में एक डबल समाप्त हो गया कतार का कहना मात्रा तय आकार की। प्रत्येक हिस्सा एक सदिश राशि है और चूजों की कतार ("ग्राफिक" नीचे के ग्राफिक में) भी एक सदिश राशि है।

एक छल की स्मृति लेआउट की योजनाबद्ध

प्रदर्शन विशेषताओं का एक शानदार विश्लेषण है और यह कोडप्रोजेक्टvector पर कैसे तुलना करता है ।

जीसीसी मानक पुस्तकालय कार्यान्वयन आंतरिक रूप T**से नक्शे का प्रतिनिधित्व करने के लिए उपयोग करता है । प्रत्येक डेटा ब्लॉक एक ऐसा T*होता है जिसे कुछ निश्चित आकार __deque_buf_size(जो निर्भर करता है sizeof(T)) के साथ आवंटित किया जाता है ।


27
यह एक छल की परिभाषा है जैसा कि मैंने इसे सीखा है, लेकिन इस तरह, यह निरंतर समय की गारंटी नहीं दे सकता है, इसलिए कुछ गायब होना चाहिए।
स्टेफानव

14
@stefaanv, @Konrad: C ++ कार्यान्वयन मैंने देखा है कि पॉइंटर्स की एक सरणी का उपयोग निश्चित आकार के सरणियों में किया जाता है। इसका प्रभावी रूप से मतलब है कि push_front और push_back वास्तव में निरंतर-बार नहीं हैं, लेकिन स्मार्ट बढ़ते कारकों के साथ, आप अभी भी लगातार बार बार परिशोधन करते हैं, इसलिए O (1) इतना गलत नहीं है, और व्यवहार में यह वेक्टर की तुलना में तेज़ है क्योंकि आप स्वैप कर रहे हैं पूरे ऑब्जेक्ट्स के बजाय सिंगल पॉइंटर्स (और ऑब्जेक्ट्स की तुलना में कम पॉइंटर्स)।
मैथ्यू एम।

5
निरंतर-समय पर पहुंच अभी भी संभव है। बस, यदि आपको सामने एक नया ब्लॉक आवंटित करने की आवश्यकता है, तो मुख्य वेक्टर पर एक नया पॉइंटर वापस धक्का दें और सभी पॉइंटर्स को शिफ्ट करें।
Xeo

4
यदि नक्शा (कतार खुद) एक डबल-एंडेड सूची थी, तो मैं यह नहीं देखता कि यह ओ (1) यादृच्छिक उपयोग की अनुमति कैसे दे सकता है। इसे एक परिपत्र बफर के रूप में लागू किया जा सकता है, जो परिपत्र बफर को अधिक कुशल बनाने की अनुमति देगा: केवल कतार में सभी तत्वों के बजाय बिंदुओं को कॉपी करें। फिर भी ऐसा लगता है कि एक छोटा सा लाभ है।
13

14
@JeremyWest क्यों नहीं? अनुक्रमित पहुंच i / B-th ब्लॉक (B = ब्लॉक आकार) में i% B-th तत्व पर जाती है, यह स्पष्ट रूप से O (1) है। आप amortized O (1) में एक नया ब्लॉक जोड़ सकते हैं, इसलिए अंत में amortized O (1) तत्वों को जोड़ना है। शुरुआत में एक नया तत्व जोड़ना O (1) है जब तक कि एक नए ब्लॉक को जोड़ने की आवश्यकता नहीं होती है। शुरुआत में एक नया ब्लॉक जोड़ना O (1) नहीं है, सच है, यह O (N) है लेकिन वास्तव में इसका एक बहुत छोटा स्थिर कारक है क्योंकि आपको केवल N / B पॉइंटर्स को N तत्वों के बजाय स्थानांतरित करने की आवश्यकता है।
कोनराड रुडोल्फ

22

इसे वैक्टर के वेक्टर के रूप में कल्पना करें। केवल वे मानक नहीं हैं std::vector

बाहरी वेक्टर में आंतरिक वैक्टर के संकेत होते हैं। जब इसकी क्षमता को वास्तविक स्थान के माध्यम से बदल दिया जाता है, तो खाली स्थान को सभी के रूप में अंत में आवंटित करने के बजाय std::vector, यह खाली स्थान को सदिश की शुरुआत और अंत में बराबर भागों में विभाजित करता है। यह अनुमति देता है push_frontऔर push_backइस वेक्टर पर दोनों परिशोधित ओ (1) समय में होते हैं।

आंतरिक वेक्टर व्यवहार को इस बात पर निर्भर करने की आवश्यकता है कि यह आगे या पीछे है या नहीं deque। पीछे यह एक मानक के रूप में व्यवहार कर सकता है std::vectorजहां यह अंत में बढ़ता है, और push_backओ (1) समय में होता है। सामने वाले को इसके विपरीत करने की आवश्यकता है, प्रत्येक के साथ शुरुआत में बढ़ रहा है push_front। व्यवहार में यह आसानी से सामने के तत्व और आकार के साथ विकास की दिशा में एक संकेतक जोड़कर प्राप्त किया जाता है। इसके साथ साधारण संशोधन push_frontभी O (1) समय हो सकता है।

किसी भी तत्व तक पहुंचने के लिए ओट (1) में होने वाले उचित बाहरी वेक्टर इंडेक्स को ऑफसेट करने और विभाजित करने की आवश्यकता होती है, और आंतरिक वेक्टर में अनुक्रमण होता है जो ओ (1) भी है। यह मानता है कि आंतरिक वैक्टर सभी निश्चित आकार के होते हैं, सिवाय शुरुआत या अंत के deque


1
आप निर्धारित क्षमता के
Caleth

18

deque = डबल एंडेड कतार

एक कंटेनर जो दोनों दिशा में बढ़ सकता है।

Deque है आम तौर पर एक के रूप में लागू vectorकी vectors(वैक्टर की एक सूची निरंतर समय रैंडम एक्सेस नहीं दे सकता)। जबकि माध्यमिक वैक्टर का आकार निर्भरता पर लागू होता है, एक सामान्य एल्गोरिथ्म बाइट्स में एक निरंतर आकार का उपयोग करना है।


6
आंतरिक रूप से इसका वैक्टर नहीं है । आंतरिक संरचनाओं को शुरुआत में और साथ ही अंत में अप्रयुक्त क्षमता भी आवंटित की जा सकती है
मूइंग डक

@MingDuck: इसे लागू करना वास्तव में परिभाषित किया गया है। यह एरे का एक सरणी या वेक्टर या कुछ भी हो सकता है जो मानक द्वारा व्यवहार और जटिलता को प्रदान कर सकता है।
आलोक सेव

1
@ एल्स: मुझे नहीं लगता कि arrayकुछ भी या vectorकुछ भी amortized O(1)push_front का वादा कर सकता है । कम से कम दो संरचनाओं के आंतरिक, एक होने में सक्षम होना चाहिएO(1) push_front , जो न तो गारंटी दे सकता है arrayऔर न ही vectorकर सकता है।
मूविंग डक

4
@MingDuck उस आवश्यकता को आसानी से पूरा करता है, यदि पहला चंक नीचे-ऊपर की बजाय ऊपर-नीचे बढ़ता है। जाहिर है कि एक मानक vectorऐसा नहीं करता है, लेकिन यह ऐसा करने के लिए एक सरल पर्याप्त संशोधन है।
मार्क रैनसम

3
@ मूइंग डक, दोनों पुश_फ्रंट और पुश_बैक को एकल वेक्टर संरचना के साथ परिमित ओ (1) में आसानी से किया जा सकता है। यह सर्कुलर बफर का सिर्फ थोड़ा सा बहीखाता है, इससे ज्यादा कुछ नहीं। मान लें कि आपके पास 0 से 99 के पदों पर 100 तत्वों के साथ क्षमता 1000 का एक नियमित वेक्टर है। अब जब एक push_Front होता है तो आप अंत में धक्का देते हैं अर्थात स्थिति 999, फिर 998 आदि जब तक दोनों छोर मिलते नहीं हैं। फिर आप पुनः लोड करें (जैसे घातीय वृद्धि के साथ लगातार समय की गारंटी देने के लिए) जैसे आप एक साधारण वेक्टर के साथ करेंगे। तो प्रभावी रूप से आपको केवल पहले एक अतिरिक्त सूचक की आवश्यकता है।
plamenko

14

(यह एक उत्तर है जिसे मैंने एक और सूत्र में दिया है । मूलतः मैं तर्क दे रहा हूं कि काफी भोले-भाले कार्यान्वयन भी vector, एकल का उपयोग करते हुए , "निरंतर गैर-परिशोधन धक्का_ {सामने, पीछे}" की आवश्यकताओं के अनुरूप। आप आश्चर्यचकित हो सकते हैं। , और लगता है कि यह असंभव है, लेकिन मैंने मानक में अन्य प्रासंगिक उद्धरण पाए हैं जो संदर्भ को आश्चर्यजनक तरीके से परिभाषित करते हैं। कृपया मेरे साथ रहें; यदि मैंने इस उत्तर में कोई गलती की है, तो किन चीजों की पहचान करना बहुत मददगार होगा। मैंने सही कहा है और जहां मेरा तर्क टूट गया है।)

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

deque<T>का उपयोग करके सही ढंग से लागू किया जा सकता है vector<T*>। सभी तत्वों को एक वेक्टर में संग्रहीत ढेर और बिंदुओं पर कॉपी किया जाता है। (वेक्टर पर बाद में अधिक)।

T*इसके बजाय क्यों T? क्योंकि मानक के लिए यह आवश्यक है

"हिरण के किसी भी छोर पर एक सम्मिलन सभी पुनरावृत्तियों को मृग को अमान्य कर देता है, लेकिन मृग के तत्वों के संदर्भ की वैधता पर कोई प्रभाव नहीं पड़ता है। "

(मेरा जोर)। उस T*को संतुष्ट करने में मदद करता है। यह हमें इसे संतुष्ट करने में भी मदद करता है:

"किसी भी तत्व को या तो शुरुआत या अंत में हमेशा एक छल के साथ ..... टी के एक निर्माता को एक ही कॉल का कारण बनता है ।"

अब (विवादास्पद) बिट के लिए। क्यों vectorस्टोर करने के लिए एक का उपयोग करें T*? यह हमें रैंडम एक्सेस देता है, जो एक अच्छी शुरुआत है। आइए एक पल के लिए वेक्टर की जटिलता के बारे में भूल जाएं और इसे सावधानीपूर्वक बनाएं:

मानक "निहित वस्तुओं पर संचालन की संख्या" के बारे में बात करता है। के लियेdeque::push_front स्पष्ट रूप से 1 है क्योंकि वास्तव में एक Tवस्तु का निर्माण किया जाता है और मौजूदा Tवस्तुओं के शून्य को किसी भी तरह से पढ़ा या स्कैन किया जाता है। यह संख्या, 1, स्पष्ट रूप से एक स्थिर है और वर्तमान में छल में मौजूद वस्तुओं की संख्या से स्वतंत्र है। यह हमें यह कहने की अनुमति देता है कि:

'हमारे लिए deque::push_front , निहित वस्तुओं (टीएस) पर संचालन की संख्या निर्धारित है और पहले से ही छल में वस्तुओं की संख्या से स्वतंत्र है।'

बेशक, T*वसीयत पर संचालन की संख्या इतनी अच्छी तरह से व्यवहार नहीं की जाएगी। जब vector<T*>बहुत बड़ा हो जाता है, तो यह फिर से हो जाएगा और कई T*एस की नकल की जाएगी। हां, संचालन की संख्याT* वसीयत बेतहाशा बदलाव होगा, लेकिन संचालन की संख्या Tप्रभावित नहीं होगी।

क्यों हम इस पर भेद करने के बारे में Tऔर ऑपरेशन की गिनती पर आपरेशन के बीच भेद के बारे में परवाह हैT* ? ऐसा इसलिए है क्योंकि मानक कहते हैं:

इस खंड में सभी जटिलता आवश्यकताएं केवल निहित वस्तुओं पर संचालन की संख्या के संदर्भ में बताई गई हैं।

के लिए deque, निहित वस्तुओं रहे हैं T, नहींT* , जिसका अर्थ है कि हम किसी भी आपरेशन अनदेखा कर सकते हैं जो प्रतियां (या reallocs) एक T*

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

पिछले कुछ पैराग्राफों में, हमने विश्लेषण किया है deque::push_frontऔर पहले से ही छल में वस्तुओं की संख्या और निहित- Tविशेषण पर push_front द्वारा किए गए संचालन की संख्या के बीच संबंध है । और हमने पाया कि वे एक दूसरे से स्वतंत्र थे। जैसा कि मानक कहते हैं कि जटिलता संचालन के संदर्भ में है- T, तो हम कह सकते हैं कि इसमें निरंतर जटिलता है।

हां, ऑपरेशंस-ऑन-टी * -Complexity amortized है (इसके कारण vector), लेकिन हम केवल इसमें रुचि रखते हैं -ऑन-टी-कॉम्प्लेक्सिटी और यह निरंतर (गैर-एमॉर्टाइज़्ड) है।

वेक्टर की जटिलता :: push_back या वेक्टर :: push_front इस कार्यान्वयन में अप्रासंगिक है; उन विचारों में परिचालन शामिल है T*और इसलिए अप्रासंगिक हैं। यदि मानक जटिलता की 'पारंपरिक' सैद्धांतिक धारणा की बात कर रहा था, तो उन्होंने स्पष्ट रूप से खुद को "निहित वस्तुओं पर संचालन की संख्या" तक सीमित नहीं किया होगा। क्या मैं उस वाक्य की व्याख्या कर रहा हूं?


8
यह मुझे धोखा देने जैसा लगता है! जब आप किसी ऑपरेशन की जटिलता को निर्दिष्ट करते हैं, तो आप इसे केवल डेटा के कुछ हिस्से पर नहीं करते हैं: आप जिस ऑपरेशन को कॉल कर रहे हैं, उसके अपेक्षित रनटाइम का अंदाजा लगाना चाहते हैं, चाहे वह इस पर काम करता हो। यदि मैं T पर परिचालनों के बारे में आपके तर्क का पालन करता हूं, तो इसका मतलब यह होगा कि आप जाँच सकते हैं कि क्या प्रत्येक T का मान एक प्राइम नंबर है जो हर बार ऑपरेशन किया जाता है और फिर भी मानक का सम्मान करता है क्योंकि आप Ts को नहीं छूते हैं। क्या आप बता सकते हैं कि आपके उद्धरण कहाँ से आए हैं?
ज़ोनको

2
मुझे लगता है कि मानक लेखक जानते हैं कि वे पारंपरिक जटिलता सिद्धांत का उपयोग नहीं कर सकते क्योंकि हमारे पास पूरी तरह से निर्दिष्ट प्रणाली नहीं है जहां हम जानते हैं, उदाहरण के लिए, स्मृति आवंटन की जटिलता। यह ढोंग करना यथार्थवादी नहीं है कि listसूची के वर्तमान आकार की परवाह किए बिना एक नए सदस्य के लिए स्मृति आवंटित की जा सकती है ; यदि सूची बहुत बड़ी है, तो आवंटन धीमा होगा या विफल हो जाएगा। इसलिए, जहां तक ​​मैं देख सकता हूं, समिति ने केवल उन कार्यों को निर्दिष्ट करने का निर्णय लिया, जिन्हें उद्देश्यपूर्वक गिना और मापा जा सकता है। (पुनश्च: मेरे पास इस बारे में एक और उत्तर के लिए एक और सिद्धांत है।)
हारून मैकडैड

मुझे पूरा यकीन O(n)है कि ऑपरेशन की संख्या तत्वों की संख्या के समानुपातिक रूप से आनुपातिक है। IE, मेटा-ऑपरेशंस की गणना। अन्यथा लुकअप को सीमित करने का कोई मतलब नहीं होगा O(1)। एर्गो, लिंक-लिस्ट योग्य नहीं हैं।
मूकिंग डक

8
यह एक बहुत ही रोचक व्याख्या है, लेकिन इस तर्क के द्वारा listबिंदुओं के रूप में भी लागू किया जा सकता है vector(बीच में सम्मिलन के परिणामस्वरूप एकल प्रतिलिपि निर्माता मंगलाचरण होगा, सूची आकार की परवाह किए बिना, और O(N)बिंदुओं के फेरबदल को अनदेखा किया जा सकता है क्योंकि) वे ऑपरेशन-ऑन-टी) नहीं हैं।
मँकारसे

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

13

अवलोकन से, आप dequeएक के रूप में सोच सकते हैंdouble-ended queue

छल अवलोकन

में डेटा deque तय आकार वेक्टर के chuncks द्वारा जमा हो जाती है, जो कर रहे हैं

द्वारा इंगित map(जो वेक्टर का एक हिस्सा भी है, लेकिन इसका आकार बदल सकता है)

आंतरिक संरचना

का मुख्य भाग कोड इस प्रकार deque iteratorहै:

/*
buff_size is the length of the chunk
*/
template <class T, size_t buff_size>
struct __deque_iterator{
    typedef __deque_iterator<T, buff_size>              iterator;
    typedef T**                                         map_pointer;

    // pointer to the chunk
    T* cur;       
    T* first;     // the begin of the chunk
    T* last;      // the end of the chunk

    //because the pointer may skip to other chunk
    //so this pointer to the map
    map_pointer node;    // pointer to the map
}

का मुख्य भाग कोड इस प्रकार dequeहै:

/*
buff_size is the length of the chunk
*/
template<typename T, size_t buff_size = 0>
class deque{
    public:
        typedef T              value_type;
        typedef T&            reference;
        typedef T*            pointer;
        typedef __deque_iterator<T, buff_size> iterator;

        typedef size_t        size_type;
        typedef ptrdiff_t     difference_type;

    protected:
        typedef pointer*      map_pointer;

        // allocate memory for the chunk 
        typedef allocator<value_type> dataAllocator;

        // allocate memory for map 
        typedef allocator<pointer>    mapAllocator;

    private:
        //data members

        iterator start;
        iterator finish;

        map_pointer map;
        size_type   map_size;
}

नीचे मैं आपको dequeमुख्य रूप से तीन भागों के बारे में बताऊंगा:

  1. इटरेटर

  2. निर्माण कैसे करें deque

1. पुनरावृति ( __deque_iterator)

इटरेटर की मुख्य समस्या है, जब ++, - इटरेटर, यह अन्य चंक को छोड़ सकता है (यदि यह पॉइंटर को किनारे करने के लिए है)। उदाहरण के लिए, वहाँ तीन डेटा हिस्सा हैं: chunk 1, chunk 2, chunk 3

pointer1की ओर इशारा की शुरू chunk 2, जब ऑपरेटर --pointerइसका अंत सूचक होगा chunk 1ताकि के रूप में, pointer2

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

नीचे मैं मुख्य कार्य दूंगा __deque_iterator:

सबसे पहले, किसी भी व्यक्ति को छोड़ें:

void set_node(map_pointer new_node){
    node = new_node;
    first = *new_node;
    last = first + chunk_size();
}

ध्यान दें, कि chunk_size() फ़ंक्शन जो चंक आकार की गणना करता है, आप सोच सकते हैं कि यह यहां सरल बनाने के लिए 8 रिटर्न देता है।

operator* चंक में डेटा प्राप्त करें

reference operator*()const{
    return *cur;
}

operator++, --

// वृद्धिशील रूप

self& operator++(){
    ++cur;
    if (cur == last){      //if it reach the end of the chunk
        set_node(node + 1);//skip to the next chunk
        cur = first;
    }
    return *this;
}

// postfix forms of increment
self operator++(int){
    self tmp = *this;
    ++*this;//invoke prefix ++
    return tmp;
}
self& operator--(){
    if(cur == first){      // if it pointer to the begin of the chunk
        set_node(node - 1);//skip to the prev chunk
        cur = last;
    }
    --cur;
    return *this;
}

self operator--(int){
    self tmp = *this;
    --*this;
    return tmp;
}
itter स्किप n स्टेप्स / रैंडम एक्सेस
self& operator+=(difference_type n){ // n can be postive or negative
    difference_type offset = n + (cur - first);
    if(offset >=0 && offset < difference_type(buffer_size())){
        // in the same chunk
        cur += n;
    }else{//not in the same chunk
        difference_type node_offset;
        if (offset > 0){
            node_offset = offset / difference_type(chunk_size());
        }else{
            node_offset = -((-offset - 1) / difference_type(chunk_size())) - 1 ;
        }
        // skip to the new chunk
        set_node(node + node_offset);
        // set new cur
        cur = first + (offset - node_offset * chunk_size());
    }

    return *this;
}

// skip n steps
self operator+(difference_type n)const{
    self tmp = *this;
    return tmp+= n; //reuse  operator +=
}

self& operator-=(difference_type n){
    return *this += -n; //reuse operator +=
}

self operator-(difference_type n)const{
    self tmp = *this;
    return tmp -= n; //reuse operator +=
}

// random access (iterator can skip n steps)
// invoke operator + ,operator *
reference operator[](difference_type n)const{
    return *(*this + n);
}

2. निर्माण कैसे करें deque

का सामान्य कार्य deque

iterator begin(){return start;}
iterator end(){return finish;}

reference front(){
    //invoke __deque_iterator operator*
    // return start's member *cur
    return *start;
}

reference back(){
    // cna't use *finish
    iterator tmp = finish;
    --tmp; 
    return *tmp; //return finish's  *cur
}

reference operator[](size_type n){
    //random access, use __deque_iterator operator[]
    return start[n];
}


template<typename T, size_t buff_size>
deque<T, buff_size>::deque(size_t n, const value_type& value){
    fill_initialize(n, value);
}

template<typename T, size_t buff_size>
void deque<T, buff_size>::fill_initialize(size_t n, const value_type& value){
    // allocate memory for map and chunk
    // initialize pointer
    create_map_and_nodes(n);

    // initialize value for the chunks
    for (map_pointer cur = start.node; cur < finish.node; ++cur) {
        initialized_fill_n(*cur, chunk_size(), value);
    }

    // the end chunk may have space node, which don't need have initialize value
    initialized_fill_n(finish.first, finish.cur - finish.first, value);
}

template<typename T, size_t buff_size>
void deque<T, buff_size>::create_map_and_nodes(size_t num_elements){
    // the needed map node = (elements nums / chunk length) + 1
    size_type num_nodes = num_elements / chunk_size() + 1;

    // map node num。min num is  8 ,max num is "needed size + 2"
    map_size = std::max(8, num_nodes + 2);
    // allocate map array
    map = mapAllocator::allocate(map_size);

    // tmp_start,tmp_finish poniters to the center range of map
    map_pointer tmp_start  = map + (map_size - num_nodes) / 2;
    map_pointer tmp_finish = tmp_start + num_nodes - 1;

    // allocate memory for the chunk pointered by map node
    for (map_pointer cur = tmp_start; cur <= tmp_finish; ++cur) {
        *cur = dataAllocator::allocate(chunk_size());
    }

    // set start and end iterator
    start.set_node(tmp_start);
    start.cur = start.first;

    finish.set_node(tmp_finish);
    finish.cur = finish.first + num_elements % chunk_size();
}

मान लेते हैं i_dequeकि 20 इंट तत्व हैं 0~19जिनके चंक का आकार 8 है, और अब push_back 3 तत्व (0, 1, 2) i_deque:

i_deque.push_back(0);
i_deque.push_back(1);
i_deque.push_back(2);

यह नीचे की तरह आंतरिक संरचना है:

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

फिर push_back फिर से, यह आह्वान करेगा नया हिस्सा आवंटित:

push_back(3)

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

यदि हम push_front, यह प्रचलित से पहले नया हिस्सा आवंटित करेंगेstart

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

ध्यान दें कि जब push_backतत्व डीके में है, यदि सभी नक्शे और विखंडन भरे हुए हैं, तो यह नए नक्शे को आवंटित करेगा, और विखंडू को समायोजित करेगा। लेकिन उपरोक्त कोड आपको समझने के लिए पर्याप्त हो सकता है deque


आपने उल्लेख किया है, "ध्यान दें कि जब पुश_बैक एलिमेंट डॉक में है, यदि सभी मैप्स और चंक्स भरे हुए हैं, तो यह नए मैप को आवंटित करेगा, और चंक्स को समायोजित करेगा"। मुझे आश्चर्य है कि C ++ मानक क्यों कहता है "[26.3.8.4.3] N4713 में एक ही तत्व की शुरुआत या अंत में हमेशा एक हिरण हमेशा एक ही समय लेता है"। डेटा की एक चक को आवंटित करने में निरंतर समय से अधिक समय लगता है। नहीं?
एचसीएसएफ

7

मैं एडम Drozdek द्वारा "C ++ में डेटा संरचना और एल्गोरिदम" पढ़ रहा था, और इसे उपयोगी पाया। HTH।

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

आप देख सकते हैं कि मध्य बिंदु डेटा की ओर संकेत करता है (दाईं ओर स्थित), और आप यह भी देख सकते हैं कि बीच का सरणी गतिशील रूप से बदल रहा है।

एक छवि एक हजार शब्दों के लायक है।

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


1
एक पुस्तक का संदर्भ देने के लिए धन्यवाद। मैंने dequeभाग पढ़ा और यह काफी अच्छा है।
रिक

@ सुनकर खुशी हुई। मुझे याद है कि मैं किसी समय डिक्की में खुदाई कर रहा था क्योंकि मुझे समझ नहीं आ रहा था कि आप ओ (1) में रैंडम एक्सेस ([] ऑपरेटर) कैसे ले सकते हैं। यह भी साबित करना कि (पुश / पॉप) _ (पीछे / सामने) ने O (1) जटिलता को परिमित किया है, एक दिलचस्प 'आह क्षण' है।
केलू

6

हालांकि मानक किसी विशेष कार्यान्वयन (केवल निरंतर-समय रैंडम एक्सेस) को अनिवार्य नहीं करता है, एक छलावा आमतौर पर सन्निहित स्मृति "पृष्ठों" के संग्रह के रूप में लागू किया जाता है। नए पृष्ठ आवश्यकतानुसार आवंटित किए जाते हैं, लेकिन आपके पास अभी भी यादृच्छिक पहुँच है। इसके विपरीत std::vector, आपको यह वादा नहीं किया जाता है कि डेटा को आकस्मिक रूप से संग्रहीत किया जाता है, लेकिन वेक्टर की तरह, बीच में सम्मिलन के लिए बहुत सारे स्थानांतरण की आवश्यकता होती है।


4
या बीच में विलोपन के लिए बहुत सारे स्थानांतरण की आवश्यकता होती है
मार्क हेंड्रिकसन

यदि insertबहुत सारे स्थानांतरण की आवश्यकता होती है तो प्रयोग 4 यहां कैसे और किसके बीच का अंतर दिखाता है ? vector::insert()deque::insert()
तुला

1
@ तुला: शायद विवरणों के गलत संचार के कारण? Deque डालने की जटिलता "सम्मिलित किए गए तत्वों की संख्या में रैखिक है और deque की शुरुआत और अंत में दूरी की कम होती है।" इस लागत को महसूस करने के लिए, आपको वर्तमान मध्य में सम्मिलित करना होगा; यह कि आपका बेंचमार्क क्या कर रहा है?
केरेक एसबी ऑक्ट

@KerrekSB: बेंचमार्क वाले लेख को कोनराड उत्तर में संदर्भित किया गया था। वास्तव में मैंने नीचे लेख के टिप्पणी अनुभाग पर ध्यान नहीं दिया। थ्रेड में 'लेकिन डीके में रैखिक सम्मिलन का समय है?' लेखक ने उल्लेख किया कि उन्होंने सभी परीक्षणों के माध्यम से स्थिति 100 पर सम्मिलन का उपयोग किया, जिससे परिणाम थोड़ा अधिक समझ में आता है।
तुला
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.