मैं उत्सुक हूँ अगर O (n लॉग एन) सबसे अच्छी तरह से जुड़ा हुआ सूची है।
मैं उत्सुक हूँ अगर O (n लॉग एन) सबसे अच्छी तरह से जुड़ा हुआ सूची है।
जवाबों:
यह अपेक्षा करना उचित है कि आप रनिंग टाइम में O (N log N) से बेहतर कुछ नहीं कर सकते ।
हालांकि, दिलचस्प हिस्सा यह जांचना है कि क्या आप इसे जगह में , निश्चित रूप से , इसके सबसे खराब मामले और इतने पर सॉर्ट कर सकते हैं ।
पुट्टी की प्रसिद्धि के साइमन टाथम बताते हैं कि मर्ज के साथ एक लिंक्ड सूची को कैसे सॉर्ट करना है । उन्होंने निम्नलिखित टिप्पणियों के साथ निष्कर्ष निकाला:
किसी भी स्वाभिमानी सॉर्ट एल्गोरिथ्म की तरह, इसमें ओ (एन लॉग एन) का समय चल रहा है। क्योंकि यह मर्जेसर्ट है, सबसे खराब चल रहा समय अभी भी ओ (एन लॉग एन) है; कोई रोग संबंधी मामले नहीं हैं।
सहायक भंडारण आवश्यकता छोटी और स्थिर होती है (यानी छँटाई दिनचर्या के भीतर कुछ चर)। सरणियों से अंतर्निहित सूचियों के स्वाभाविक रूप से भिन्न व्यवहार के लिए धन्यवाद, यह मर्जसोर्ट कार्यान्वयन ओ (एन) सहायक भंडारण लागत को सामान्य रूप से एल्गोरिथ्म से जुड़ा हुआ है।
सी में एक उदाहरण कार्यान्वयन भी है जो एकल और दोगुनी लिंक्ड सूची के लिए काम करता है।
जैसा कि @ जोर्गेन फॉग ने नीचे उल्लेख किया है, बिग-ओ नोटेशन कुछ स्थिर कारकों को छिपा सकता है जो स्मृति स्थानीयता के कारण बेहतर प्रदर्शन करने के लिए एक एल्गोरिथ्म का कारण बन सकता है, क्योंकि वस्तुओं की कम संख्या, आदि।
listsort
, तो आप देखेंगे कि आप पैरामीटर का उपयोग करके स्विच कर सकते हैं int is_double
।
listsort
केवल एकल-लिंक की गई सूचियों का समर्थन करता है
कई कारकों के आधार पर, यह वास्तव में सूची को किसी सरणी में कॉपी करने और फिर क्विकॉर्ट का उपयोग करने के लिए तेज़ हो सकता है ।
इसका कारण यह तेज़ हो सकता है कि किसी सरणी में लिंक की गई सूची की तुलना में बेहतर कैश प्रदर्शन है। यदि सूची में नोड्स मेमोरी में बिखरे हुए हैं, तो आप सभी जगह कैश मिस उत्पन्न कर सकते हैं। फिर, यदि सरणी बड़ी है, तो आपको कैश मिसेज़ वैसे भी मिलेंगे।
मर्जेसर्ट बेहतर तरीके से समानांतर करता है, इसलिए यदि आप चाहते हैं तो यह एक बेहतर विकल्प हो सकता है। यदि आप सीधे लिंक की गई सूची पर इसे निष्पादित करते हैं तो यह बहुत तेज़ है।
चूंकि दोनों एल्गोरिदम ओ (एन * लॉग एन) में चलते हैं, इसलिए एक सूचित निर्णय लेने से आपको उन दोनों मशीन पर प्रोफाइलिंग करना शामिल होगा जिन्हें आप उन्हें चलाना चाहते हैं।
--- संपादित करें
मैंने अपनी परिकल्पना का परीक्षण करने का निर्णय लिया और एक सी-प्रोग्राम लिखा, जिसने clock()
किलों की एक लिंक्ड सूची को सॉर्ट करने के लिए लिए गए समय (उपयोग ) को मापा । मैंने एक लिंक की गई सूची के साथ कोशिश की, जहां प्रत्येक नोड के साथ आवंटित किया गया था malloc()
और एक लिंक की गई सूची जहां नोड्स को एक सरणी में रैखिक रूप से बाहर रखा गया था, इसलिए कैश प्रदर्शन बेहतर होगा। मैंने इनकी तुलना अंतर्निहित qsort से की है, जिसमें खंडित सूची से एक सरणी में सब कुछ कॉपी करना और परिणाम को फिर से कॉपी करना शामिल है। प्रत्येक एल्गोरिथ्म को एक ही 10 डेटा सेट पर चलाया गया था और परिणाम औसत थे।
ये परिणाम हैं:
एन = 1000:
मर्ज प्रकार के साथ खंडित सूची: 0.000000 सेकंड
Ars with qsort: 0.000000 सेकंड
मर्ज सॉर्ट के साथ पैक्ड सूची: 0.000000 सेकंड
एन = 100000:
मर्ज प्रकार के साथ खंडित सूची: 0.039000 सेकंड
Ars with qsort: 0.025000 सेकंड
मर्ज सॉर्ट के साथ पैक्ड सूची: 0.009000 सेकंड
एन = 1000000:
मर्ज सॉर्ट के साथ खंडित सूची: 1.162000 सेकंड
क्षार के साथ सरणी: 0.420000 सेकंड
मर्ज सॉर्ट के साथ पैक्ड सूची: 0.112000 सेकंड
एन = 100000000:
मर्ज प्रकार के साथ खंडित सूची: 364.797000 सेकंड
क्वॉर्ट के साथ ऐरे: 61.166000 सेकंड
मर्ज सॉर्ट के साथ पैक सूची: 16.525000 सेकंड
निष्कर्ष:
कम से कम मेरी मशीन पर, एक सरणी में कॉपी करना अच्छी तरह से कैश प्रदर्शन को बेहतर बनाने के लिए लायक है, क्योंकि आपके पास वास्तविक जीवन में पूरी तरह से पैक की गई सूची है। यह ध्यान दिया जाना चाहिए कि मेरी मशीन में 2.8GHz फिनोम II है, लेकिन केवल 0.6GHz रैम है, इसलिए कैश बहुत महत्वपूर्ण है।
तुलना प्रकार (अर्थात तत्वों की तुलना के आधार पर) संभवतः इससे अधिक तेज़ नहीं हो सकते n log n
। इससे कोई फर्क नहीं पड़ता कि अंतर्निहित डेटा संरचना क्या है। विकिपीडिया देखें ।
अन्य प्रकार के प्रकार जो सूची में समान तत्वों के बहुत सारे होने का लाभ उठाते हैं (जैसे कि गिनती सॉर्ट), या सूची में तत्वों के कुछ अपेक्षित वितरण तेजी से होते हैं, हालांकि मैं किसी भी काम के बारे में विशेष रूप से अच्छी तरह से नहीं सोच सकता हूं एक लिंक्ड सूची पर।
यह इस विषय पर एक अच्छा सा पेपर है। उनका अनुभवजन्य निष्कर्ष यह है कि ट्रीसोर्ट सबसे अच्छा है, इसके बाद क्विकॉर्ट और मर्जेसॉर्ट हैं। तलछट प्रकार, बुलबुला प्रकार, चयन प्रकार बहुत खराब प्रदर्शन करते हैं।
चिंग-कुआंग शीन द्वारा लिंक किए गए लिस्ट के प्रकार का एक समग्र अध्ययन
http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.31.9981
जैसा कि कई बार कहा गया है, सामान्य डेटा के लिए तुलना आधारित छँटाई पर कम ओ (एन लॉग एन) होने जा रहा है। संक्षेप में इन तर्कों को फिर से शुरू करने के लिए, n हैं! अलग-अलग तरीकों से एक सूची को सॉर्ट किया जा सकता है। किसी भी प्रकार के तुलनात्मक पेड़ जो n है! (जो O (n ^ n) में है) अंतिम रूप में संभव है कि इसकी ऊंचाई के रूप में कम से कम लॉग (n!) की आवश्यकता है: यह आपको O (लॉग (n ^ n)) निम्न बाउंड देता है, जो O (n) है लॉग एन)।
इसलिए, एक लिंक्ड सूची में सामान्य डेटा के लिए, किसी भी डेटा पर काम करने वाला सबसे अच्छा संभव प्रकार जो दो वस्तुओं की तुलना कर सकता है, वह ओ (एन लॉग एन) है। हालाँकि, यदि आपके पास काम करने के लिए अधिक सीमित डोमेन है, तो आप इसमें लगने वाले समय (कम से कम आनुपातिक से n) में सुधार कर सकते हैं। उदाहरण के लिए, यदि आप पूर्णांकों के साथ काम कर रहे हैं तो कुछ मूल्य से अधिक नहीं, आप काउंटिंग सॉर्ट या रेडिक्स सॉर्ट का उपयोग कर सकते हैं , क्योंकि ये उन विशिष्ट वस्तुओं का उपयोग करते हैं जिन्हें आप n के अनुपात से जटिलता को कम करने के लिए छांट रहे हैं। हालांकि, सावधान रहें, ये कुछ अन्य चीजों को जटिलता में जोड़ते हैं जिन्हें आप विचार नहीं कर सकते हैं (उदाहरण के लिए, काउंटिंग सॉर्ट और रेडिक्स सॉर्ट दोनों उन कारकों में जोड़ते हैं जो आपके द्वारा सॉर्ट किए जा रहे नंबरों के आकार के आधार पर होते हैं, ओ (एन + के) ) जहां k उदाहरण के लिए काउंटिंग सॉर्ट के लिए सबसे बड़ी संख्या का आकार है)।
इसके अलावा, यदि आपके पास ऐसी वस्तुएं हैं जिनके पास एक आदर्श हैश (या कम से कम एक हैश है जो सभी मूल्यों को अलग-अलग रूप से मैप करता है), तो आप उनके हैश कार्यों पर एक गिनती या मूलांक प्रकार का उपयोग करने का प्रयास कर सकते हैं।
एक रेडिक्स सॉर्ट विशेष रूप से एक लिंक की गई सूची के अनुकूल है, क्योंकि एक अंक के प्रत्येक संभावित मूल्य के अनुरूप हेड पॉइंटर्स की तालिका बनाना आसान है।
मर्ज सॉर्ट में O (1) एक्सेस की आवश्यकता नहीं है और O (n ln n) है। सामान्य डेटा को सॉर्ट करने के लिए कोई भी ज्ञात एल्गोरिदम O (n ln n) से बेहतर नहीं है।
विशेष डेटा एल्गोरिदम जैसे कि मूलांक सॉर्ट (डेटा की सीमा आकार) या हिस्टोग्राम सॉर्ट (असतत डेटा की गणना) कम वृद्धि फ़ंक्शन के साथ एक लिंक की गई सूची को सॉर्ट कर सकता है, जब तक कि आप ओ (1) के साथ एक अलग संरचना का उपयोग अस्थायी भंडारण के रूप में करते हैं। ।
विशेष डेटा का एक अन्य वर्ग क्रम से बाहर के तत्वों के साथ एक लगभग क्रमबद्ध सूची की तुलना है। इसे O (kn) संचालन में हल किया जा सकता है।
सूची को किसी सरणी में और वापस कॉपी करना O (N) होगा, इसलिए किसी भी प्रकार के एल्गोरिथ्म का उपयोग किया जा सकता है यदि स्थान कोई समस्या नहीं है।
उदाहरण के लिए, एक लिंक वाली सूची दी गई है uint_8
, यह कोड ओ (एन) समय में एक हिस्टोग्राम सॉर्ट का उपयोग करके इसे सॉर्ट करेगा:
#include <stdio.h>
#include <stdint.h>
#include <malloc.h>
typedef struct _list list_t;
struct _list {
uint8_t value;
list_t *next;
};
list_t* sort_list ( list_t* list )
{
list_t* heads[257] = {0};
list_t* tails[257] = {0};
// O(N) loop
for ( list_t* it = list; it != 0; it = it -> next ) {
list_t* next = it -> next;
if ( heads[ it -> value ] == 0 ) {
heads[ it -> value ] = it;
} else {
tails[ it -> value ] -> next = it;
}
tails[ it -> value ] = it;
}
list_t* result = 0;
// constant time loop
for ( size_t i = 255; i-- > 0; ) {
if ( tails[i] ) {
tails[i] -> next = result;
result = heads[i];
}
}
return result;
}
list_t* make_list ( char* string )
{
list_t head;
for ( list_t* it = &head; *string; it = it -> next, ++string ) {
it -> next = malloc ( sizeof ( list_t ) );
it -> next -> value = ( uint8_t ) * string;
it -> next -> next = 0;
}
return head.next;
}
void free_list ( list_t* list )
{
for ( list_t* it = list; it != 0; ) {
list_t* next = it -> next;
free ( it );
it = next;
}
}
void print_list ( list_t* list )
{
printf ( "[ " );
if ( list ) {
printf ( "%c", list -> value );
for ( list_t* it = list -> next; it != 0; it = it -> next )
printf ( ", %c", it -> value );
}
printf ( " ]\n" );
}
int main ( int nargs, char** args )
{
list_t* list = make_list ( nargs > 1 ? args[1] : "wibble" );
print_list ( list );
list_t* sorted = sort_list ( list );
print_list ( sorted );
free_list ( list );
}
O(n lg n)
तुलना-आधारित नहीं होगा (जैसे, मूलांक क्रमांक)। परिभाषा के अनुसार, तुलना क्रम किसी भी डोमेन पर लागू होता है जिसमें कुल ऑर्डर होता है (यानी, तुलना की जा सकती है)।
आपके प्रश्न का सीधा उत्तर नहीं है, लेकिन यदि आप एक स्किप सूची का उपयोग करते हैं , तो यह पहले से ही क्रमबद्ध है और इसमें O (log N) खोज समय है।
O(lg N)
खोज समय - लेकिन गारंटीकृत नहीं है, क्योंकि स्किप सूची यादृच्छिकता पर निर्भर करती है। यदि आप अविश्वासित इनपुट प्राप्त कर रहे हैं, तो सुनिश्चित करें कि इनपुट का आपूर्तिकर्ता आपके RNG की भविष्यवाणी नहीं कर सकता है, या वे आपको डेटा भेज सकते हैं जो इसके सबसे खराब मामले के प्रदर्शन को ट्रिगर करता है
जैसा कि मुझे पता है, सबसे अच्छा सॉर्टिंग एल्गोरिथ्म है ओ (एन * लॉग एन), जो भी कंटेनर है - यह साबित हो गया है कि शब्द के व्यापक अर्थों में सॉर्ट करना (मर्जेसर्ट / क्विकॉर्ट आदि शैली) कम नहीं हो सकता है। लिंक की गई सूची का उपयोग करने से आपको बेहतर रन समय नहीं मिलेगा।
केवल एक एल्गोरिथ्म जो ओ (एन) में चलता है, एक "हैक" एल्गोरिथ्म है जो वास्तव में छंटनी के बजाय गिनती मूल्यों पर निर्भर करता है।
O(n lg c)
। यदि आपके सभी तत्व अद्वितीय हैं, तो c >= n
, और इसलिए इसे अधिक समय लगता है O(n lg n)
।
यहां एक कार्यान्वयन है जो सूची को केवल एक बार ट्रेस करता है, रन एकत्र करता है, फिर उसी तरह मर्ज को शेड्यूल करता है जिस तरह से मर्ज करें।
जटिलता हे (एन लॉग एम) जहां n वस्तुओं की संख्या है और एम रन की संख्या है। सबसे अच्छा मामला O (n) है (यदि डेटा पहले से ही सॉर्ट किया गया है) और सबसे खराब स्थिति उम्मीद के मुताबिक O (n log n) है।
इसके लिए ओ (लॉग एम) अस्थायी मेमोरी की आवश्यकता होती है; सॉर्ट सूची में जगह में किया जाता है।
(नीचे अद्यतन। टिप्पणीकार एक अच्छी बात यह है कि मुझे यहाँ इसका वर्णन करना चाहिए)
एल्गोरिथ्म का सार है:
while list not empty
accumulate a run from the start of the list
merge the run with a stack of merges that simulate mergesort's recursion
merge all remaining items on the stack
रनिंग रनिंग के लिए बहुत स्पष्टीकरण की आवश्यकता नहीं होती है, लेकिन आरोही रन और अवरोही रन (घूमना) दोनों को संचित करने का अवसर लेना अच्छा है। यहाँ यह रन के प्रमुख की तुलना में छोटी वस्तुओं को प्रस्तुत करता है और रन के अंत से अधिक या इसके बराबर आइटम जोड़ता है। (ध्यान दें कि छंटनी की स्थिरता को बनाए रखने के लिए पहले से कम सख्त का उपयोग करना चाहिए।)
यहां सिर्फ मर्जिंग कोड पेस्ट करना आसान है:
int i = 0;
for ( ; i < stack.size(); ++i) {
if (!stack[i])
break;
run = merge(run, stack[i], comp);
stack[i] = nullptr;
}
if (i < stack.size()) {
stack[i] = run;
} else {
stack.push_back(run);
}
सूची को छाँटने पर विचार करें (dagibecfjh) (रन की अनदेखी)। स्टैक स्थिति इस प्रकार आगे बढ़ती है:
[ ]
[ (d) ]
[ () (a d) ]
[ (g), (a d) ]
[ () () (a d g i) ]
[ (b) () (a d g i) ]
[ () (b e) (a d g i) ]
[ (c) (b e) (a d g i ) ]
[ () () () (a b c d e f g i) ]
[ (j) () () (a b c d e f g i) ]
[ () (h j) () (a b c d e f g i) ]
फिर, अंत में इन सभी सूचियों को मर्ज करें।
ध्यान दें कि स्टैक [i] पर वस्तुओं की संख्या (रन) या तो शून्य या 2 ^ i है और स्टैक का आकार 1 + log2 (nruns) से घिरा है। प्रत्येक तत्व को स्टैक स्तर के अनुसार एक बार मर्ज किया जाता है, इसलिए ओ (एन लॉग एम) तुलना। यहाँ Timsort के लिए एक समान समानता है, हालांकि Timsort एक फिबोनाची अनुक्रम की तरह कुछ का उपयोग करके अपने ढेर को बनाए रखता है जहां यह दो की शक्तियों का उपयोग करता है।
संचित रन किसी भी पहले से ही सॉर्ट किए गए डेटा का लाभ उठाते हैं ताकि पहले से ही सॉर्ट की गई सूची (एक रन) के लिए सबसे अच्छा मामला जटिलता ओ (एन) हो। चूँकि हम आरोही और अवरोही दोनों तरह के रन बना रहे हैं, रन हमेशा कम से कम लंबाई के होंगे 2. (यह कम से कम एक से अधिकतम स्टैक की गहराई कम कर देता है, पहले स्थान पर रन खोजने की लागत के लिए भुगतान करना।) सबसे खराब स्थिति जटिलता। ओ (एन लॉग एन), जैसा कि अपेक्षित है, डेटा के लिए जो अत्यधिक यादृच्छिक है।
(उम ... दूसरा अपडेट)
या बस नीचे-ऊपर मर्जोर्ट पर विकिपीडिया देखें ।
O(log m)
अतिरिक्त मेमोरी की आवश्यकता नहीं होनी चाहिए - बस एक खाली होने तक वैकल्पिक रूप से दो सूची में रन जोड़ें।
आप इसे एक सरणी में कॉपी कर सकते हैं और फिर उसे सॉर्ट कर सकते हैं।
सरणी O (n) में प्रतिलिपि बनाते हुए,
सॉर्टिंग O (nlgn) (यदि आप मर्ज सॉर्ट जैसे तेज़ एल्गोरिथम का उपयोग करते हैं),
यदि आवश्यक हो, तो वापस लिंक की गई सूची O (n) में कॉपी करना
तो यह O (nlgn) होने वाला है।
ध्यान दें कि यदि आपको लिंक की गई सूची में तत्वों की संख्या नहीं पता है तो आपको सरणी का आकार नहीं पता होगा। यदि आप जावा में कोडिंग कर रहे हैं, तो आप उदाहरण के लिए एक Arraylist का उपयोग कर सकते हैं।
Mergesort सबसे अच्छा आप यहाँ कर सकते हैं।
सवाल है लेटकोड # 148 , और सभी प्रमुख भाषाओं में बहुत सारे समाधान दिए गए हैं। मेरा इस प्रकार है, लेकिन मैं समय जटिलता के बारे में सोच रहा हूं। मध्य तत्व को खोजने के लिए, हम हर बार पूरी सूची का पता लगाते हैं। पहली बार n
तत्वों को पुनरावृत्त किया जाता है, दूसरी बार 2 * n/2
तत्वों को पुनरावृत्त किया जाता है, इसलिए आगे और आगे। O(n^2)
समय लगता है ।
def sort(linked_list: LinkedList[int]) -> LinkedList[int]:
# Return n // 2 element
def middle(head: LinkedList[int]) -> LinkedList[int]:
if not head or not head.next:
return head
slow = head
fast = head.next
while fast and fast.next:
slow = slow.next
fast = fast.next.next
return slow
def merge(head1: LinkedList[int], head2: LinkedList[int]) -> LinkedList[int]:
p1 = head1
p2 = head2
prev = head = None
while p1 and p2:
smaller = p1 if p1.val < p2.val else p2
if not head:
head = smaller
if prev:
prev.next = smaller
prev = smaller
if smaller == p1:
p1 = p1.next
else:
p2 = p2.next
if prev:
prev.next = p1 or p2
else:
head = p1 or p2
return head
def merge_sort(head: LinkedList[int]) -> LinkedList[int]:
if head and head.next:
mid = middle(head)
mid_next = mid.next
# Makes it easier to stop
mid.next = None
return merge(merge_sort(head), merge_sort(mid_next))
else:
return head
return merge_sort(linked_list)