Std :: list :: रिवर्स में O (n) जटिलता क्यों है?


192

std::listC ++ मानक लाइब्रेरी में क्लास के लिए रिवर्स फ़ंक्शन में रैखिक रनटाइम क्यों होता है? मुझे लगता है कि डबल-लिंक्ड सूचियों के लिए रिवर्स फ़ंक्शन ओ (1) होना चाहिए था।

एक डबल-लिंक्ड सूची को उलट कर सिर्फ सिर और पूंछ के बिंदुओं को बदलना शामिल होना चाहिए।


18
मुझे समझ नहीं आता कि लोग इस सवाल को क्यों दरकिनार कर रहे हैं। यह एक पूरी तरह से उचित सवाल है। एक डबल लिंक की गई सूची को उलट कर O (1) समय लेना चाहिए।
उत्सुक

46
दुर्भाग्य से, कुछ लोग "प्रश्न अच्छा है" की अवधारणाओं को भ्रमित करते हैं, "प्रश्न का एक अच्छा विचार है"। मुझे इस तरह के सवाल पसंद हैं, जहां मूल रूप से "मेरी समझ आमतौर पर स्वीकृत अभ्यास से अलग लगती है, कृपया इस संघर्ष को हल करने में मदद करें", क्योंकि आप जो सोचते हैं उसका विस्तार करते हुए सड़क के नीचे और भी कई समस्याओं को हल करने में आपकी मदद करता है! ऐसा लगता है कि अन्य लोग "99.9999% मामलों में प्रसंस्करण की बर्बादी" के बारे में सोचते हैं, इसके बारे में भी नहीं सोचते हैं। यदि यह कोई सांत्वना है, तो मुझे बहुत कम, बहुत कम के लिए नीचा दिखाया गया है!
corsiKa

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

3
या मुझे आपके साथ प्रमाण का बोझ डालने दें, @Curious: मैंने यहां एक दोहरी लिंक की गई सूची कार्यान्वयन को व्हिप किया है: ideone.com/c1HebO । क्या आप इंगित कर सकते हैं कि आप Reverseओ (1) में फ़ंक्शन को लागू करने की अपेक्षा कैसे करेंगे ?
10

2
@CompuChip: वास्तव में, कार्यान्वयन के आधार पर, यह नहीं हो सकता है। आपको यह जानने के लिए कि आपको किस सूचक का उपयोग करने के लिए एक अतिरिक्त बूलियन की आवश्यकता नहीं है: बस आपको वापस न इंगित करने वाले का उपयोग करें ... जो वैसे भी एक XOR'ed लिंक्ड सूची के साथ स्वचालित हो सकता है। तो हां, यह इस बात पर निर्भर करता है कि सूची को कैसे लागू किया गया है, और ओपी कथन को स्पष्ट किया जा सकता है।
मथिउ एम।

जवाबों:


187

परिकल्पित, reverseहो सकता था हे (1) । वहाँ (फिर से काल्पनिक रूप से) एक बूलियन सूची सदस्य हो सकता है जो यह दर्शाता है कि लिंक की गई सूची की दिशा वर्तमान में समान या विपरीत है जहां सूची बनाई गई थी।

दुर्भाग्य से, यह मूल रूप से किसी भी अन्य ऑपरेशन के प्रदर्शन को कम कर देगा (यद्यपि असेप्टिक रनटाइम को बदले बिना)। प्रत्येक ऑपरेशन में, एक बुलियन को एक लिंक के "अगला" या "प्राइम" पॉइंटर का पालन करने के लिए विचार करने के लिए परामर्श करने की आवश्यकता होगी।

चूँकि यह निश्चित रूप से एक अपेक्षाकृत असीम संचालन माना जाता था, मानक (जो कार्यान्वयन को निर्धारित नहीं करता है, केवल जटिलता), निर्दिष्ट करता है कि जटिलता रैखिक हो सकती है। यह "नेक्स्ट" पॉइंटर्स को हमेशा समान दिशा में समान रूप से सामान्य दिशा के संचालन को तेज करने की अनुमति देता है।


29
@MooseBoys: मैं आपकी उपमा से सहमत नहीं हूँ। अंतर यह है कि, एक सूची के मामले में, कार्यान्वयन इस बूलियन फ्लैग ट्रिक का उपयोग करके किसी अन्य ऑपरेशन के बिग-ओ को प्रभावित किए बिना जटिलता के reverseसाथ प्रदान कर सकता है । लेकिन, व्यवहार में प्रत्येक ऑपरेशन में एक अतिरिक्त शाखा महंगा है, भले ही यह तकनीकी रूप से ओ (1) हो। इसके विपरीत, आप एक सूची संरचना नहीं बना सकते हैं जिसमें O (1) हो और अन्य सभी ऑपरेशन समान लागत वाले हों। सवाल का मुद्दा यह है कि, प्रतीत होता है, आप मुफ्त में रिवर्स प्राप्त कर सकते हैं यदि आप केवल बड़े ओ की परवाह करते हैं, तो उन्होंने ऐसा क्यों नहीं किया। O(1)sortO(1)
क्रिस बेक

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

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

4
@ केविन: एचएम, क्या? आप सीधे वैसे भी दो संकेत XOR, आप (प्रकार की स्पष्ट रूप से पहले पूर्णांकों के लिए उन्हें बदलने की आवश्यकता नहीं कर सकते हैं std::uintptr_tतो फिर आप उन्हें XOR कर सकते हैं।
Deduplicator

3
@ केविन, आप निश्चित रूप से C ++ में XOR से जुड़ी सूची बना सकते हैं, यह व्यावहारिक रूप से इस तरह की चीज़ के लिए पोस्टर-चाइल्ड भाषा है। कुछ भी नहीं आप का उपयोग करने के लिए कहते हैं std::uintptr_t, आप एक charसरणी के लिए और फिर घटकों XOR कर सकते हैं। यह धीमी लेकिन 100% पोर्टेबल होगी। संभवत: आप इसे इन दो कार्यान्वयनों के बीच चयन कर सकते हैं और uintptr_tलापता होने पर केवल दूसरे का उपयोग कर सकते हैं। अगर कुछ इस उत्तर में वर्णित है: stackoverflow.com/questions/14243971/…
क्रिस बेक

61

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

current = current->next;

में operator++सूची इटरेटर की, आप मिलेगा

if (reversed)
  current = current->prev;
else
  current = current->next;

जो कुछ ऐसा नहीं है जिसे आप आसानी से जोड़ने का निर्णय लेंगे। यह देखते हुए कि सूचियों को आमतौर पर बहुत अधिक बार उलट दिया जाता है, क्योंकि यह इस तकनीक को अनिवार्य करने के लिए मानक के लिए बहुत नासमझी होगी । इसलिए, रिवर्स ऑपरेशन को रैखिक जटिलता की अनुमति है। टिप्पणी करते हैं, तथापि, कि टीहे (1) ⇒ टीहे ( एन ) ऐसा है, जैसा कि पहले उल्लेख के रूप में, अपने "अनुकूलन" को लागू करने तकनीकी रूप से अनुमति होगी।

यदि आप एक जावा या इसी तरह की पृष्ठभूमि से आते हैं, तो आप आश्चर्यचकित हो सकते हैं कि क्यों हर बार ध्वजवाहक को ध्वज की जांच करनी होती है। इसके बजाय क्या हमारे पास दो अलग-अलग इट्रेटर प्रकार नहीं हो सकते हैं, दोनों एक सामान्य आधार प्रकार से प्राप्त होते हैं, std::list::beginऔर std::list::rbeginपॉलीमॉर्फ़िक रूप से उपयुक्त इट्रेटर लौटाते हैं? जबकि संभव है, यह पूरी बात को और भी बदतर बना देगा क्योंकि इट्रेटर को आगे बढ़ाना अब अप्रत्यक्ष (हार्ड इनलाइन) फ़ंक्शन कॉल होगा। जावा में, आप इस मूल्य को वैसे भी नियमित रूप से भुगतान कर रहे हैं, लेकिन फिर से, यह एक कारण है कि कई लोग C ++ के लिए पहुंचते हैं जब प्रदर्शन महत्वपूर्ण होता है।

जैसा कि बेंजामिन लिंडले ने टिप्पणियों में बताया , चूंकि reverseपुनरावृत्तियों को अमान्य करने की अनुमति नहीं है, मानक द्वारा अनुमत एकमात्र दृष्टिकोण ऐसा लगता है कि पुनरावृत्त को पुनरावृत्त सूची के अंदर सूची में संग्रहीत किया जाता है जो दोहरे-अप्रत्यक्ष मेमोरी एक्सेस का कारण बनता है।


7
@galinette: पुनरावृत्तियों को std::list::reverseअमान्य नहीं करता है।
बेंजामिन लिंडले

1
@galinette क्षमा करें, मैंने आपके पहले की टिप्पणी को "ध्वज प्रति सूचक " के रूप में "ध्वज प्रति नोड " के विपरीत पढ़ा था जैसा आपने लिखा था। निश्चित रूप से, प्रति नोड एक ध्वज प्रति -उत्पादक होगा जैसा कि आप फिर से, उन सभी को फ्लिप करने के लिए एक रेखीय ट्रैवर्सल करना होगा।
5gon12eder

2
@ 5gon12eder: आप बहुत खोई हुई लागत पर शाखामिलन को समाप्त कर सकते हैं: एक सरणी में और बिंदुओं को संग्रहीत करें nextऔर prevदिशा को एक 0या एक के रूप में संग्रहीत करें 1। इसे आगे बढ़ाने के लिए, आप अनुसरण करेंगे pointers[direction]और रिवर्स pointers[1-direction](या इसके विपरीत) में पुनरावृति करेंगे । यह अभी भी एक छोटे से ओवरहेड जोड़ देगा, लेकिन शायद एक शाखा से कम है।
जेरी कॉफिन

4
आप शायद पुनरावृत्तियों को सूची के अंदर पॉइंटर स्टोर नहीं कर सकते। swap()निरंतर समय निर्दिष्ट किया जाता है और किसी भी पुनरावृत्तियों को अमान्य नहीं किया जाता है।
तवियन बार्न्स

1
@TavianBarnes लानत है! खैर, ट्रिपल अविवेक फिर ... (मेरा मतलब है, वास्तव में ट्रिपल नहीं आप इटरेटर में एक गतिशील आवंटित वस्तु में झंडा लेकिन सूचक स्टोर करने के लिए होगा कर सकते हैं पाठ्यक्रम बिंदु के सीधे कि बजाय सूची पर indirecting की वस्तु है।।)
5gon12eder

37

निश्चित रूप से चूंकि सभी कंटेनर जो द्विदिश पुनरावृत्तियों का समर्थन करते हैं, उनके पास रबगिन () और रेंडरिंग () की अवधारणा है, यह सवाल मूक है?

यह एक प्रॉक्सी बनाने के लिए तुच्छ है जो पुनरावृत्तियों को उलट देता है और कंटेनर को उसी के माध्यम से एक्सेस करता है।

यह गैर-ऑपरेशन वास्तव में ओ (1) है।

जैसे कि:

#include <iostream>
#include <list>
#include <string>
#include <iterator>

template<class Container>
struct reverse_proxy
{
    reverse_proxy(Container& c)
    : _c(c)
    {}

    auto begin() { return std::make_reverse_iterator(std::end(_c)); }
    auto end() { return std::make_reverse_iterator(std::begin(_c)); }

    auto begin() const { return std::make_reverse_iterator(std::end(_c)); }
    auto end() const { return std::make_reverse_iterator(std::begin(_c)); }

    Container& _c;
};

template<class Container>
auto reversed(Container& c)
{
    return reverse_proxy<Container>(c);
}

int main()
{
    using namespace std;
    list<string> l { "the", "cat", "sat", "on", "the", "mat" };

    auto r = reversed(l);
    copy(begin(r), end(r), ostream_iterator<string>(cout, "\n"));

    return 0;
}

अपेक्षित उत्पादन:

mat
the
on
sat
cat
the

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

बस मेरा 2 सी।


18

क्योंकि इसमें हर नोड ( nकुल) को पार करना होगा और अपने डेटा को अपडेट करना होगा (अपडेट स्टेप वास्तव में है O(1))। इससे पूरा ऑपरेशन हो जाता है O(n*1) = O(n)


27
क्योंकि आपको हर आइटम के लिंक को भी अपडेट करना होगा। कागज के एक टुकड़े को बाहर निकालें और उसे नीचे उतारने के बजाय बाहर निकालें।
ब्लाइंड

11
क्यों पूछते हैं कि क्या आप निश्चित हैं? आप हमारा समय बर्बाद कर रहे हैं।
अँधेरी

28
@Curious डाउली लिस्टेड नोड्स में दिशा का बोध होता है। वहाँ से कारण।
जूता

11
@Blindy एक अच्छा जवाब पूरा होना चाहिए। "कागज का एक टुकड़ा निकालें और इसे बाहर निकालें" इस प्रकार एक अच्छे उत्तर का एक आवश्यक घटक नहीं होना चाहिए। जो उत्तर अच्छे नहीं हैं, वे नीचे दिए गए हैं।
आरएम

7
@ शॉ: क्या उनके पास है? कृपया XOR-लिंक्ड-लिस्ट और लाइक पर रिसर्च करें।
डेडुप्लिकेटर

2

यह प्रत्येक नोड के लिए पिछले और अगले पॉइंटर को भी स्वैप करता है। यही कारण है कि यह रैखिक लेता है। यद्यपि यह O (1) में किया जा सकता है यदि इस LL का उपयोग करने वाला फ़ंक्शन LL के बारे में भी जानकारी लेता है जैसे कि इनपुट सामान्य रूप से एक्सेस कर रहा है या रिवर्स।


1

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


1

यह ओ (एन) केवल इसलिए है क्योंकि इसे सूची को रिवर्स ऑर्डर में कॉपी करना होगा। प्रत्येक व्यक्तिगत आइटम ऑपरेशन O (1) है, लेकिन पूरी सूची में उनमें से n हैं।

निश्चित रूप से नई सूची के लिए स्थान स्थापित करने और उसके बाद बिंदुओं को बदलने आदि में कुछ निरंतर-समय के संचालन शामिल हैं, ओ अंकन पहले-क्रम n कारक को शामिल करने के बाद व्यक्तिगत स्थिरांक पर विचार नहीं करता है।

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