मर्ज सॉर्ट एल्गोरिथ्म का उपयोग करके इन-प्लेस को कैसे सॉर्ट करना है?


244

मुझे पता है कि प्रश्न बहुत विशिष्ट नहीं है।

मैं चाहता हूं कि कोई मुझे बताए कि एक सामान्य मर्ज सॉर्ट को इन-प्लेस मर्ज सॉर्ट (या निरंतर अतिरिक्त स्थान के साथ मर्ज सॉर्ट) में कैसे परिवर्तित किया जाए।

सभी मुझे मिल सकता है (नेट पर) यह कहते हुए पृष्ठ "यह बहुत जटिल है" या "इस पाठ के दायरे से बाहर"।

व्यावहारिक कार्यक्रम को कम करने के लिए इन-प्लेस (किसी भी अतिरिक्त स्थान के बिना) में विलय करने के एकमात्र ज्ञात तरीके बहुत जटिल हैं। ( यहां से लिया गया )

यहां तक ​​कि अगर यह बहुत जटिल है, तो मर्ज को इन-प्लेस बनाने की मूल अवधारणा क्या है?


अच्छा सवाल है, मैंने कल से एक प्रश्न के माध्यम से पढ़ते समय खुद से पूछा: stackoverflow.com/questions/2566459/…
क्रिस लेर्चर

यहाँ वर्णित एक काफी सरल विधि है: xinok.wordpress.com/2014/08/17/…
ब्रांको

जवाबों:


140

नुथ ने इसे एक अभ्यास के रूप में छोड़ दिया (वॉल्यूम 3, 5.2.5)। इन-प्लेस मर्ज प्रकार मौजूद हैं। उन्हें सावधानीपूर्वक लागू किया जाना चाहिए।

सबसे पहले, इन-प्लेस मर्ज जैसे कि यहाँ वर्णित सही समाधान नहीं है। यह O (N 2 ) के प्रदर्शन को डाउनग्रेड करता है ।

विलय के लिए कार्य क्षेत्र के रूप में बाकी का उपयोग करते हुए सरणी के हिस्से को सॉर्ट करना है।

उदाहरण के लिए निम्न मर्ज फ़ंक्शन की तरह।

void wmerge(Key* xs, int i, int m, int j, int n, int w) {
    while (i < m && j < n)
        swap(xs, w++, xs[i] < xs[j] ? i++ : j++);
    while (i < m)
        swap(xs, w++, i++);
    while (j < n)
        swap(xs, w++, j++);
}  

यह सरणी लेता है xs, दो क्रमबद्ध उप-सरणियों को क्रमशः [i, m)और [j, n)क्रमशः श्रेणियों के रूप में दर्शाया जाता है। कार्य क्षेत्र से शुरू होता है w। अधिकांश पाठ्यपुस्तकों में दिए गए मानक मर्ज एल्गोरिथ्म के साथ तुलना करें, यह एक सॉर्ट किए गए उप-सरणी और कार्य क्षेत्र के बीच सामग्री का आदान-प्रदान करता है। परिणामस्वरूप, पिछले कार्य क्षेत्र में मर्ज किए गए सॉर्ट किए गए तत्व होते हैं, जबकि कार्य क्षेत्र में संग्रहीत पिछले तत्वों को दो उप-सरणियों में ले जाया जाता है।

हालाँकि, दो बाधाएँ हैं जिन्हें संतुष्ट किया जाना चाहिए:

  1. कार्य क्षेत्र सरणी की सीमा के भीतर होना चाहिए। दूसरे शब्दों में, किसी भी आउट-ऑफ-बाउंड त्रुटि के बिना एक्सचेंज किए गए तत्वों को रखने के लिए यह काफी बड़ा होना चाहिए।
  2. कार्य क्षेत्र को दो सॉर्ट किए गए सरणियों के साथ ओवरलैप किया जा सकता है; हालाँकि, यह सुनिश्चित करना चाहिए कि कोई भी अपरिष्कृत तत्व ओवरराइट न हों।

इस विलय एल्गोरिथ्म के साथ परिभाषित किया गया है, एक समाधान की कल्पना करना आसान है, जो सरणी के आधे हिस्से को सॉर्ट कर सकता है; अगला सवाल यह है कि कार्य क्षेत्र में संग्रहीत शेष भाग के शेष भाग से कैसे निपटा जाए:

... unsorted 1/2 array ... | ... sorted 1/2 array ...

एक सहज ज्ञान युक्त कार्य क्षेत्र के एक और आधे को पुनरावर्ती करना है, इस प्रकार केवल 1/4 तत्व अभी तक सॉर्ट नहीं किए गए हैं।

... unsorted 1/4 array ... | sorted 1/4 array B | sorted 1/2 array A ...

इस स्तर पर मुख्य बिंदु यह है कि हमें क्रमबद्ध 1/4 तत्वों B को क्रमबद्ध 1/2 तत्वों A के साथ शीघ्र या बाद में विलय करना होगा।

क्या कार्य क्षेत्र बचा हुआ है, जो केवल 1/4 तत्वों को रखता है, ए और बी को विलय करने के लिए पर्याप्त है? दुर्भाग्य से, यह नहीं है।

हालाँकि, ऊपर उल्लिखित दूसरा अवरोध हमें संकेत देता है, कि यदि हम विलय के अनुक्रम को सुनिश्चित कर सकते हैं कि अनर्जित तत्वों को अधिलेखित नहीं किया जाएगा, तो हम उप-सरणी के साथ ओवरलैप करने के लिए कार्य क्षेत्र की व्यवस्था करके इसका फायदा उठा सकते हैं।

वास्तव में, कार्य क्षेत्र के दूसरे भाग को छाँटने के बजाय, हम पहले आधे को छाँट सकते हैं, और इस तरह दो हल किए गए सरणियों के बीच कार्य क्षेत्र डाल सकते हैं:

... sorted 1/4 array B | unsorted work area | ... sorted 1/2 array A ...

यह सेटअप प्रभावी रूप से उप-सरणी ए के साथ कार्य क्षेत्र को ओवरलैप करने की व्यवस्था करता है। यह विचार [जिरकी कटजैन, टोमी पासानन, जुका तेहुला में प्रस्तावित है। `` प्रैक्टिकल-प्लेस मर्जर्ट ''। कम्प्यूटिंग जर्नल ऑफ नॉर्डिक, 1996]।

तो केवल एक चीज बची हुई है, उपरोक्त चरण को दोहराना, जो कि कार्य क्षेत्र को 1/2, 1/4, 1/8 से कम करता है, ... जब कार्य क्षेत्र काफी छोटा हो जाता है (उदाहरण के लिए, केवल दो तत्व बचे हैं), हम कर सकते हैं इस एल्गोरिथ्म को समाप्त करने के लिए एक तुच्छ प्रविष्टि सॉर्ट पर स्विच करें।

इस पेपर के आधार पर एएनएसआई सी में कार्यान्वयन इस प्रकार है।

void imsort(Key* xs, int l, int u);

void swap(Key* xs, int i, int j) {
    Key tmp = xs[i]; xs[i] = xs[j]; xs[j] = tmp;
}

/* 
 * sort xs[l, u), and put result to working area w. 
 * constraint, len(w) == u - l
 */
void wsort(Key* xs, int l, int u, int w) {
    int m;
    if (u - l > 1) {
        m = l + (u - l) / 2;
        imsort(xs, l, m);
        imsort(xs, m, u);
        wmerge(xs, l, m, m, u, w);
    }
    else
        while (l < u)
            swap(xs, l++, w++);
}

void imsort(Key* xs, int l, int u) {
    int m, n, w;
    if (u - l > 1) {
        m = l + (u - l) / 2;
        w = l + u - m;
        wsort(xs, l, m, w); /* the last half contains sorted elements */
        while (w - l > 2) {
            n = w;
            w = l + (n - l + 1) / 2;
            wsort(xs, w, n, l);  /* the first half of the previous working area contains sorted elements */
            wmerge(xs, l, l + n - w, n, u, w);
        }
        for (n = w; n > l; --n) /*switch to insertion sort*/
            for (m = n; m < u && xs[m] < xs[m-1]; ++m)
                swap(xs, m, m - 1);
    }
}

जहां wmerge को पहले से परिभाषित किया गया है।

पूर्ण स्रोत कोड यहां पाया जा सकता है और विस्तृत विवरण यहां पाया जा सकता है

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


6
Knuth left this as an exercise (Vol 3, 5.2.5).पूर्व को संदर्भित करता है। 13. [40] [इस खंड के करीब] सुझाई गई आंतरिक छँटाई विधि को लागू करें, जो केवल O (sqrt (N)) अतिरिक्त मेमोरी स्थानों पर O (N) इकाइयों में यादृच्छिक डेटा को सॉर्ट करता है ? ( 40 काफी कठिन या लंबी समस्या को इंगित करता है जो संभवतः कक्षा की स्थितियों में एक टर्म प्रोजेक्ट के रूप में उपयुक्त है। )
ग्रेबियर्ड

4
मुझे लगता है कि पेंगुइन इन साइट में उल्लिखित इन-प्लेस एल्गोरिथ्म की समय-जटिलता हे (लॉग एन * ^ ^ 2) है। यदि हमारे पास लॉग एन मर्ज है और प्रत्येक मर्ज ऑर्डर ओ (एन ^ 2) का है। है ना?
कोड 4 फन

1
क्या यह एल्गोरिथ्म अभी भी स्थिर है और एन लॉग एन समय में है?
पॉल स्टेलियन

1
@PaulStelian - यह स्थिर नहीं है। कार्य क्षेत्र में तत्वों को क्रमबद्ध क्षेत्र में तत्वों पर आदेश संचालन के अनुसार पुनर्व्यवस्थित किया जाता है। इसका मतलब यह है कि समान मान वाले कार्यशील तत्वों को फिर से व्यवस्थित किया जाएगा ताकि वे अब अपने मूल क्रम में न हों।
rcgldr

1
@PaulStelian - विकी में ब्लॉक मर्ज सॉर्ट के लिए एक लेख है , जैसा कि आपने टिप्पणी की थी कि वह स्थिर है। यह सबसे अच्छा काम करता है अगर कम से कम 2 · sqrt (n) अद्वितीय मूल्य हैं, जो उन्हें एक सरणी के कार्य क्षेत्रों को प्रदान करने और स्थिर रहने के लिए फिर से आदेश दिया जाता है।
rcgldr

59

इसके "बड़े परिणाम" को शामिल करते हुए, इस पत्र में इन-प्लेस मर्ज सॉर्ट (पीडीएफ) के कुछ रूपों का वर्णन किया गया है:

http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.22.5514&rep=rep1&type=pdf

कम चाल के साथ जगह में छँटाई

जिरकी कटजैनें, तोमी ए पासनें

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

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

http://comjnl.oxfordjournals.org/cgi/content/abstract/38/8/681

इष्टतम स्थिर विलय

एंटोनियोस सिमोविनिस

यह कागज दिखाता है कि ओ और (एम + एन) असाइनमेंट्स के साथ, ओ (mlog (n / m + 1)) तुलना और केवल एक स्थिरांक का उपयोग करके, क्रमशः m और n, m, n, के दो अनुक्रम A और B को कैसे मर्ज किया जाए। अतिरिक्त स्थान की मात्रा। यह परिणाम सभी ज्ञात निम्न सीमाओं से मेल खाता है ...


12

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

वैसे भी, अगर आपको यह करना चाहिए तो आपको करना चाहिए। यहाँ मैं क्या पाया: एक और दो । मैं इनलेट मर्ज सॉर्ट से परिचित नहीं हूं, लेकिन ऐसा लगता है कि मूल विचार अतिरिक्त मेमोरी का उपयोग किए बिना दो सरणियों को मर्ज करने की सुविधा के लिए घुमाव का उपयोग करना है।

ध्यान दें कि यह क्लासिक मर्ज सॉर्ट की तुलना में भी धीमा है जो कि इनहेल नहीं है।


9
Quicksort स्थिर नहीं है। यह वास्तव में बहुत सारे उत्पादन कोड के लिए मायने रखता है।
डोनल फैलो

7
quicksort स्थिर हो सकता है, और iirc मर्ज सॉर्ट आवश्यक नहीं है कि अगर जगह
jk

4
@jk: क्विकॉर्ट स्थिर नहीं है; यह गति उससे प्राप्त होती है और आपको अन्यथा दावा करने का प्रयास नहीं करना चाहिए। यह बहुत अच्छा व्यापार है। हां, मूल कुंजी को शेष के साथ जोड़ना संभव है, ताकि आपको दो तत्व समान न मिलें, एक स्थिर क्रम दे; यह कुछ अतिरिक्त स्थान (तत्वों की संख्या में रैखिक) की एक आवश्यक लागत पर आता है क्योंकि आप अतिरिक्त तत्व आंदोलनों का सहारा लिए बिना "समतुल्य" तत्वों के सापेक्ष क्रम को बनाए नहीं रख सकते हैं जो प्रदर्शन को नष्ट करते हैं।
डोनाल्ड फेलो

4
क्विकॉर्ट के पास विशेष रूप से तैयार किए गए इनपुट के लिए ओ (एन ^ 2) सबसे खराब स्थिति है
हॉबोबेन

4
@ डोनल फेलो: jk बिलकुल सही है; quicksort अतिरिक्त स्थान लागत के बिना, स्थिर होने के लिए लागू किया जा सकता है। विकिपीडिया की जाँच करें।
जंग लगी

10

क्रिटिकल स्टेप को मर्ज होने की जगह मिल रही है। यह उतना मुश्किल नहीं है जितना कि वे स्रोत बनाते हैं, लेकिन जब आप कोशिश करते हैं तो आप कुछ खो देते हैं।

मर्ज के एक चरण को देखते हुए:

[... सूची- क्रमबद्ध ... | x ... सूची- A ... | y ... सूची- B ...]

हम जानते हैं कि क्रमबद्ध अनुक्रम बाकी सब से कम है, कि एक्स में सब कुछ से भी कम है एक , और कहा कि y में सब कुछ से भी कम है बी । ऐसे मामले में जहां x , y से कम या बराबर है , आप अपने पॉइंटर को एक पर A की शुरुआत में ले जाते हैं । इस मामले में जहां में वाई से भी कम है x , आप शफ़ल करने के लिए मिल गया है y के पूरे अतीत एक को हल कर । यह अंतिम चरण है जो इसे महंगा बनाता है (पतित मामलों को छोड़कर)।

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


5
आपके इन-प्लेस मर्ज में O (m n) सबसे खराब स्थिति है, जहाँ m A आकार है, और n B आकार है। यह वह स्थिति है जब A में पहला आइटम B. में अंतिम आइटम से बड़ा होता है। जटिलता को O (k log (k) + m + n) में सुधारा जा सकता है , जहाँ k = min (m, n) जोड़कर ए और बी के बीच ढेर। इस ढेर में ए से आइटम होना चाहिए, जो बी में शेष वस्तुओं से बड़ा है, लेकिन ए में शेष वस्तुओं की तुलना में छोटा है। यदि ए पहले समाप्त हो गया है, तो ढेर को बी के अंत में ले जाना चाहिए। अन्यथा ढेर को ए की शुरुआत में ले जाया जाना चाहिए। फिर ढेर आइटम को जगह में पॉप आउट करना चाहिए और मर्ज को पूरा करने के लिए उलट करना चाहिए।
वलयाला

2
@valyala ध्यान दें कि ढेर का उपयोग करते समय, सॉर्ट अब स्थिर नहीं है। इसके अलावा, यदि आप एक ढेर का उपयोग करते हैं, तो आप मर्ज सॉर्ट के बजाय हीप सॉर्ट के साथ जा सकते हैं।
Martinkunev

8

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

मैंने एक स्थिर इन-प्लेस मर्ज सॉर्ट और जावा में एक स्थिर इन-प्लेस एस्कॉर्ट को लागू करने को समाप्त कर दिया । कृपया ध्यान दें कि जटिलता हे (एन (लॉग एन) ^ 2)


4

सी में बफरलेस मर्जॉर्ट का एक उदाहरण।

#define SWAP(type, a, b) \
    do { type t=(a);(a)=(b);(b)=t; } while (0)

static void reverse_(int* a, int* b)
{
    for ( --b; a < b; a++, b-- )
       SWAP(int, *a, *b);
}
static int* rotate_(int* a, int* b, int* c)
/* swap the sequence [a,b) with [b,c). */
{
    if (a != b && b != c)
     {
       reverse_(a, b);
       reverse_(b, c);
       reverse_(a, c);
     }
    return a + (c - b);
}

static int* lower_bound_(int* a, int* b, const int key)
/* find first element not less than @p key in sorted sequence or end of
 * sequence (@p b) if not found. */
{
    int i;
    for ( i = b-a; i != 0; i /= 2 )
     {
       int* mid = a + i/2;
       if (*mid < key)
          a = mid + 1, i--;
     }
    return a;
}
static int* upper_bound_(int* a, int* b, const int key)
/* find first element greater than @p key in sorted sequence or end of
 * sequence (@p b) if not found. */
{
    int i;
    for ( i = b-a; i != 0; i /= 2 )
     {
       int* mid = a + i/2;
       if (*mid <= key)
          a = mid + 1, i--;
     }
    return a;
}

static void ip_merge_(int* a, int* b, int* c)
/* inplace merge. */
{
    int n1 = b - a;
    int n2 = c - b;

    if (n1 == 0 || n2 == 0)
       return;
    if (n1 == 1 && n2 == 1)
     {
       if (*b < *a)
          SWAP(int, *a, *b);
     }
    else
     {
       int* p, * q;

       if (n1 <= n2)
          p = upper_bound_(a, b, *(q = b+n2/2));
       else
          q = lower_bound_(b, c, *(p = a+n1/2));
       b = rotate_(p, b, q);

       ip_merge_(a, p, b);
       ip_merge_(b, q, c);
     }
}

void mergesort(int* v, int n)
{
    if (n > 1)
     {
       int h = n/2;
       mergesort(v, h); mergesort(v+h, n-h);
       ip_merge_(v, v+h, v+n);
     }
}

अनुकूली मर्जोर्ट (अनुकूलित) का एक उदाहरण।

जब किसी भी आकार का एक सहायक बफर उपलब्ध हो (तब भी अतिरिक्त मेमोरी के बिना काम करता है) मर्ज में तेजी लाने के लिए सपोर्ट कोड और संशोधनों को जोड़ता है। आगे और पीछे मर्जिंग, रिंग रोटेशन, छोटे अनुक्रम मर्जिंग और सॉर्टिंग, और पुनरावृत्ति विलय का उपयोग करता है।

#include <stdlib.h>
#include <string.h>

static int* copy_(const int* a, const int* b, int* out)
{
    int count = b - a;
    if (a != out)
       memcpy(out, a, count*sizeof(int));
    return out + count;
}
static int* copy_backward_(const int* a, const int* b, int* out)
{
    int count = b - a;
    if (b != out)
       memmove(out - count, a, count*sizeof(int));
    return out - count;
}

static int* merge_(const int* a1, const int* b1, const int* a2,
  const int* b2, int* out)
{
    while ( a1 != b1 && a2 != b2 )
       *out++ = (*a1 <= *a2) ? *a1++ : *a2++;
    return copy_(a2, b2, copy_(a1, b1, out));
}
static int* merge_backward_(const int* a1, const int* b1,
  const int* a2, const int* b2, int* out)
{
    while ( a1 != b1 && a2 != b2 )
       *--out = (*(b1-1) > *(b2-1)) ? *--b1 : *--b2;
    return copy_backward_(a1, b1, copy_backward_(a2, b2, out));
}

static unsigned int gcd_(unsigned int m, unsigned int n)
{
    while ( n != 0 )
     {
       unsigned int t = m % n;
       m = n;
       n = t;
     }
    return m;
}
static void rotate_inner_(const int length, const int stride,
  int* first, int* last)
{
    int* p, * next = first, x = *first;
    while ( 1 )
     {
       p = next;
       if ((next += stride) >= last)
          next -= length;
       if (next == first)
          break;
       *p = *next;
     }
    *p = x;
}
static int* rotate_(int* a, int* b, int* c)
/* swap the sequence [a,b) with [b,c). */
{
    if (a != b && b != c)
     {
       int n1 = c - a;
       int n2 = b - a;

       int* i = a;
       int* j = a + gcd_(n1, n2);

       for ( ; i != j; i++ )
          rotate_inner_(n1, n2, i, c);
     }
    return a + (c - b);
}

static void ip_merge_small_(int* a, int* b, int* c)
/* inplace merge.
 * @note faster for small sequences. */
{
    while ( a != b && b != c )
       if (*a <= *b)
          a++;
       else
        {
          int* p = b+1;
          while ( p != c && *p < *a )
             p++;
          rotate_(a, b, p);
          b = p;
        }
}
static void ip_merge_(int* a, int* b, int* c, int* t, const int ts)
/* inplace merge.
 * @note works with or without additional memory. */
{
    int n1 = b - a;
    int n2 = c - b;

    if (n1 <= n2 && n1 <= ts)
     {
       merge_(t, copy_(a, b, t), b, c, a);
     }
    else if (n2 <= ts)
     {
       merge_backward_(a, b, t, copy_(b, c, t), c);
     }
    /* merge without buffer. */
    else if (n1 + n2 < 48)
     {
       ip_merge_small_(a, b, c);
     }
    else
     {
       int* p, * q;

       if (n1 <= n2)
          p = upper_bound_(a, b, *(q = b+n2/2));
       else
          q = lower_bound_(b, c, *(p = a+n1/2));
       b = rotate_(p, b, q);

       ip_merge_(a, p, b, t, ts);
       ip_merge_(b, q, c, t, ts);
     }
}
static void ip_merge_chunk_(const int cs, int* a, int* b, int* t,
  const int ts)
{
    int* p = a + cs*2;
    for ( ; p <= b; a = p, p += cs*2 )
       ip_merge_(a, a+cs, p, t, ts);
    if (a+cs < b)
       ip_merge_(a, a+cs, b, t, ts);
}

static void smallsort_(int* a, int* b)
/* insertion sort.
 * @note any stable sort with low setup cost will do. */
{
    int* p, * q;
    for ( p = a+1; p < b; p++ )
     {
       int x = *p;
       for ( q = p; a < q && x < *(q-1); q-- )
          *q = *(q-1);
       *q = x;
     }
}
static void smallsort_chunk_(const int cs, int* a, int* b)
{
    int* p = a + cs;
    for ( ; p <= b; a = p, p += cs )
       smallsort_(a, p);
    smallsort_(a, b);
}

static void mergesort_lower_(int* v, int n, int* t, const int ts)
{
    int cs = 16;
    smallsort_chunk_(cs, v, v+n);
    for ( ; cs < n; cs *= 2 )
       ip_merge_chunk_(cs, v, v+n, t, ts);
}

static void* get_buffer_(int size, int* final)
{
    void* p = NULL;
    while ( size != 0 && (p = malloc(size)) == NULL )
       size /= 2;
    *final = size;
    return p;
}
void mergesort(int* v, int n)
{
    /* @note buffer size may be in the range [0,(n+1)/2]. */
    int request = (n+1)/2 * sizeof(int);
    int actual;
    int* t = (int*) get_buffer_(request, &actual);

    /* @note allocation failure okay. */
    int tsize = actual / sizeof(int);
    mergesort_lower_(v, n, t, tsize);
    free(t);
}

2
क्या आपने यह लिखा? यह अन्य उत्तरों में व्यक्त कठिनाइयों को कैसे दूर करता है? इसका रनिंग टाइम क्या है?
थॉमस अहले

यह मेरी अपनी कस्टम लाइब्रेरी से अनुकूलित है , लेकिन मैंने ये एल्गोरिदम नहीं बनाया है, अगर आप यही पूछ रहे हैं। विकास ओ (n (लॉग एन) ^ 2) सहायक मेमोरी के बिना है; पूर्ण बफर के साथ ओ (एन लॉग एन)। यह एक व्यावहारिक कार्यान्वयन बनने की कोशिश करता है, और घटक एल्गोरिदम दिखाने के लिए संरचित है।
जॉनी केज

दो क्रमबद्ध सूचियों को मर्ज करने के लिए आपको पुनरावृत्ति या अतिरिक्त बफर की आवश्यकता क्यों है? मुझे लगता है कि यह दो बिंदुओं को आगे बढ़ाकर और स्वैप किया जा सकता है यदि बाएं से दाएं बड़ा है।
जैक

3

इस उत्तर में एक कोड उदाहरण है , जो बिंग-चाओ हुआंग और माइकल ए लैंगस्टन द्वारा पेपर प्रैक्टिकल इन-प्लेस मर्जिंग में वर्णित एल्गोरिदम को लागू करता है । मुझे यह स्वीकार करना होगा कि मैं विवरणों को नहीं समझता, लेकिन मर्ज चरण की दी गई जटिलता O (n) है।

व्यावहारिक दृष्टिकोण से, इस बात के प्रमाण हैं कि वास्तविक विश्व के परिदृश्यों में शुद्ध इन-प्लेस कार्यान्वयन बेहतर प्रदर्शन नहीं कर रहे हैं। उदाहरण के लिए, C ++ मानक std :: inplace_merge को परिभाषित करता है , जैसा कि नाम से पता चलता है कि इन-प्लेस मर्ज ऑपरेशन है।

यह मानते हुए कि C ++ लाइब्रेरी आमतौर पर बहुत अच्छी तरह से अनुकूलित हैं, यह देखना दिलचस्प है कि इसे कैसे लागू किया जाता है:

1) libstdc ++ (GCC कोड आधार का हिस्सा): std :: inplace_merge

कार्यान्वयन __inplace_merge को दर्शाता है , जो एक अस्थायी बफर आवंटित करने की कोशिश करके समस्या को चकमा देता है :

typedef _Temporary_buffer<_BidirectionalIterator, _ValueType> _TmpBuf;
_TmpBuf __buf(__first, __len1 + __len2);

if (__buf.begin() == 0)
  std::__merge_without_buffer
    (__first, __middle, __last, __len1, __len2, __comp);
else
  std::__merge_adaptive
   (__first, __middle, __last, __len1, __len2, __buf.begin(),
     _DistanceType(__buf.size()), __comp);

अन्यथा, यह एक कार्यान्वयन ( __merge_without_buffer ) पर वापस आता है , जिसे अतिरिक्त मेमोरी की आवश्यकता नहीं है, लेकिन अब O (n) समय में नहीं चलता है।

2) libc ++ ( Clang कोड बेस का हिस्सा): std :: inplace_merge

समान दिखता है। यह एक फ़ंक्शन को दर्शाता है , जो एक बफर को आवंटित करने का भी प्रयास करता है । इसके आधार पर कि क्या इसे पर्याप्त तत्व मिले हैं, यह कार्यान्वयन का चयन करेगा। निरंतर-मेमोरी फ़ॉलबैक फ़ंक्शन को __buffered_inplace_merge कहा जाता है ।

हो सकता है कि फ़ॉलबैक अभी भी O (n) समय है, लेकिन मुद्दा यह है कि वे अस्थायी मेमोरी उपलब्ध होने पर कार्यान्वयन का उपयोग नहीं करते हैं।


ध्यान दें कि C ++ मानक स्पष्ट रूप से क्रियान्वयन को ओ (n) से O (एन लॉग एन) के लिए आवश्यक जटिलता को कम करके इस दृष्टिकोण को चुनने की स्वतंत्रता देता है:

जटिलता: यदि पर्याप्त अतिरिक्त मेमोरी उपलब्ध है, तो वास्तव में एन -1 तुलना। यदि स्मृति अपर्याप्त है, तो O (N log N) तुलना।

बेशक, इसे एक प्रमाण के रूप में नहीं लिया जा सकता है कि ओ (एन) समय में लगातार अंतरिक्ष में विलय का उपयोग नहीं किया जाना चाहिए। दूसरी ओर, यदि यह तेज़ होगा, तो अनुकूलित C ++ लाइब्रेरीज़ शायद उस प्रकार के कार्यान्वयन पर स्विच हो जाएंगी।


2

यह मेरा C संस्करण है:

void mergesort(int *a, int len) {
  int temp, listsize, xsize;

  for (listsize = 1; listsize <= len; listsize*=2) {
    for (int i = 0, j = listsize; (j+listsize) <= len; i += (listsize*2), j += (listsize*2)) {
      merge(& a[i], listsize, listsize);
    }
  }

  listsize /= 2;

  xsize = len % listsize;
  if (xsize > 1)
    mergesort(& a[len-xsize], xsize);

  merge(a, listsize, xsize);
}

void merge(int *a, int sizei, int sizej) {
  int temp;
  int ii = 0;
  int ji = sizei;
  int flength = sizei+sizej;

  for (int f = 0; f < (flength-1); f++) {
    if (sizei == 0 || sizej == 0)
      break;

    if (a[ii] < a[ji]) {
      ii++;
      sizei--;
    }
    else {
      temp = a[ji];

      for (int z = (ji-1); z >= ii; z--)
        a[z+1] = a[z];  
      ii++;

      a[f] = temp;

      ji++;
      sizej--;
    }
  }
}

ध्यान दें कि यह कार्यान्वयन सबसे खराब स्थिति (उलट सरणी) में Θ (n ^ 2 लॉग एन) समय लेता है।
Martinkunev

1

क्रोन्रोड की मूल तकनीक का उपयोग करते हुए इन-प्लेस मर्ज सॉर्ट का अपेक्षाकृत सरल कार्यान्वयन है लेकिन सरल कार्यान्वयन के साथ। एक चित्रात्मक उदाहरण जो इस तकनीक को दिखाता है वह यहां पाया जा सकता है: http://www.logiccoder.com/TheSortProblem/BestMergeInfo.htm

इस लिंक से जुड़े एक ही लेखक द्वारा अधिक विस्तृत सैद्धांतिक विश्लेषण के लिंक भी हैं।


इस लिंक का परिणाम 403
शार्लेट टैन

3
लिंक तय है। वहाँ के प्रलेखन में उद्दंडता की बात है। मुझे लगता है कि वहाँ एक दिलचस्प विचार है, लेकिन कोई एल्गोरिथ्म प्रस्तुत नहीं किया गया है, बस आरेखों का एक सेट और कुछ कमजोर विवरण। मैं आरेखों के कमजोर विवरणों को एक दिलचस्प तरीके से बांधने में सक्षम नहीं था, इसलिए मैंने हार मान ली।
इरा बैक्सटर

-6

मैं सिर्फ निम्नलिखित चरणों का उपयोग करके सम्मिलन प्रकार एल्गोरिथ्म का उपयोग करके जावा में विलय के लिए एल्गोरिथ्म मर्ज एल्गोरिथ्म में जगह की कोशिश की ।
1) दो क्रमबद्ध सरणियाँ उपलब्ध हैं।
2) प्रत्येक सरणी के पहले मूल्यों की तुलना करें; और पहले सरणी में सबसे छोटा मान रखें।
3) प्रविष्टि सॉर्ट (बाएं से दाएं की ओर) का उपयोग करके दूसरे सरणी में बड़ा मूल्य रखें।
4) फिर पहले सरणी के दूसरे मूल्य और दूसरे सरणी के पहले मूल्य की तुलना करें, और ऐसा ही करें। लेकिन जब स्वैपिंग होती है, तो आगे की वस्तुओं की तुलना करने पर स्किप पर कुछ सुराग होता है, लेकिन बस स्वैपिंग की आवश्यकता होती है।

मैंने यहाँ कुछ अनुकूलन किया है; प्रविष्टि प्रकार में कम तुलना रखने के लिए।
इस समाधान के साथ मुझे जो एकमात्र कमी मिली, वह यह है कि दूसरे एरे में एरे तत्वों की बड़ी अदला-बदली की आवश्यकता है।

जैसे)

First___Array: 3, 7, 8, 9

दूसरा ऐरे: 1, 2, 4, 5

फिर 7, 8, 9 दूसरी सरणी को स्वैप करने के लिए बनाता है (एक-एक करके छोड़ दिया जाता है) इसके सभी तत्वों को एक बार अंतिम रूप से रखने के लिए।

तो यहाँ अनुमान है कि स्वैपिंग आइटम दो वस्तुओं की तुलना करने के लिए नगण्य है।

https://github.com/skanagavelu/algorithams/blob/master/src/sorting/MergeSort.java

package sorting;

import java.util.Arrays;

public class MergeSort {
    public static void main(String[] args) {
    int[] array = { 5, 6, 10, 3, 9, 2, 12, 1, 8, 7 };
    mergeSort(array, 0, array.length -1);
    System.out.println(Arrays.toString(array));

    int[] array1 = {4, 7, 2};
    System.out.println(Arrays.toString(array1));
    mergeSort(array1, 0, array1.length -1);
    System.out.println(Arrays.toString(array1));
    System.out.println("\n\n");

    int[] array2 = {4, 7, 9};
    System.out.println(Arrays.toString(array2));
    mergeSort(array2, 0, array2.length -1);
    System.out.println(Arrays.toString(array2));
    System.out.println("\n\n");

    int[] array3 = {4, 7, 5};
    System.out.println(Arrays.toString(array3));
    mergeSort(array3, 0, array3.length -1);
    System.out.println(Arrays.toString(array3));
    System.out.println("\n\n");

    int[] array4 = {7, 4, 2};
    System.out.println(Arrays.toString(array4));
    mergeSort(array4, 0, array4.length -1);
    System.out.println(Arrays.toString(array4));
    System.out.println("\n\n");

    int[] array5 = {7, 4, 9};
    System.out.println(Arrays.toString(array5));
    mergeSort(array5, 0, array5.length -1);
    System.out.println(Arrays.toString(array5));
    System.out.println("\n\n");

    int[] array6 = {7, 4, 5};
    System.out.println(Arrays.toString(array6));
    mergeSort(array6, 0, array6.length -1);
    System.out.println(Arrays.toString(array6));
    System.out.println("\n\n");

    //Handling array of size two
    int[] array7 = {7, 4};
    System.out.println(Arrays.toString(array7));
    mergeSort(array7, 0, array7.length -1);
    System.out.println(Arrays.toString(array7));
    System.out.println("\n\n");

    int input1[] = {1};
    int input2[] = {4,2};
    int input3[] = {6,2,9};
    int input4[] = {6,-1,10,4,11,14,19,12,18};
    System.out.println(Arrays.toString(input1));
    mergeSort(input1, 0, input1.length-1);
    System.out.println(Arrays.toString(input1));
    System.out.println("\n\n");

    System.out.println(Arrays.toString(input2));
    mergeSort(input2, 0, input2.length-1);
    System.out.println(Arrays.toString(input2));
    System.out.println("\n\n");

    System.out.println(Arrays.toString(input3));
    mergeSort(input3, 0, input3.length-1);
    System.out.println(Arrays.toString(input3));
    System.out.println("\n\n");

    System.out.println(Arrays.toString(input4));
    mergeSort(input4, 0, input4.length-1);
    System.out.println(Arrays.toString(input4));
    System.out.println("\n\n");
}

private static void mergeSort(int[] array, int p, int r) {
    //Both below mid finding is fine.
    int mid = (r - p)/2 + p;
    int mid1 = (r + p)/2;
    if(mid != mid1) {
        System.out.println(" Mid is mismatching:" + mid + "/" + mid1+ "  for p:"+p+"  r:"+r);
    }

    if(p < r) {
        mergeSort(array, p, mid);
        mergeSort(array, mid+1, r);
//      merge(array, p, mid, r);
        inPlaceMerge(array, p, mid, r);
        }
    }

//Regular merge
private static void merge(int[] array, int p, int mid, int r) {
    int lengthOfLeftArray = mid - p + 1; // This is important to add +1.
    int lengthOfRightArray = r - mid;

    int[] left = new int[lengthOfLeftArray];
    int[] right = new int[lengthOfRightArray];

    for(int i = p, j = 0; i <= mid; ){
        left[j++] = array[i++];
    }

    for(int i = mid + 1, j = 0; i <= r; ){
        right[j++] = array[i++];
    }

    int i = 0, j = 0;
    for(; i < left.length && j < right.length; ) {
        if(left[i] < right[j]){
            array[p++] = left[i++];
        } else {
            array[p++] = right[j++];
        }
    }
    while(j < right.length){
        array[p++] = right[j++];
    } 
    while(i < left.length){
        array[p++] = left[i++];
    }
}

//InPlaceMerge no extra array
private static void inPlaceMerge(int[] array, int p, int mid, int r) {
    int secondArrayStart = mid+1;
    int prevPlaced = mid+1;
    int q = mid+1;
    while(p < mid+1 && q <= r){
        boolean swapped = false;
        if(array[p] > array[q]) {
            swap(array, p, q);
            swapped = true;
        }   
        if(q != secondArrayStart && array[p] > array[secondArrayStart]) {
            swap(array, p, secondArrayStart);
            swapped = true;
        }
        //Check swapped value is in right place of second sorted array
        if(swapped && secondArrayStart+1 <= r && array[secondArrayStart+1] < array[secondArrayStart]) {
            prevPlaced = placeInOrder(array, secondArrayStart, prevPlaced);
        }
        p++;
        if(q < r) {     //q+1 <= r) {
            q++;
        }
    }
}

private static int placeInOrder(int[] array, int secondArrayStart, int prevPlaced) {
    int i = secondArrayStart;
    for(; i < array.length; i++) {
        //Simply swap till the prevPlaced position
        if(secondArrayStart < prevPlaced) {
            swap(array, secondArrayStart, secondArrayStart+1);
            secondArrayStart++;
            continue;
        }
        if(array[i] < array[secondArrayStart]) {
            swap(array, i, secondArrayStart);
            secondArrayStart++;
        } else if(i != secondArrayStart && array[i] > array[secondArrayStart]){
            break;
        }
    }
    return secondArrayStart;
}

private static void swap(int[] array, int m, int n){
    int temp = array[m];
    array[m] = array[n];
    array[n] = temp;
}
}

3
यह O (n ^ 2) है और अत्यधिक अपठनीय (सामयिक वाक्यविन्यास त्रुटियों और असंगत / खराब शैली के कारण)
glaba
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.