दोनों quicksort और heapsort इन-प्लेस सॉर्टिंग करते हैं। कौनसा अच्छा है? उन अनुप्रयोगों और मामलों को क्या पसंद किया जाता है?
जवाबों:
इस पत्र में कुछ विश्लेषण है।
इसके अलावा, विकिपीडिया से:
क्विकसॉर्ट का सबसे सीधा प्रतियोगी है हीप्सोर्ट। हीप्सर्ट आमतौर पर क्विकसॉर्ट की तुलना में कुछ धीमा होता है, लेकिन सबसे खराब स्थिति में चलने का समय हमेशा somewhat (nlogn) होता है। Quicksort आमतौर पर तेज़ होता है, हालांकि इंट्रोसर्ट वैरिएंट को छोड़कर सबसे खराब केस परफॉर्मेंस की संभावना बनी रहती है, जो कि खराब केस का पता चलने पर बदल जाता है। यदि यह पहले से ही ज्ञात है कि हेप्सॉर्ट आवश्यक होने जा रहा है, तो इसका उपयोग करने के लिए इसे स्विच करने के लिए इंट्रोसॉर्ट की प्रतीक्षा करने की तुलना में सीधे उपयोग करना तेज होगा।
हीप्सोर्ट हे (एन लॉग एन) की गारंटी है, क्विकॉर्ट में सबसे खराब स्थिति से बेहतर क्या है। हीप्सोर्ट को ऑर्डर किए गए डेटा को डालने के लिए अन्य मेमोरी के लिए अधिक मेमोरी की आवश्यकता नहीं है, जैसा कि मर्जेसर्ट द्वारा आवश्यक है। तो क्यों Comercial अनुप्रयोग Quicksort के साथ चिपके रहते हैं? क्विकॉर्ट के पास ऐसा क्या है जो दूसरों के कार्यान्वयन पर इतना विशेष है?
मैंने स्वयं एल्गोरिदम का परीक्षण किया है और मैंने देखा है कि क्विकसॉर्ट में वास्तव में कुछ विशेष है। यह हीप और मर्ज एल्गोरिदम की तुलना में बहुत तेज, तेज चलता है।
क्विकॉर्ट का रहस्य है: यह लगभग अनावश्यक तत्व स्वैप नहीं करता है। स्वैप में समय लगता है।
हीप्सोर्ट के साथ, भले ही आपका सभी डेटा पहले से ही ऑर्डर किया गया हो, आप सरणी को ऑर्डर करने के लिए 100% तत्वों को स्वैप करने जा रहे हैं।
मर्जेसर्ट के साथ, यह और भी बुरा है। आप 100% तत्वों को किसी अन्य सरणी में लिखने जा रहे हैं और इसे मूल एक में वापस लिख सकते हैं, भले ही डेटा पहले से ही ऑर्डर किया गया हो।
क्विकॉर्ट के साथ आप स्वैप नहीं करते हैं जो पहले से ही ऑर्डर किया गया है। यदि आपका डेटा पूरी तरह से ऑर्डर किया गया है, तो आप लगभग कुछ भी नहीं स्वैप करते हैं! हालांकि सबसे खराब स्थिति के बारे में बहुत अधिक उपद्रव है, धुरी की पसंद पर थोड़ा सुधार, सरणी के पहले या अंतिम तत्व को प्राप्त करने के अलावा कोई भी इससे बच सकता है। यदि आपको पहले, अंतिम और मध्य तत्व के बीच के मध्यवर्ती तत्व से एक धुरी मिलती है, तो यह सबसे खराब स्थिति से बचने के लिए पर्याप्त है।
क्विकसॉर्ट में जो बेहतर है वह सबसे खराब मामला नहीं है, लेकिन सबसे अच्छा मामला है! सबसे अच्छा मामले में आप एक ही संख्या की तुलना करते हैं, ठीक है, लेकिन आप लगभग कुछ भी नहीं स्वैप करते हैं। औसत मामले में आप तत्वों का हिस्सा स्वैप करते हैं, लेकिन सभी तत्व नहीं, जैसा कि हीप्सॉर्ट और मर्जेसॉर्ट में है। यह वही है जो क्विकॉर्ट को सबसे अच्छा समय देता है। कम स्वैप, अधिक गति।
मेरे कंप्यूटर पर C # के नीचे कार्यान्वयन, रिलीज़ मोड पर चल रहा है, Array.Sort को मध्य धुरी के साथ 3 सेकंड और बेहतर धुरी के साथ 2 सेकंड तक हरा देता है (हाँ, एक अच्छी धुरी पाने के लिए एक उपरि है)।
static void Main(string[] args)
{
int[] arrToSort = new int[100000000];
var r = new Random();
for (int i = 0; i < arrToSort.Length; i++) arrToSort[i] = r.Next(1, arrToSort.Length);
Console.WriteLine("Press q to quick sort, s to Array.Sort");
while (true)
{
var k = Console.ReadKey(true);
if (k.KeyChar == 'q')
{
// quick sort
Console.WriteLine("Beg quick sort at " + DateTime.Now.ToString("HH:mm:ss.ffffff"));
QuickSort(arrToSort, 0, arrToSort.Length - 1);
Console.WriteLine("End quick sort at " + DateTime.Now.ToString("HH:mm:ss.ffffff"));
for (int i = 0; i < arrToSort.Length; i++) arrToSort[i] = r.Next(1, arrToSort.Length);
}
else if (k.KeyChar == 's')
{
Console.WriteLine("Beg Array.Sort at " + DateTime.Now.ToString("HH:mm:ss.ffffff"));
Array.Sort(arrToSort);
Console.WriteLine("End Array.Sort at " + DateTime.Now.ToString("HH:mm:ss.ffffff"));
for (int i = 0; i < arrToSort.Length; i++) arrToSort[i] = r.Next(1, arrToSort.Length);
}
}
}
static public void QuickSort(int[] arr, int left, int right)
{
int begin = left
, end = right
, pivot
// get middle element pivot
//= arr[(left + right) / 2]
;
//improved pivot
int middle = (left + right) / 2;
int
LM = arr[left].CompareTo(arr[middle])
, MR = arr[middle].CompareTo(arr[right])
, LR = arr[left].CompareTo(arr[right])
;
if (-1 * LM == LR)
pivot = arr[left];
else
if (MR == -1 * LR)
pivot = arr[right];
else
pivot = arr[middle];
do
{
while (arr[left] < pivot) left++;
while (arr[right] > pivot) right--;
if(left <= right)
{
int temp = arr[right];
arr[right] = arr[left];
arr[left] = temp;
left++;
right--;
}
} while (left <= right);
if (left < end) QuickSort(arr, left, end);
if (begin < right) QuickSort(arr, begin, right);
}
ज्यादातर स्थितियों के लिए, जल्दी बनाम थोड़ा तेज होना अप्रासंगिक है ... आप बस यह कभी नहीं चाहते हैं कि कभी-कभी वेयय धीम हो जाएं। यद्यपि आप धीमी परिस्थितियों से बचने के लिए क्विकॉर्ट को ट्विक कर सकते हैं, आप मूल क्विकसॉर्ट की भव्यता खो देते हैं। इसलिए, ज्यादातर चीजों के लिए, मैं वास्तव में हीप्सॉर्ट को पसंद करता हूं ... आप इसे अपनी पूर्ण सरलता में लागू कर सकते हैं, और कभी भी धीमी गति नहीं पा सकते हैं।
उन स्थितियों के लिए जहां आप ज्यादातर मामलों में अधिकतम गति चाहते हैं, क्विकॉर्ट को हेपॉर्ट पर पसंद किया जा सकता है, लेकिन न तो सही उत्तर हो सकता है। गति-गंभीर स्थितियों के लिए, यह स्थिति के विवरणों की बारीकी से जांच करने के लायक है। उदाहरण के लिए, मेरे कुछ स्पीड-क्रिटिकल कोड में, यह बहुत सामान्य है कि डेटा पहले से ही सॉर्ट या पास-सॉर्ट किया गया है (यह कई संबंधित फ़ील्ड्स को अनुक्रमित कर रहा है जो अक्सर या तो एक साथ चलते हैं और एक-दूसरे के विपरीत ऊपर और नीचे बढ़ते हैं या नीचे जाते हैं,) एक बार जब आप एक बार छाँटते हैं, तो दूसरे या तो छाँटे जाते हैं या रिवर्स-सॉर्ट किए जाते हैं या बंद होते हैं ... जिनमें से या तो क्विकॉर्ट को मार सकते हैं)। उस मामले के लिए, मैंने न तो लागू किया ... इसके बजाय, मैंने दिज्क्स्ट्रा का स्मूथसर्ट लागू किया ... एक हेप्सॉर्ट संस्करण है जो ओ (एन) है जब पहले से ही सॉर्ट किया गया या पास-सॉर्ट किया गया ... यह इतना सुरुचिपूर्ण नहीं है, समझने में आसान नहीं है। लेकिन जल्दी ... पढ़ेंयदि आप कुछ और अधिक चुनौतीपूर्ण कोड चाहते हैं तो http://www.cs.utexas.edu/users/EWD/ewd07xx/EWD796a.PDF ।
क्विकॉर्ट-हीप्सोर्ट-इन-प्लेस हाइब्रिड वास्तव में दिलचस्प हैं, भी, क्योंकि उनमें से अधिकांश को केवल सबसे खराब मामले में n * लॉग एन तुलना की आवश्यकता होती है (वे एसिम्पोटिक्स के पहले कार्यकाल के संबंध में इष्टतम हैं, इसलिए वे सबसे खराब स्थिति से बचते हैं। Quicksort का), O (लॉग एन) अतिरिक्त स्थान और वे पहले से ही डेटा के सेट के संबंध में क्विकॉर्ट के अच्छे व्यवहार का कम से कम "आधा" संरक्षित करते हैं। एक बेहद दिलचस्प एल्गोरिथ्म में Dikert और वेइस द्वारा प्रस्तुत किया जाता है http://arxiv.org/pdf/1209.4214v1.pdf :
अनि। दोनों के बीच quick sort
और merge sort
चूंकि जगह छँटाई के प्रकार हैं, व्रॉस्ट मामले के बीच एक अंतर है जो व्रॉस्ट मामले के चलने के समय के लिए त्वरित सॉर्ट के लिए समय चल रहा है O(n^2)
और ढेर सॉर्ट के लिए यह अभी भी है O(n*log(n))
और औसत मात्रा में डेटा के लिए त्वरित सॉर्ट अधिक उपयोगी होगा। चूंकि यह रैंडमाइज्ड एल्गोरिथ्म है इसलिए सही एन्स होने की संभावना। कम समय में आपके द्वारा चुने गए धुरी तत्व की स्थिति पर निर्भर करेगा।
तो ए
अच्छी कॉल: L और G का आकार 3s / 4 से कम है
खराब कॉल: L और G में से एक का आकार 3s / 4 से अधिक है
छोटी राशि के लिए हम प्रविष्टि सॉर्ट के लिए जा सकते हैं और बहुत अधिक मात्रा में डेटा हीप सॉर्ट के लिए जाते हैं।
हेपसोर्ट को ओ (एन * लॉग (एन)) का सबसे खराब चल रहा मामला होने का लाभ है, इसलिए ऐसे मामलों में जहां एस्कॉर्ट खराब प्रदर्शन करने की संभावना है (आमतौर पर सॉर्ट किए गए डेटा सेट) हेप्सॉर्ट को बहुत पसंद किया जाता है।
वैसे अगर आप आर्किटेक्चर के स्तर पर जाते हैं ... हम कैश मेमोरी संरचना का उपयोग कैश मैमोरी में करते हैं। तो कभी कतार में जो उपलब्ध होता है, उसे छाँटा जाएगा। त्वरित रूप में हमें किसी भी प्रकार से सरणी को विभाजित करने में कोई समस्या नहीं है ... लेकिन ढेर में सॉर्ट (सरणी का उपयोग करके) ऐसा हो सकता है कि माता-पिता कैश में उपलब्ध उप-सरणी में मौजूद नहीं हो सकते हैं और फिर इसे कैश मेमोरी में लाना होगा ... जो कि समय लेने वाला है। यह क्विकॉर्ट सबसे अच्छा है !! best
हीप्सोर्ट एक ढेर बनाता है और फिर बार-बार अधिकतम आइटम निकालता है। इसका सबसे खराब मामला ओ (एन लॉग एन) है।
लेकिन यदि आप त्वरित प्रकार का सबसे खराब मामला देखेंगे , जो कि O (n2) है, तो आपको एहसास होगा कि त्वरित क्रम बड़े डेटा के लिए एक अच्छा विकल्प नहीं होगा।
इसलिए यह छँटाई एक दिलचस्प बात है; मेरा मानना है कि आज बहुत सारे छंटनी वाले एल्गोरिदम रहते हैं, क्योंकि वे सभी अपने सर्वश्रेष्ठ स्थानों पर 'सर्वश्रेष्ठ' हैं। उदाहरण के लिए, बबल सॉर्ट त्वरित सॉर्ट कर सकता है यदि डेटा सॉर्ट किया गया है। या अगर हम वस्तुओं को छांटने के बारे में कुछ जानते हैं तो शायद हम बेहतर कर सकते हैं।
यह सीधे आपके प्रश्न का उत्तर नहीं दे सकता है, मैंने सोचा कि मैं अपने दो सेंट जोड़ूंगा।
बहुत बड़े इनपुट से निपटने पर हीप सॉर्ट एक सुरक्षित शर्त है। एसिम्प्टोटिक विश्लेषण से पता चलता है कि सबसे खराब स्थिति में हीप्सर्ट के विकास का क्रम है Big-O(n logn)
, जो क्विकर के Big-O(n^2)
सबसे बुरे मामले से बेहतर है । हालांकि, हीप्सोर्ट एक अच्छी तरह से लागू त्वरित प्रकार की तुलना में अधिकांश मशीनों पर अभ्यास में कुछ धीमा है। हीप्सोर्ट भी एक स्थिर छँटाई एल्गोरिथ्म नहीं है।
एस्कॉर्ट्स की तुलना में हेस्पोर्ट व्यवहार में धीमी है, क्विकॉर्ट में संदर्भ (" https://en.wikipedia.org/wiki/Locality_of_reference ") के बेहतर इलाके के कारण है , जहां डेटा तत्व अपेक्षाकृत नज़दीकी संग्रहण स्थानों पर हैं। सिस्टम जो संदर्भ के मजबूत इलाके का प्रदर्शन करते हैं, प्रदर्शन अनुकूलन के लिए महान उम्मीदवार हैं। हालाँकि, छँटनी बड़ी छलांग के साथ संबंधित है। यह छोटे आदानों के लिए क्विकॉर्ट को अधिक अनुकूल बनाता है।
मेरे लिए हेप्सॉर्ट और क्विकॉर्ट के बीच एक बहुत ही मौलिक अंतर है: उत्तरार्द्ध एक पुनरावृत्ति का उपयोग करता है। पुनरावर्ती एल्गोरिदम में ढेर पुनरावृत्ति की संख्या के साथ बढ़ता है। इससे कोई फर्क नहीं पड़ता कि n छोटा है, लेकिन अभी मैं n = 10 ^ 9 के साथ दो मैट्रिसेस छाँट रहा हूँ । कार्यक्रम में लगभग 10 जीबी रैम है और कोई भी अतिरिक्त मेमोरी मेरे कंप्यूटर को वर्चुअल डिस्क मेमोरी में स्वैप करना शुरू कर देगी। मेरी डिस्क एक रैम डिस्क है, लेकिन फिर भी इसे स्वैप करने से गति में भारी अंतर आता है । तो C ++ में कोडित एक स्टेटपैक में, जिसमें एडजस्टेबल डायमेंशन मैट्रिसेस शामिल हैं, जिसमें प्रोग्रामर से पहले साइज अनजान है, और नॉनपैमेट्रिक स्टैटिस्टिकल सॉर्टिंग मैं बहुत बड़े डेटा मैटर्स के साथ उपयोग करने में देरी से बचने के लिए हैस्पोर्ट पसंद करता हूं।
मूल प्रश्न का उत्तर देने के लिए और कुछ अन्य टिप्पणियों को यहां संबोधित करें:
मैंने सिर्फ चयन के कार्यान्वयन की तुलना की, त्वरित, मर्ज और यह देखने के लिए कि वे एक दूसरे के खिलाफ कैसे ढेर होंगे। जवाब है कि वे सभी अपने डाउनसाइड हैं।
टीएल; डीआर: क्विक सबसे अच्छा सामान्य उद्देश्य सॉर्ट है (यथोचित रूप से तेज, स्थिर और अधिकतर इन-प्लेस) व्यक्तिगत रूप से मैं ढेर सॉर्ट पसंद करता हूं जब तक कि मुझे एक स्थिर सॉर्ट की आवश्यकता नहीं है।
चयन - एन ^ 2 - यह वास्तव में केवल 20 तत्वों या उससे कम के लिए अच्छा है, तो यह बेहतर है। जब तक आपका डेटा पहले से ही सॉर्ट नहीं किया जाता है, या बहुत, बहुत लगभग। एन ^ 2 वास्तव में धीमी गति से वास्तव में तेज हो जाता है।
त्वरित, मेरे अनुभव में, नहीं वास्तव में है कि त्वरित हर समय। एक सामान्य प्रकार के रूप में त्वरित सॉर्ट का उपयोग करने के लिए बोनस हालांकि यह काफी तेजी से है और यह स्थिर है। यह एक इन-प्लेस एल्गोरिथ्म भी है, लेकिन जैसा कि आमतौर पर इसे पुनरावर्ती रूप से लागू किया जाता है, यह अतिरिक्त स्टैक स्थान लेगा। यह O (n log n) और O (n ^ 2) के बीच में भी पड़ता है। किसी प्रकार की टाइमिंग इस बात की पुष्टि करती है, खासकर जब मान एक तंग सीमा के भीतर आते हैं। यह 10,000,000 वस्तुओं पर चयन प्रकार से अधिक तेज़ है, लेकिन मर्ज या ढेर की तुलना में धीमा है।
मर्ज सॉर्ट की गारंटी O (n log n) है क्योंकि इसका सॉर्ट डेटा पर निर्भर नहीं है। यह सिर्फ वही करता है जो यह करता है, भले ही आपने इसे क्या मान दिया हो। यह स्थिर भी है, लेकिन यदि आप कार्यान्वयन के बारे में सावधान नहीं हैं, तो बहुत बड़े प्रकार आपके स्टैक को उड़ा सकते हैं। कुछ जटिल इन-प्लेस मर्ज सॉर्स कार्यान्वयन हैं, लेकिन आम तौर पर आपको अपने मूल्यों को मर्ज करने के लिए प्रत्येक स्तर में एक और सरणी की आवश्यकता होती है। यदि वे सरणियाँ स्टैक पर रहती हैं तो आप समस्याओं में भाग सकते हैं।
हीप सॉर्ट अधिकतम ओ (एन लॉग एन) है, लेकिन कई मामलों में तेज है, इस पर निर्भर करता है कि आपको लॉग एन डीप हीप में अपने मूल्यों को कितनी दूर ले जाना है। ढेर आसानी से मूल सरणी में जगह में लागू किया जा सकता है, इसलिए इसे अतिरिक्त मेमोरी की आवश्यकता नहीं है, और यह पुनरावृत्ति है, इसलिए पीछे हटते समय स्टैक ओवरफ्लो के बारे में कोई चिंता नहीं है। विशाल ढेर के नकारात्मक पक्ष यह है प्रकार है कि यह एक स्थिर प्रकार है, जो यह की सही बाहर का मतलब है अगर आपको लगता है कि जरूरत नहीं है।