Quicksort: धुरी का चयन


109

क्विकॉर्ट को लागू करते समय, आपको जो कुछ करना है, उसमें से एक धुरी चुनना है। लेकिन जब मैं नीचे दिए गए एक जैसे pseudocode को देखता हूं, तो यह स्पष्ट नहीं है कि मुझे धुरी कैसे चुननी चाहिए। सूची का पहला तत्व? कुछ और?

 function quicksort(array)
     var list less, greater
     if length(array) ≤ 1  
         return array  
     select and remove a pivot value pivot from array
     for each x in array
         if x ≤ pivot then append x to less
         else append x to greater
     return concatenate(quicksort(less), pivot, quicksort(greater))

क्या कोई मुझे धुरी चुनने की अवधारणा को समझने में मदद कर सकता है और क्या अलग-अलग परिदृश्य विभिन्न रणनीतियों के लिए कॉल करते हैं या नहीं।


जवाबों:


87

एक यादृच्छिक धुरी का चयन करने से यह मौका कम हो जाता है कि आप सबसे खराब स्थिति वाले ओ (एन 2 ) प्रदर्शन का सामना करेंगे (हमेशा पहले या अंतिम को चुनने से लगभग-सॉर्ट किए गए या लगभग-रिवर्स-सॉर्ट किए गए डेटा के लिए सबसे खराब स्थिति का प्रदर्शन होगा)। मध्यम तत्व चुनना अधिकांश मामलों में भी स्वीकार्य होगा।

इसके अलावा, यदि आप इसे स्वयं लागू कर रहे हैं, तो एल्गोरिथ्म के ऐसे संस्करण हैं जो इन-प्लेस काम करते हैं (अर्थात दो नई सूचियाँ बनाए बिना और फिर उन्हें संक्षिप्त करना)।


10
मैं इस धारणा को दूसरा मानूंगा कि खोज को लागू करना स्वयं प्रयास के लायक नहीं हो सकता है। इसके अलावा, सावधान रहें कि आप यादृच्छिक संख्या कैसे उठा रहे हैं, क्योंकि यादृच्छिक संख्या जनरेटर कभी-कभी थोड़े धीमे होते हैं।
पीटरअलेनवेब

@ जोनाथन लेफ़लर का जवाब बेहतर है
नाथन

60

यह आपकी आवश्यकताओं पर निर्भर करता है। यादृच्छिक पर एक धुरी का चयन करने से O (N ^ 2) प्रदर्शन उत्पन्न करने वाले डेटा सेट को बनाना कठिन हो जाता है। 'मेडियन-ऑफ-थ्री' (प्रथम, अंतिम, मध्य) भी समस्याओं से बचने का एक तरीका है। तुलना के सापेक्ष प्रदर्शन से सावधान रहें, हालांकि; यदि आपकी तुलना महंगी है, तो Mo3 यादृच्छिक पर (एक धुरी मूल्य) चुनने की तुलना में अधिक तुलना करता है। तुलना करने के लिए डेटाबेस रिकॉर्ड महंगा हो सकता है।


अद्यतन: उत्तर में टिप्पणियों को खींचना।

mdkess दावा किया गया:

'मेडियन ऑफ 3' पहले मध्य में नहीं है। तीन यादृच्छिक अनुक्रमित चुनें, और इस का मध्य मान लें। पूरे बिंदु यह सुनिश्चित करने के लिए है कि पिवोट्स की आपकी पसंद नियतात्मक नहीं है - यदि यह है, तो सबसे खराब स्थिति डेटा आसानी से उत्पन्न हो सकता है।

जिस पर मैंने जवाब दिया:

  • पी Kirschenhofer, एच प्रोडिंगर द्वारा Hoare's Find Algorithm with Median- to -Three Partition (1997) का विश्लेषण, C Martínez आपके विवाद का समर्थन करता है (यह कि 'माध्यस-ऑफ-थ्री' तीन यादृच्छिक आइटम हैं)।

  • हनू एर्किओ द्वारा द कंप्यूटर जर्नल, वॉल्यूम 27, नंबर 3, 1984 में प्रकाशित 'द वर्स्ट केस परफॉर्मेंस फॉर मेडियन-ऑफ-थ्री क्विकॉर्ट' के बारे में portal.acm.org पर वर्णित एक लेख है । [अपडेट 2012-02-02] 26: के लिए पाठ मिल गया लेख । धारा 2 'द अल्गोरिथम' शुरू होता है: ' ए [एल: आर] के पहले, मध्य और अंतिम तत्वों के माध्यिका का उपयोग करके, अधिकांश व्यावहारिक स्थितियों में काफी समान आकारों के भागों में कुशल विभाजन प्राप्त किए जा सकते हैं। 'इस प्रकार, यह पहले-मध्य-अंतिम Mo3 दृष्टिकोण पर चर्चा कर रहा है।]

  • एक और छोटा लेख जो दिलचस्प है, वह है Mc Mclroy , "ए किलर एडवरसरी फॉर क्विकॉर्ट" , जो सॉफ्टवेयर-प्रैक्टिस और एक्सपीरियंस, वॉल्यूम में प्रकाशित हुआ है। 29 (0), 1-4 (0 1999)। यह बताता है कि कैसे किसी भी क्विकसॉर्ट को चतुष्कोणीय व्यवहार करना है।

  • एटी एंड टी बेल लैब्स टेक जर्नल, अक्टूबर 1984 "वर्किंग सॉर्ट रूटीन के निर्माण में सिद्धांत और अभ्यास" बताता है कि "होरे ने कई बेतरतीब ढंग से चुनी गई लाइनों के माध्य के चारों ओर विभाजन का सुझाव दिया। सेडगविक [...] ने पहले के मंझले को चुनने की सिफारिश की।" ..] अंतिम [...] और मध्य "। यह इंगित करता है कि साहित्य में 'मंझला-तीन' के लिए दोनों तकनीकों को जाना जाता है। (अपडेट २०१४-११-२३: यह लेख IEEE Xplore पर या विले से उपलब्ध है - यदि आपके पास सदस्यता है या शुल्क देने के लिए तैयार हैं।)

  • सॉफ्टवेयर प्रैक्टिस एंड एक्सपीरियंस, वॉल्यूम 23 (11), नवंबर 1993 में प्रकाशित जेएल बेंटले और एमडी मैकलीरो द्वारा by इंजीनियरिंग ए सॉर्ट फंक्शन ’ , मुद्दों की व्यापक चर्चा में जाता है, और उन्होंने भाग के आधार पर एक अनुकूली विभाजन एल्गोरिथ्म चुना। डेटा सेट का आकार। विभिन्न दृष्टिकोणों के लिए व्यापार-बंदों की बहुत चर्चा होती है।

  • आगे की ट्रैकिंग के लिए 'माध्यियन-ऑफ-थ्री' की एक Google खोज बहुत अच्छी तरह से काम करती है।

जानकारी के लिए धन्यवाद; मुझे पहले केवल नियतात्मक 'माध्यिका-तीन' का सामना करना पड़ा था।


4
3 का माध्य पहले अंतिम मध्य में नहीं है। तीन यादृच्छिक अनुक्रमित चुनें, और इस का मध्य मान लें। पूरे बिंदु यह सुनिश्चित करने के लिए है कि पिवोट्स की आपकी पसंद नियतात्मक नहीं है - यदि यह है, तो सबसे खराब स्थिति डेटा आसानी से उत्पन्न हो सकता है।
माइंडवायरस वायरस

मैं abt introsort पढ़ रहा था जो दोनों quicksort और heapsort की अच्छी विशेषताओं को जोड़ती है। तीन के मध्य का उपयोग करके धुरी का चयन करने का दृष्टिकोण हमेशा अनुकूल नहीं हो सकता है।
सुमित कुमार साहा

4
यादृच्छिक सूचकांकों को चुनने में समस्या यह है कि यादृच्छिक संख्या जनरेटर बहुत महंगे हैं। हालांकि यह सॉर्ट करने की बड़ी-ओ लागत में वृद्धि नहीं करता है, यह संभवतः चीजों को धीमा कर देगा जैसे कि आपने अभी पहले, आखिरी और मध्य तत्वों को चुना था। (वास्तविक दुनिया में, मैं शर्त लगाता हूं कि कोई भी आपके त्वरित प्रकार को धीमा करने के लिए विवादित स्थितियों को नहीं बना रहा है।)
केविन चेन

20

हे, मैंने अभी इस कक्षा को पढ़ाया है।

कई विकल्प हैं।
सरल: सीमा का पहला या अंतिम तत्व चुनें। (आंशिक रूप से सॉर्ट किए गए इनपुट पर खराब) बेहतर: रेंज के बीच में आइटम चुनें। (आंशिक रूप से हल किए गए इनपुट पर बेहतर)

हालांकि, किसी भी मनमाने तत्व को लेने से आकार n की दो सरणियों में आकार 1 और n-1 के खराब विभाजन का जोखिम होता है। यदि आप अक्सर ऐसा करते हैं, तो आपका क्विकॉर्ट ओ (n ^ 2) बनने का जोखिम उठाता है।

एक सुधार जो मैंने देखा है वह है माध्यिका (पहला, अंतिम, मध्य); सबसे खराब स्थिति में, यह अभी भी O (n ^ 2) पर जा सकता है, लेकिन संभावित रूप से, यह एक दुर्लभ मामला है।

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

यदि आप अभी भी समस्याओं में भाग रहे हैं, तो मध्य मार्ग पर जाएँ।


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

आकार 1 और n-1 के दो सरणियों में आकार n के विभाजन को विभाजित करके O (n ^ 2) बनने का जोखिम क्यों होगा?
आरोन फ्रेंके

आकार एन का एक आकार मान लें। आकार में विभाजन [1, एन -1]। अगला चरण दाहिने आधे भाग को [1, N-2] में विभाजित कर रहा है। और इसी तरह, जब तक कि हमारे पास आकार के एन विभाजन नहीं हैं। 1. लेकिन, अगर हम आधे में विभाजन करना चाहते थे, तो हम प्रत्येक चरण में एन / 2 के 2 विभाजन कर रहे होंगे, जो जटिलता के लॉग (एन) के लिए अग्रणी होगा;
क्रिस कूडमोर

11

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

एक बेहतर तकनीक मध्य-तीन पद्धति है, जहां आप तीन तत्वों को यादृच्छिक रूप से चुनते हैं, और बीच का चयन करते हैं। आप जानते हैं कि आपके द्वारा चुना गया तत्व पहले या अंतिम नहीं होगा, बल्कि केंद्रीय सीमा प्रमेय द्वारा भी होगा, मध्य तत्व का वितरण सामान्य होगा, जिसका अर्थ है कि आप मध्य की ओर प्रवृत्त होंगे (और इसलिए , n lg n समय)।

यदि आप एल्गोरिथ्म के लिए O (nlgn) रनटाइम की गारंटी देना चाहते हैं, तो किसी सरणी के माध्यिका को खोजने के लिए कॉलम -5 विधि O (n) समय में चलती है, जिसका अर्थ है कि सबसे खराब स्थिति में क्विकॉर्ट के लिए पुनरावृत्ति समीकरण टी (n) = O (n) (माध्यिका ज्ञात करें) + O (n) (विभाजन) + 2T (n / 2) (बाएं और दाएं की पुनरावृत्ति)। मास्टर प्रमेय के अनुसार, यह O (n lg n) है । हालांकि, निरंतर कारक बहुत बड़ा होगा, और यदि सबसे खराब स्थिति आपके प्राथमिक चिंता का विषय है, तो इसके बजाय एक मर्ज सॉर्ट का उपयोग करें, जो औसत पर क्विकसॉर्ट की तुलना में केवल थोड़ा धीमा है, और O (nlgn) समय की गारंटी देता है (और बहुत तेज़ होगा इस लंगड़ा मंझला quicksort की तुलना में)।

मेडियन ऑफ़ मेडियंस एलोरिज़्म का स्पष्टीकरण


6

कोशिश मत करो और बहुत चालाक हो और धुरी रणनीतियों को संयोजित करें। यदि आपने पहले, अंतिम और मध्य में एक यादृच्छिक सूचकांक के माध्यिका को उठाकर यादृच्छिक धुरी के साथ 3 के मध्य को जोड़ दिया है, तो आप अभी भी कई वितरणों के लिए कमजोर होंगे जो 3 द्विघात का मध्यकाल भेजते हैं (इसलिए वास्तव में इससे भी बदतर सादे यादृच्छिक धुरी)

जैसे एक पाइप अंग वितरण (1,2,3 ... एन / 2..3,2,1) पहला और आखिरी दोनों 1 होगा और रैंडम इंडेक्स 1 की तुलना में कुछ अधिक होगा, माध्य 1 देता है ( या तो पहले या बाद में) और आपको पूरी तरह से असंतुलित विभाजन मिलता है।


2

ऐसा करने वाले तीन खंडों में क्विकॉर्ट को तोड़ना आसान है

  1. विनिमय या डेटा तत्व फ़ंक्शन स्वैप करें
  2. विभाजन कार्य
  3. विभाजन को संसाधित करना

यह केवल एक लंबे कार्य की तुलना में थोड़ा अधिक अक्षम है, लेकिन यह समझना बहुत आसान है।

कोड इस प्रकार है:

/* This selects what the data type in the array to be sorted is */

#define DATATYPE long

/* This is the swap function .. your job is to swap data in x & y .. how depends on
data type .. the example works for normal numerical data types .. like long I chose
above */

void swap (DATATYPE *x, DATATYPE *y){  
  DATATYPE Temp;

  Temp = *x;        // Hold current x value
  *x = *y;          // Transfer y to x
  *y = Temp;        // Set y to the held old x value
};


/* This is the partition code */

int partition (DATATYPE list[], int l, int h){

  int i;
  int p;          // pivot element index
  int firsthigh;  // divider position for pivot element

  // Random pivot example shown for median   p = (l+h)/2 would be used
  p = l + (short)(rand() % (int)(h - l + 1)); // Random partition point

  swap(&list[p], &list[h]);                   // Swap the values
  firsthigh = l;                                  // Hold first high value
  for (i = l; i < h; i++)
    if(list[i] < list[h]) {                 // Value at i is less than h
      swap(&list[i], &list[firsthigh]);   // So swap the value
      firsthigh++;                        // Incement first high
    }
  swap(&list[h], &list[firsthigh]);           // Swap h and first high values
  return(firsthigh);                          // Return first high
};



/* Finally the body sort */

void quicksort(DATATYPE list[], int l, int h){

  int p;                                      // index of partition 
  if ((h - l) > 0) {
    p = partition(list, l, h);              // Partition list 
    quicksort(list, l, p - 1);        // Sort lower partion
    quicksort(list, p + 1, h);              // Sort upper partition
  };
};

1

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


1

यदि आप एक यादृच्छिक-सुलभ संग्रह (एक सरणी की तरह) को सॉर्ट कर रहे हैं, तो भौतिक मध्य आइटम को चुनना सबसे अच्छा है। इसके साथ, यदि सरणी सभी तैयार क्रमबद्ध (या लगभग क्रमबद्ध) है, तो दो विभाजन समान होंगे, और आपको सबसे अच्छी गति मिलेगी।

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

हालांकि, एक लिंक्ड-लिस्ट के लिए, पहले के अलावा कुछ भी चुनना, बस मामलों को बदतर बना देगा। यह एक सूचीबद्ध सूची में मध्य आइटम को उठाता है, आपको प्रत्येक विभाजन चरण पर इसके माध्यम से कदम उठाना होगा - एक ओ (एन / 2) ऑपरेशन जोड़ना जो लॉगएन बार कुल समय ओ (1.5 एन * लॉग एन) बनाते हुए किया जाता है। और अगर हम जानते हैं कि सूची शुरू होने से पहले कितनी लंबी है - आमतौर पर हम ऐसा नहीं करते हैं तो हमें उन्हें गिनने के लिए सभी तरह से कदम उठाना होगा, फिर मध्य खोजने के लिए आधा रास्ता तय करना होगा, फिर एक के माध्यम से कदम बढ़ाना होगा तीसरी बार वास्तविक विभाजन करने के लिए: O (2.5N * log N)


0

आदर्श रूप से धुरी पूरे सरणी में मध्य मान होना चाहिए। यह सबसे खराब स्थिति प्रदर्शन की संभावना को कम करेगा।


1
यहाँ घोड़े के सामने गाड़ी।
ncmathsadist

0

त्वरित मूल्य की जटिलता धुरी मूल्य के चयन के साथ बहुत भिन्न होती है। उदाहरण के लिए यदि आप हमेशा पहले तत्व को एक धुरी के रूप में चुनते हैं, तो एल्गोरिथ्म की जटिलता O (n ^ 2) जितनी ही खराब हो जाती है। यहाँ धुरी तत्व चुनने की एक स्मार्ट विधि है- 1. सरणी का पहला, मध्य, अंतिम तत्व चुनें। 2. इन तीन नंबरों की तुलना करें और उस संख्या का पता लगाएं जो एक से अधिक है और अन्य अर्थात माध्यिका से छोटी है। 3. इस तत्व को धुरी तत्व बनाते हैं।

इस विधि से धुरी का चयन करने से सरणी लगभग दो आधे में विभाजित हो जाती है और इसलिए जटिलता O (nlog (n)) तक कम हो जाती है।


0

औसतन, 3 का माध्य छोटे n के लिए अच्छा है। 5 का माध्य बड़ा n के लिए थोड़ा बेहतर है। निथर, जो "तीन के तीन माध्यकों का माध्यिका" है, बहुत बड़े n के लिए और भी बेहतर है।

जितना अधिक आप बेहतर एन नमूने के साथ जाते हैं, उतना ही बढ़ता जाता है, लेकिन नमूनों में वृद्धि के रूप में सुधार नाटकीय रूप से धीमा हो जाता है। और आप नमूने और सॉर्टिंग नमूनों के ओवरहेड को उकसाते हैं।


0

मैं मध्य सूचकांक का उपयोग करने की सलाह देता हूं, क्योंकि इसकी गणना आसानी से की जा सकती है।

आप इसे गोलाई (array.length / 2) द्वारा गणना कर सकते हैं।


-1

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


1
3 तत्वों के मध्य को ढूंढना निरंतर समय में किया जा सकता है। किसी भी अधिक, और हम अनिवार्य रूप से उप सरणी को सॉर्ट करना होगा। जैसे-जैसे n बड़ा होता जाता है, हम दाईं ओर फिर से छंटनी की समस्या में चले जाते हैं।
क्रिस कुडमोर
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.