लिंक की गई सूची को सॉर्ट करने के लिए सबसे तेज़ एल्गोरिथम क्या है?


95

मैं उत्सुक हूँ अगर O (n लॉग एन) सबसे अच्छी तरह से जुड़ा हुआ सूची है।


31
बस इतना है कि आप जानते हैं, O (nlogn) तुलना आधारित प्रकारों के लिए बाध्य है। गैर-तुलना आधारित प्रकार हैं जो ओ (एन) प्रदर्शन (जैसे गिनती प्रकार) दे सकते हैं, लेकिन उन्हें डेटा पर अतिरिक्त बाधाओं की आवश्यकता होती है।
MAK

वे दिन थे जब सवालों के विपरीत "क्यों यह कोड काम नहीं करता है ?????" एसओ पर स्वीकार्य थे।
अभिजीत सरकार

जवाबों:


100

यह अपेक्षा करना उचित है कि आप रनिंग टाइम में O (N log N) से बेहतर कुछ नहीं कर सकते ।

हालांकि, दिलचस्प हिस्सा यह जांचना है कि क्या आप इसे जगह में , निश्चित रूप से , इसके सबसे खराब मामले और इतने पर सॉर्ट कर सकते हैं ।

पुट्टी की प्रसिद्धि के साइमन टाथम बताते हैं कि मर्ज के साथ एक लिंक्ड सूची को कैसे सॉर्ट करना है । उन्होंने निम्नलिखित टिप्पणियों के साथ निष्कर्ष निकाला:

किसी भी स्वाभिमानी सॉर्ट एल्गोरिथ्म की तरह, इसमें ओ (एन लॉग एन) का समय चल रहा है। क्योंकि यह मर्जेसर्ट है, सबसे खराब चल रहा समय अभी भी ओ (एन लॉग एन) है; कोई रोग संबंधी मामले नहीं हैं।

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

सी में एक उदाहरण कार्यान्वयन भी है जो एकल और दोगुनी लिंक्ड सूची के लिए काम करता है।

जैसा कि @ जोर्गेन फॉग ने नीचे उल्लेख किया है, बिग-ओ नोटेशन कुछ स्थिर कारकों को छिपा सकता है जो स्मृति स्थानीयता के कारण बेहतर प्रदर्शन करने के लिए एक एल्गोरिथ्म का कारण बन सकता है, क्योंकि वस्तुओं की कम संख्या, आदि।


3
यह एकल लिंक्ड सूची के लिए नहीं है। उसका C कोड * prev और * अगले का उपयोग कर रहा है।
LE

3
@ यह वास्तव में दोनों के लिए है । यदि आप के लिए हस्ताक्षर देखते हैं listsort, तो आप देखेंगे कि आप पैरामीटर का उपयोग करके स्विच कर सकते हैं int is_double
सीएसएल

1
@ एलईई: यहां सी कोड का एक पायथन संस्करण है जो listsortकेवल एकल-लिंक की गई सूचियों का समर्थन करता है
jfs

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

74

कई कारकों के आधार पर, यह वास्तव में सूची को किसी सरणी में कॉपी करने और फिर क्विकॉर्ट का उपयोग करने के लिए तेज़ हो सकता है ।

इसका कारण यह तेज़ हो सकता है कि किसी सरणी में लिंक की गई सूची की तुलना में बेहतर कैश प्रदर्शन है। यदि सूची में नोड्स मेमोरी में बिखरे हुए हैं, तो आप सभी जगह कैश मिस उत्पन्न कर सकते हैं। फिर, यदि सरणी बड़ी है, तो आपको कैश मिसेज़ वैसे भी मिलेंगे।

मर्जेसर्ट बेहतर तरीके से समानांतर करता है, इसलिए यदि आप चाहते हैं तो यह एक बेहतर विकल्प हो सकता है। यदि आप सीधे लिंक की गई सूची पर इसे निष्पादित करते हैं तो यह बहुत तेज़ है।

चूंकि दोनों एल्गोरिदम ओ (एन * लॉग एन) में चलते हैं, इसलिए एक सूचित निर्णय लेने से आपको उन दोनों मशीन पर प्रोफाइलिंग करना शामिल होगा जिन्हें आप उन्हें चलाना चाहते हैं।

--- संपादित करें

मैंने अपनी परिकल्पना का परीक्षण करने का निर्णय लिया और एक सी-प्रोग्राम लिखा, जिसने 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 रैम है, इसलिए कैश बहुत महत्वपूर्ण है।


2
अच्छी टिप्पणियाँ, लेकिन आपको सूची से डेटा को एक सरणी में कॉपी करने की गैर-निरंतर लागत पर विचार करना चाहिए (आपको सूची को पीछे करना होगा), साथ ही क्विकॉर्ट के लिए सबसे खराब समय चल रहा है।
सीएसएल

1
O (n * log n) सैद्धांतिक रूप से O (n * log n + n) के समान है, जो प्रतिलिपि की लागत सहित होगा। किसी भी पर्याप्त रूप से बड़े एन के लिए, प्रतिलिपि की लागत वास्तव में मायने नहीं रखती है; एक बार के अंत में एक सूची traversing n समय होना चाहिए।
डीन जे

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

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

2
@Steve: आप सही कह रहे हैं कि qsort एक ड्रॉप-इन प्रतिस्थापन नहीं है, लेकिन मेरी बात वास्तव में qsort बनाम मर्ज़ॉर्ट के बारे में नहीं है। मैं बस मर्ज के दूसरे संस्करण को लिखने की तरह महसूस नहीं करता था जब qsort आसानी से उपलब्ध था। मानक पुस्तकालय अपने तरीके से रोल करने की तुलना में अधिक सुविधाजनक है।
जोर्जेन फॉग

8

तुलना प्रकार (अर्थात तत्वों की तुलना के आधार पर) संभवतः इससे अधिक तेज़ नहीं हो सकते n log n। इससे कोई फर्क नहीं पड़ता कि अंतर्निहित डेटा संरचना क्या है। विकिपीडिया देखें ।

अन्य प्रकार के प्रकार जो सूची में समान तत्वों के बहुत सारे होने का लाभ उठाते हैं (जैसे कि गिनती सॉर्ट), या सूची में तत्वों के कुछ अपेक्षित वितरण तेजी से होते हैं, हालांकि मैं किसी भी काम के बारे में विशेष रूप से अच्छी तरह से नहीं सोच सकता हूं एक लिंक्ड सूची पर।


8

यह इस विषय पर एक अच्छा सा पेपर है। उनका अनुभवजन्य निष्कर्ष यह है कि ट्रीसोर्ट सबसे अच्छा है, इसके बाद क्विकॉर्ट और मर्जेसॉर्ट हैं। तलछट प्रकार, बुलबुला प्रकार, चयन प्रकार बहुत खराब प्रदर्शन करते हैं।

चिंग-कुआंग शीन द्वारा लिंक किए गए लिस्ट के प्रकार का एक समग्र अध्ययन

http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.31.9981


5

जैसा कि कई बार कहा गया है, सामान्य डेटा के लिए तुलना आधारित छँटाई पर कम ओ (एन लॉग एन) होने जा रहा है। संक्षेप में इन तर्कों को फिर से शुरू करने के लिए, n हैं! अलग-अलग तरीकों से एक सूची को सॉर्ट किया जा सकता है। किसी भी प्रकार के तुलनात्मक पेड़ जो n है! (जो O (n ^ n) में है) अंतिम रूप में संभव है कि इसकी ऊंचाई के रूप में कम से कम लॉग (n!) की आवश्यकता है: यह आपको O (लॉग (n ^ n)) निम्न बाउंड देता है, जो O (n) है लॉग एन)।

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

इसके अलावा, यदि आपके पास ऐसी वस्तुएं हैं जिनके पास एक आदर्श हैश (या कम से कम एक हैश है जो सभी मूल्यों को अलग-अलग रूप से मैप करता है), तो आप उनके हैश कार्यों पर एक गिनती या मूलांक प्रकार का उपयोग करने का प्रयास कर सकते हैं।


3

एक रेडिक्स सॉर्ट विशेष रूप से एक लिंक की गई सूची के अनुकूल है, क्योंकि एक अंक के प्रत्येक संभावित मूल्य के अनुरूप हेड पॉइंटर्स की तालिका बनाना आसान है।


1
क्या आप कृपया इस विषय पर अधिक व्याख्या कर सकते हैं या लिंक की गई सूची में मूलांक के लिए कोई संसाधन लिंक दे सकते हैं।
LoveToCode

2

मर्ज सॉर्ट में 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 );
}

5
यह साबित हो गया है कि कोई तुलना-आधारित सॉर्ट अल्ग्रेम्स मौजूद नहीं हैं जो एन लॉग एन की तुलना में तेज़ हैं।
आर्टिलियस

9
नहीं, यह साबित हो गया है कि सामान्य डेटा पर कोई तुलना-आधारित सॉर्ट एल्गोरिदम एन लॉग एन की तुलना में तेज़ हैं
पीट किर्कम

नहीं, किसी भी प्रकार का एल्गोरिथ्म तेजी से O(n lg n)तुलना-आधारित नहीं होगा (जैसे, मूलांक क्रमांक)। परिभाषा के अनुसार, तुलना क्रम किसी भी डोमेन पर लागू होता है जिसमें कुल ऑर्डर होता है (यानी, तुलना की जा सकती है)।
बोन्डलन

3
@bdonlan "सामान्य डेटा" की बात यह है कि ऐसे एल्गोरिदम हैं जो यादृच्छिक इनपुट के बजाय विवश इनपुट के लिए तेज़ हैं। सीमित करने के मामले में, आप एक तुच्छ हे (1) एल्गोरिथ्म लिख सकते हैं जो इनपुट डेटा दिए गए एक सूची को छांटता है, जो पहले से ही हल होने के लिए विवश है
पीट किर्कम

और यह तुलना-आधारित प्रकार नहीं होगा। संशोधक "सामान्य डेटा पर" बेमानी है, क्योंकि तुलनात्मक रूप से पहले से ही सामान्य डेटा को संभालता है (और बिग-ओ नोटेशन की तुलना की गई संख्या के लिए है)।
स्टीव जेसप

1

आपके प्रश्न का सीधा उत्तर नहीं है, लेकिन यदि आप एक स्किप सूची का उपयोग करते हैं , तो यह पहले से ही क्रमबद्ध है और इसमें O (log N) खोज समय है।


1
अपेक्षित O(lg N) खोज समय - लेकिन गारंटीकृत नहीं है, क्योंकि स्किप सूची यादृच्छिकता पर निर्भर करती है। यदि आप अविश्वासित इनपुट प्राप्त कर रहे हैं, तो सुनिश्चित करें कि इनपुट का आपूर्तिकर्ता आपके RNG की भविष्यवाणी नहीं कर सकता है, या वे आपको डेटा भेज सकते हैं जो इसके सबसे खराब मामले के प्रदर्शन को ट्रिगर करता है
bdonlan

1

जैसा कि मुझे पता है, सबसे अच्छा सॉर्टिंग एल्गोरिथ्म है ओ (एन * लॉग एन), जो भी कंटेनर है - यह साबित हो गया है कि शब्द के व्यापक अर्थों में सॉर्ट करना (मर्जेसर्ट / क्विकॉर्ट आदि शैली) कम नहीं हो सकता है। लिंक की गई सूची का उपयोग करने से आपको बेहतर रन समय नहीं मिलेगा।

केवल एक एल्गोरिथ्म जो ओ (एन) में चलता है, एक "हैक" एल्गोरिथ्म है जो वास्तव में छंटनी के बजाय गिनती मूल्यों पर निर्भर करता है।


3
यह एक हैक एल्गोरिथ्म नहीं है, और यह ओ (एन) में नहीं चलता है। यह O (cn) में चलता है, जहाँ c सबसे बड़ा मूल्य है जिसे आप छाँट रहे हैं (ठीक है, वास्तव में यह उच्चतम और निम्नतम मूल्यों के बीच का अंतर है) और केवल अभिन्न मूल्यों पर काम करता है। O (n) और O (cn) के बीच एक अंतर है, जब तक कि आप उन मूल्यों के लिए एक निश्चित ऊपरी सीमा नहीं दे सकते हैं जो आप छांट रहे हैं (और इस तरह इसे निरंतर द्वारा बाध्य करते हैं), आपके पास जटिलता को जटिल करने वाले दो कारक हैं।
दिव्यवुल्फवुड

कड़ाई से बोलते हुए, यह अंदर चलता है O(n lg c)। यदि आपके सभी तत्व अद्वितीय हैं, तो c >= n, और इसलिए इसे अधिक समय लगता है O(n lg n)
बोडलान

1

यहां एक कार्यान्वयन है जो सूची को केवल एक बार ट्रेस करता है, रन एकत्र करता है, फिर उसी तरह मर्ज को शेड्यूल करता है जिस तरह से मर्ज करें।

जटिलता हे (एन लॉग एम) जहां 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)अतिरिक्त मेमोरी की आवश्यकता नहीं होनी चाहिए - बस एक खाली होने तक वैकल्पिक रूप से दो सूची में रन जोड़ें।
ग्रेबर्ड

1

आप इसे एक सरणी में कॉपी कर सकते हैं और फिर उसे सॉर्ट कर सकते हैं।

  • सरणी O (n) में प्रतिलिपि बनाते हुए,

  • सॉर्टिंग O (nlgn) (यदि आप मर्ज सॉर्ट जैसे तेज़ एल्गोरिथम का उपयोग करते हैं),

  • यदि आवश्यक हो, तो वापस लिंक की गई सूची O (n) में कॉपी करना

तो यह O (nlgn) होने वाला है।

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




0

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