मैं मौजूदा महान उत्तरों में कुछ गणित जोड़ना चाहूंगा कि क्विकॉर्ट कैसे सर्वश्रेष्ठ मामले से हटकर प्रदर्शन करता है और यह कैसे संभव है, जो मुझे आशा है कि लोगों को थोड़ा बेहतर समझने में मदद मिलेगी कि क्यों ओ (एन ^ 2) मामला वास्तविक नहीं है QuickSort के अधिक परिष्कृत कार्यान्वयन में चिंता।
रैंडम एक्सेस मुद्दों के बाहर, दो मुख्य कारक हैं जो क्विकसॉर्ट के प्रदर्शन को प्रभावित कर सकते हैं और वे दोनों इस बात से संबंधित हैं कि डेटा की छंटनी कैसे की जाती है।
1) डेटा में कुंजियों की एक छोटी संख्या। सभी समान मानों का एक डेटासेट वैनीला 2-विभाजन क्विकॉर्ट पर n ^ 2 समय में सॉर्ट करेगा क्योंकि पिवट लोकेशन को छोड़कर सभी मान हर बार एक तरफ रखे जाते हैं। आधुनिक कार्यान्वयन 3-विभाजन प्रकार का उपयोग करके विधियों द्वारा इसे संबोधित करते हैं। ये विधियाँ O (n) समय में सभी समान मानों के डेटासेट पर निष्पादित होती हैं। इसलिए इस तरह के कार्यान्वयन का उपयोग करने का मतलब है कि कम संख्या में कुंजी के साथ एक इनपुट वास्तव में प्रदर्शन के समय में सुधार करता है और अब चिंता का विषय नहीं है।
2) अत्यधिक खराब धुरी चयन सबसे खराब स्थिति प्रदर्शन का कारण बन सकता है। एक आदर्श मामले में, धुरी हमेशा ऐसी होगी कि 50% डेटा छोटा होता है और 50% डेटा बड़ा होता है, जिससे प्रत्येक पुनरावृत्ति के दौरान इनपुट आधे में टूट जाएगा। यह हमें n तुलना देता है और O (n * logn) समय के लिए लॉग -2 (n) पुनरावृत्ति स्वैप समय देता है।
गैर-आदर्श धुरी चयन निष्पादन समय को कितना प्रभावित करता है?
आइए एक मामले पर विचार करें जहां पिवट को लगातार चुना जाता है ताकि 75% डेटा पिवट के एक तरफ हो। यह अभी भी O (n * logn) है, लेकिन अब लॉग का आधार 1 / 0.75 या 1.33 में बदल गया है। आधार बदलते समय प्रदर्शन में संबंध हमेशा लॉग (2) / लॉग (newBase) द्वारा निरूपित होता है। इस मामले में, वह निरंतर 2.4 है। इसलिए धुरी के चुनाव का यह गुण आदर्श की तुलना में 2.4 गुना अधिक है।
यह कितनी तेजी से खराब होता है?
बहुत जल्दी नहीं जब तक कि धुरी का विकल्प नहीं मिलता (लगातार) बहुत बुरा:
- एक तरफ 50%: (आदर्श मामला)
- एक तरफ 75%: लंबे समय तक 2.4 गुना
- एक तरफ 90%: लंबे समय तक 6.6 गुना
- एक तरफ 95%: लंबे समय तक 13.5 गुना
- एक तरफ 99%: लंबे समय तक 69 बार
जैसे ही हम एक तरफ 100% पहुंचते हैं, निष्पादन का लॉग भाग n के पास पहुंच जाता है और पूरा निष्पादन asymptotically O (n ^ 2) के पास पहुंच जाता है।
क्विकसॉर्ट के एक भोले-भाले कार्यान्वयन में, सॉर्ट किए गए एरे (प्रथम तत्व पिवट के लिए) या रिवर्स-सॉर्टेड ऐरे (अंतिम तत्व पिवट के लिए) जैसे मामले विश्वसनीय रूप से एक खराब स्थिति वाले ओ (एन ^ 2) निष्पादन समय का उत्पादन करेंगे। इसके अतिरिक्त, एक पूर्वानुमानित धुरी चयन के साथ कार्यान्वयन को सबसे खराब स्थिति निष्पादन के लिए डिज़ाइन किए गए डेटा द्वारा DoS हमले के अधीन किया जा सकता है। आधुनिक कार्यान्वयन विभिन्न तरीकों से इससे बचते हैं, जैसे कि डेटा को क्रमबद्ध करने से पहले यादृच्छिक करना, 3 बेतरतीब ढंग से चुने गए अनुक्रमों के माध्यिका का चयन करना आदि। मिश्रण में इस यादृच्छिककरण के साथ, हमारे पास 2 मामले हैं:
- छोटा डेटा सेट। सबसे खराब मामला यथोचित रूप से संभव है लेकिन O (n ^ 2) भयावह नहीं है क्योंकि n इतना छोटा है कि n ^ 2 भी छोटा है।
- बड़ा डेटा सेट। सबसे खराब मामला सिद्धांत में संभव है लेकिन व्यवहार में नहीं।
हम कितना भयानक प्रदर्शन देख सकते हैं?
संभावना गायब छोटे हैं । आइए एक प्रकार के 5,000 मानों पर विचार करें:
हमारा काल्पनिक कार्यान्वयन 3 बेतरतीब ढंग से चुने गए अनुक्रमों के माध्यिका का उपयोग करके एक धुरी का चयन करेगा। हम उन पिवोट्स पर विचार करेंगे जो 25% -75% रेंज में "अच्छे" और पिवोट्स हैं जो 0% -25% या 75% -100% रेंज में "खराब" हैं। यदि आप 3 यादृच्छिक अनुक्रमितों के माध्यिका का उपयोग करके संभाव्यता वितरण को देखते हैं, तो प्रत्येक पुनरावृत्ति के पास एक अच्छी धुरी के साथ समाप्त होने का 11/16 मौका है। हमें गणित को सरल बनाने के लिए 2 रूढ़िवादी (और गलत) धारणाएँ बनाते हैं:
अच्छे पिवोट्स हमेशा 25% / 75% पर विभाजित होते हैं और 2.4 * आदर्श मामले में संचालित होते हैं। हमें कभी भी एक आदर्श विभाजन या 25/75 से बेहतर कोई विभाजन नहीं मिलता है।
खराब पिवोट हमेशा सबसे खराब स्थिति होते हैं और अनिवार्य रूप से समाधान के लिए कुछ भी योगदान नहीं करते हैं।
हमारा QuickSort कार्यान्वयन n = 10 पर रुक जाएगा और सम्मिलन प्रकार पर स्विच हो जाएगा, इसलिए हमें 5,000 मूल्य के इनपुट को नीचे तक तोड़ने के लिए 22 25% / 75% धुरी विभाजन की आवश्यकता है। (१० * १.३३३३३३ ^ २२> ५०००) या, हमें ४ ९ ९ ० सबसे खराब केस पिवोट्स की आवश्यकता है। ध्यान रखें कि अगर हम किसी भी बिंदु पर 22 अच्छे पिवोट्स जमा करते हैं तो सॉर्ट पूरा हो जाएगा, इसलिए सबसे खराब स्थिति या इसके पास की किसी भी चीज के लिए बहुत अच्छे भाग्य की आवश्यकता होती है । अगर यह वास्तव में n = 10 के लिए आवश्यक 22 अच्छे पिवोट्स को प्राप्त करने के लिए 88 पुनरावर्ती हमें ले गया, तो यह 4 * 2.4 * आदर्श मामला होगा या आदर्श मामले के निष्पादन समय के लगभग 10 गुना होगा। यह कैसे संभव है कि हम 88 पुनरावृत्तियों के बाद आवश्यक 22 अच्छे पिवोट्स प्राप्त नहीं करेंगे ?
द्विपद संभाव्यता वितरण इसका उत्तर दे सकते हैं, और इसका उत्तर लगभग 10 ^ -18 है। (n 88 है, k 21 है, पी 0.6875 है) आपका उपयोगकर्ता लगभग एक हजार गुना अधिक बिजली की चपेट में आने की संभावना है 1 सेकंड में यह क्लिक करने के लिए [SORT] की तुलना में वे देखते हैं कि 5,000 आइटम सॉर्ट किसी भी बदतर चलाते हैं 10 * आदर्श मामले से। यह मौका छोटा हो जाता है क्योंकि डेटासेट बड़ा हो जाता है। यहां कुछ सरणी आकार और उनके समान संभावनाएं 10 * से अधिक समय तक चलने वाली हैं:
- 640 वस्तुओं की सरणी: 10 ^ -13 (60 कोशिशों में से 15 अच्छे धुरी बिंदुओं की आवश्यकता होती है)
- 5,000 वस्तुओं की सरणी: 10 ^ -18 (88 प्रयासों में से 22 अच्छे पिवोट्स की आवश्यकता है)
- 40,000 वस्तुओं की सरणी: 10 ^ -23 (116 में से 29 अच्छे पिवोट्स की आवश्यकता होती है)
याद रखें कि यह 2 रूढ़िवादी मान्यताओं के साथ है जो वास्तविकता से भी बदतर हैं। इसलिए वास्तविक प्रदर्शन अभी तक बेहतर है, और शेष संभावना का संतुलन आदर्श के करीब नहीं है।
अंत में, जैसा कि दूसरों ने उल्लेख किया है, यहां तक कि इन बेतुके असंभावित मामलों को एक ढेर के रूप में बदलकर समाप्त किया जा सकता है यदि पुनरावृत्ति ढेर बहुत गहरा हो जाता है। तो TLDR यह है कि क्विकॉर्ट के अच्छे कार्यान्वयन के लिए, सबसे खराब स्थिति वास्तव में मौजूद नहीं है क्योंकि इसे इंजीनियर किया गया है और निष्पादन O (n * logn) समय में पूरा होता है।
qsort
, Python'slist.sort
, औरArray.prototype.sort
फ़ायरफ़ॉक्स के जावास्क्रिप्ट में सभी सॉलिड-अप मर्ज प्रकार हैं। (जीएनयू एसटीएलsort
इसके बजाय इंट्रोस्पोर्ट का उपयोग करता है, लेकिन ऐसा इसलिए हो सकता है क्योंकि सी ++ में, स्वैपिंग संभावित रूप से कॉपी करने पर बड़ी जीत होती है।)