Birleştirme sıralama algoritmasını kullanarak yerinde nasıl sıralanır?


244

Sorunun çok spesifik olmadığını biliyorum.

Tek istediğim birisi bana normal bir birleştirme sıralamasını yerinde bir birleştirme sıralamasına (veya sabit ekstra boşluk ek yükü olan bir birleştirme sıralamasına) nasıl dönüştüreceğini söyleyecek.

Tüm bulabildiğim (internette) "çok karmaşık" veya "bu metnin kapsamı dışında" diyen sayfalardır.

Yerinde birleştirmek için bilinen tek yol (fazladan boşluk olmadan), pratik programa indirilemeyecek kadar karmaşıktır. ( buradan alınır )

Çok karmaşık olsa bile , birleştirmenin nasıl yerinde yapılacağına dair temel kavram nedir?


Güzel soru, dün bir soru okurken kendime sordum: stackoverflow.com/questions/2566459/…
Chris Lercher

Burada açıklanan oldukça basit bir yöntem var: xinok.wordpress.com/2014/08/17/…
Branko Dimitrijevic

Yanıtlar:


140

Knuth bunu bir egzersiz olarak bıraktı (Cilt 3, 5.2.5). Yerinde birleştirme çeşitleri vardır. Bunlar dikkatli bir şekilde uygulanmalıdır.

İlk olarak, burada tarif edilen gibi saf yerinde birleştirme doğru çözüm değildir. Performansı O (N 2 ) değerine düşürür .

Fikir, geri kalanı birleştirmek için çalışma alanı olarak kullanırken dizinin bir kısmını sıralamaktır.

Örneğin aşağıdaki birleştirme işlevi gibi.

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++);
}  

Diziyi alır, xssıralanan iki alt dizi sırasıyla aralık [i, m)ve [j, n)olarak gösterilir. Çalışma alanı başlar w. Çoğu ders kitabında verilen standart birleştirme algoritmasıyla karşılaştırıldığında, bu, sıralanan alt dizi ile çalışma alanı arasında içerik alışverişi yapar. Sonuç olarak, önceki çalışma alanı birleştirilmiş sıralanmış öğeleri içerirken, çalışma alanında depolanan önceki öğeler iki alt diziye taşınır.

Ancak, yerine getirilmesi gereken iki kısıtlama vardır:

  1. Çalışma alanı dizinin sınırları içinde olmalıdır. Başka bir deyişle, sınırsız hataya neden olmadan değiştirilen öğeleri tutmak için yeterince büyük olmalıdır.
  2. Çalışma alanı iki sıralı diziden biriyle çakışabilir; ancak, birleştirilmemiş öğelerin hiçbirinin üzerine yazılmadığından emin olmalıdır.

Tanımlanan bu birleştirme algoritması ile, dizinin yarısını sıralayabilen bir çözüm hayal etmek kolaydır; Sonraki soru, aşağıda gösterildiği gibi çalışma alanında depolanan ayrılmamış parçanın geri kalanıyla nasıl başa çıkılacağıdır:

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

Sezgisel bir fikir, çalışma alanının başka bir yarısını özyinelemeli olarak sıralamaktır, bu nedenle henüz sıralanmamış sadece 1/4 unsur vardır.

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

Bu aşamadaki anahtar nokta, sıralanan 1/4 eleman B'yi sıralı 1/2 eleman A ile er ya da geç birleştirmemiz gerektiğidir.

Sadece 1/4 eleman barındıran çalışma alanı, A ve B'yi birleştirecek kadar büyük mü? Maalesef öyle değil.

Bununla birlikte, yukarıda bahsedilen ikinci kısıtlama, birleştirilmemiş öğelerin üzerine yazılmayacağı birleştirme sırasını sağlayabilirsek, çalışma alanını her iki alt diziyle çakışacak şekilde düzenleyerek bu alandan yararlanabileceğimiz konusunda bir ipucu verir.

Aslında, çalışma alanının ikinci yarısını sıralamak yerine, ilk yarıyı sıralayabilir ve çalışma alanını şu iki sıralı dizi arasına koyabiliriz:

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

Bu kurulum, çalışma alanının A alt dizisi ile örtüşmesini etkili bir şekilde düzenler. Bu fikir [Jyrki Katajainen, Tomi Pasanen, Jukka Teuhola. `` Pratik yerinde birleştirme ''. Nordic Computing Journal, 1996].

Yani geriye kalan tek şey yukarıdaki adımı tekrarlamaktır, bu da çalışma alanını 1/2, 1/4, 1/8, ... 'den düşürür. Çalışma alanı yeterince küçük olduğunda (örneğin, sadece iki eleman kaldı), bu algoritmayı sonlandırmak için önemsiz bir ekleme türüne geçin.

İşte bu yazıyı temel alan ANSI C'deki uygulama.

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);
    }
}

Nerede wmerge daha önce tanımlanmış.

Tam kaynak kodu burada bulunabilir ve ayrıntılı açıklama burada bulunabilir

Bu arada, bu sürüm en hızlı birleştirme türü değil çünkü daha fazla takas işlemine ihtiyaç duyuyor. Testime göre, her yinelemede ekstra alan ayıran standart sürümden daha hızlı. Ancak, orijinal diziyi önceden iki katına çıkaran ve daha fazla birleştirme için kullanan optimize edilmiş sürümden daha yavaştır.


6
Knuth left this as an exercise (Vol 3, 5.2.5).Örn. 13. [40] O (N) zaman birimleriyle yalnızca O (sqrt (N)) ek bellek konumlarında rastgele verileri ayıran [bu bölümün kapanışında] önerilen dahili sıralama yöntemini uygulayın . ? ( 40 gösteren sınıf içinde bir dönem projesi belki de uygundur oldukça zor bir ya da uzun bir problemi vardır. )
ihtiyar adam

4
Penguin.ew sitesinde belirtilen yerinde algoritmanın zaman karmaşıklığının O olduğunu düşünüyorum (log n * n ^ 2). Öyle değil mi?
code4fun

1
Bu algoritma hala kararlı mı ve oturum açma zamanında mı?
Paul Stelian

1
@PaulStelian - kararlı değil. Çalışma alanındaki elemanlar, sıralanan alandaki elemanların sipariş işlemlerine göre yeniden düzenlenir. Bu, eşit değerlere sahip çalışma alanı öğelerinin artık orijinal sıralarında olmayacak şekilde yeniden düzenleneceği anlamına gelir.
rcgldr

1
@PaulStelian - Wiki, yorum yaptığınız gibi kararlı birleştirme sıralaması için bir makaleye sahiptir . En az 2 · sqrt (n) benzersiz değer varsa en iyi sonucu verir.
rcgldr

59

"Büyük sonucunu" da içeren bu makalede, yerinde birleştirme sıralamasının (PDF) birkaç çeşidi açıklanmaktadır:

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

Daha az hareketle yerinde sıralama

Tomi A. Pasanen, Jyrki Katajainen

Bir n eleman dizisinin O (1) fazladan boşluk, O (n log n / log log n) elementi hareketleri ve n log 2 n + O (n log log n) karşılaştırmaları kullanılarak sıralanabileceği gösterilmiştir . Bu, O (n log n) karşılaştırmalarını garanti ederken en kötü durumda o (n log n) hareketlerini gerektiren ilk yerinde sıralama algoritmasıdır, ancak ilgili sabit faktörler nedeniyle algoritma ağırlıklı olarak teorik ilgi çekicidir.

Bunun da alakalı olduğunu düşünüyorum. Etrafta yatan bir çıktısı var, bir meslektaşım tarafından bana geçti, ama okumadım. Temel teoriyi kapsıyor gibi görünüyor, ancak konuyu ne kadar kapsamlı bir şekilde yargılayacağımı bilmiyorum:

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

Optimal Kararlı Birleştirme

Antonios Symvonis

Bu makale sırasıyla m ve n, m ≤ n boyutlarındaki iki A ve B dizisinin O (m + n) atamaları, O (mlog (n / m + 1)) karşılaştırmalarıyla ve sadece sabit bir ek alan miktarı. Bu sonuç, bilinen tüm alt sınırlarla eşleşir ...


12

Gerçekten kolay veya verimli değildir ve gerçekten gerekmedikçe bunu yapmamanızı öneririm (ve yerinde birleştirme uygulamaları çoğunlukla teorik olduğundan, bu ödev olmadıkça muhtemelen gerekmez). Bunun yerine quicksort kullanamaz mısınız? Quicksort birkaç basit optimizasyon ile daha hızlı olacak ve ekstra belleği O (log N) olacak .

Her neyse, eğer yapmanız gerekiyorsa yapmalısınız. İşte bulduğum şey: bir ve iki . Yerinde birleştirme sıralamasına aşina değilim, ancak temel fikir, iki diziyi fazladan bellek kullanmadan birleştirmeyi kolaylaştırmak için döndürmeler kullanmak gibi görünüyor.

Bunun, yerinde olmayan klasik birleştirme türünden bile daha yavaş olduğunu unutmayın.


9
Quicksort sabit değil. Bu , birçok üretim kodu için gerçekten önemlidir.
Donal Fellows

7
quicksort kararlı olabilir ve iirc birleştirme sıralaması yerinde değilse mutlaka sabit değildir
jk.

4
@jk: Quicksort kararlı değil; hızı bundan kaynaklanır ve başka türlü iddia etmeye çalışmamalısınız. Çok iyi bir değiş tokuş. Evet, orijinal dizini anahtarın geri kalanıyla ilişkilendirmek mümkündür, böylece asla aynı iki öğeye sahip olmazsınız, böylece istikrarlı bir sıralama elde edersiniz; bu, bazı ekstra alanın (eleman sayısında doğrusal) gerekli bir maliyetle gelir, çünkü performansı düşüren ekstra eleman hareketlerine başvurmadan “eşdeğer” öğelerin göreli sırasını koruyamazsınız.
Donal Fellows

4
Quicksort ayrıca özel hazırlanmış girdi için O (n ^ 2) en kötü duruma sahiptir
HoboBen

4
@DonalFellows: jk tam olarak doğru; quicksort ekstra alan maliyeti olmadan istikrarlı olarak uygulanabilir. Wikipedia'yı kontrol edin.
Rusty

10

Kritik adım, birleşmenin yerinde olmasını sağlamaktır. Bu kaynaklar kadar zor değil, ama denediğinizde bir şeyler kaybediyorsunuz.

Birleşmenin bir adımına bakıldığında:

[... liste sıralı ... | x ... liste- A ... | y ... liste- B ...]

Bunu biliyoruz sıralanmış sekansı, her şey daha, yani X her şey daha az A ve bu y azdır her şey daha B . X'in y'den küçük veya y'ye eşit olması durumunda , imlecinizi bir tanesinin A başlangıcına getirmeniz yeterlidir . Durumunda y azdır x , sen karıştırmak lazım y bütün geçmiş A'ya göre sıralanmış . Bu son adım bunu pahalı yapan şeydir (yozlaşmış durumlar hariç).

Genelde daha ucuzdur (özellikle diziler, öğe başına yalnızca tek sözcükler, örneğin bir dizgiye veya yapıya işaretçi) zaman ayırmak ve aralarında sıraladığınız ayrı bir geçici diziye sahip olmak daha ucuzdur.


5
Yerinde birleştirmenizde O (m n) en kötü durum karmaşıklığı vardır, burada m A boyutu ve n B boyutudur. Bu, A'daki ilk öğenin B'deki son öğeden daha büyük olduğu durumdur. Karmaşıklık, O (k günlüğü (k) + m + n) olarak geliştirilebilir; burada k = min (m, n), A ve B arasında bir yığın olmalıdır. Bu yığın, A'daki öğeleri B'de kalan öğelerden daha büyük, ancak A'daki diğer öğelerden daha küçük içermelidir. Önce A tükenirse, öbek B'nin sonuna taşınmalıdır. Aksi takdirde, yığın A'nın başına taşınmalıdır. Daha sonra, yığın öğelerini yerinde dışarı atmalı ve birleştirmeyi tamamlamak için tersine çevrilmelidir.
valyala

2
@valyala Bir yığın kullanırken, sıralamanın artık kararlı olmadığını unutmayın. Ayrıca, bir yığın kullanırsanız, birleştirme sıralaması yerine yığın sıralaması ile gidebilirsiniz.
martinkunev


4

C'de tamponsuz birleştirme örneği.

#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);
     }
}

Adaptif birleştirme örneği (optimize edilmiş).

Herhangi bir boyutta yardımcı bir arabellek bulunduğunda birleştirmeyi hızlandırmak için destek kodu ve değişiklikler ekler (yine de ek bellek olmadan çalışır). İleri ve geri birleştirme, halka döndürme, küçük sıralı birleştirme ve sıralama ve yinelemeli birleştirme kullanır.

#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
Bunu sen mi yazdın? Diğer cevaplarda ifade edilen zorlukların üstesinden nasıl gelir? Çalışma süresi nedir?
Thomas Ahle

Bu, kendi özel kitaplığımdan uyarlandı , ancak istediğin buysa, bu algoritmaları oluşturmadım. Büyüme, yardımcı bellek olmadan O (n (log n) ^ 2); Tam tamponlu O (n log n). Bu pratik bir uygulama olmaya çalışır ve kurucu algoritmaları gösterecek şekilde yapılandırılmıştır.
Johnny Cage

İki sıralı listeyi birleştirmek için neden özyineleme veya fazla ara belleğe ihtiyacınız var? Bence bu iki göstergeyi ileri hareket ettirerek ve soldan sağdan büyükse değiştirerek yapılabilir.
jack

3

Bu yanıtta Bing-Chao Huang ve Michael A. Langston tarafından Pratik Yerinde Birleştirme makalesinde açıklanan algoritmayı uygulayan bir kod örneği vardır . Ayrıntıları anlamadığımı itiraf etmeliyim, ancak birleştirme adımının verilen karmaşıklığı O (n).

Pratik bir bakış açısından, gerçek yerinde uygulamaların gerçek dünya senaryolarında daha iyi performans göstermediğine dair kanıtlar vardır. Örneğin, C ++ standardı adın yerinde birleştirme işlemini ima ettiği gibi std :: inplace_merge öğesini tanımlar .

C ++ kütüphanelerinin genellikle çok iyi optimize edildiğini varsayarsak, nasıl uygulandığını görmek ilginçtir:

1) libstdc ++ (GCC kod tabanının bir parçası): std :: inplace_merge

Uygulama , geçici bir arabellek ayırmaya çalışarak sorunu atlayan __inplace_merge yetkisini verir :

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);

Aksi takdirde, fazladan bellek gerektirmeyen, ancak artık O (n) sürede çalışmayan bir uygulamaya ( __merge_without_buffer ) geri döner .

2) libc ++ (Clang kod tabanının bir parçası): std :: inplace_merge

Benzer görünüyor. Bir arabellek ayırmaya çalışan bir işleve yetki verir . Yeterli öğeye sahip olup olmadığına bağlı olarak, uygulamayı seçecektir. Sabit bellek yedek işlevine __buffered_inplace_merge adı verilir .

Belki de geri dönüş hala O (n) zamanıdır, ancak asıl nokta geçici bellek varsa uygulamayı kullanmamalarıdır.


C ++ standardının açık bir şekilde uygulamalara gerekli karmaşıklığı O (n) 'den O (N log N)' ye düşürerek bu yaklaşımı seçme özgürlüğü sağladığını unutmayın:

Karmaşıklık: Yeterli ek bellek varsa tam olarak N-1 karşılaştırmaları. Bellek yetersizse, O (N log N) karşılaştırmaları.

Tabii ki, bu, O (n) zamanında sabit boşluk yerinde birleşmenin asla kullanılmaması gerektiğinin kanıtı olarak kabul edilemez. Öte yandan, eğer daha hızlı olsaydı, optimize edilmiş C ++ kütüphaneleri muhtemelen bu tür bir uygulamaya geçecektir.


2

Bu benim C versiyonum:

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--;
    }
  }
}

Bu uygulamanın en kötü durumda (ters dizi) Θ (n ^ 2 log n) zaman aldığını unutmayın.
martinkunev

1

Kronrod'un orijinal tekniğini kullanarak daha basit bir uygulama ile yerinde birleştirme sıralamasının nispeten basit bir uygulaması vardır. Bu tekniği gösteren resimsel bir örnek burada bulunabilir: http://www.logiccoder.com/TheSortProblem/BestMergeInfo.htm .

Aynı yazarın bu bağlantıyla ilişkili daha ayrıntılı teorik analizine de bağlantılar vardır.


bu bağlantı bir 403 ile sonuçlanır
Charlotte Tan

3
Bağlantı düzeltildi. Oradaki belgeler obtusite noktasına şifreli. Orada ilginç bir fikir olduğu izlenimini edindim, ama hiçbir algoritma sunulmuyor, sadece bir dizi diyagram ve oldukça zayıf açıklamalar var. Zayıf açıklamaları şemalara ilginç bir şekilde bağlayamadım, bu yüzden vazgeçtim.
Ira Baxter

-6

Sadece aşağıdaki adımları kullanarak ekleme sıralama algoritması kullanarak JAVA birleştirme sıralaması için yerinde birleştirme algoritması denedim .
1) İki sıralı dizi mevcuttur.
2) Her dizinin ilk değerlerini karşılaştırın; ve en küçük değeri ilk diziye yerleştirin.
3) Ekleme sıralaması (soldan sağa doğru) kullanarak daha büyük değeri ikinci diziye yerleştirin.
4) Ardından ilk dizinin ikinci değerini ve ikinci dizinin ilk değerini tekrar karşılaştırın ve aynısını yapın. Ancak takas gerçekleştiğinde, diğer öğeleri karşılaştırırken atlama konusunda bir ipucu var, ancak sadece takas gerekiyor.

Burada bazı optimizasyonlar yaptım; ekleme sıralamasında daha az karşılaştırma yapmak için.
Bu çözümlerle bulduğum tek dezavantaj, ikinci dizideki dizi öğelerinin daha büyük değiştirilmesine ihtiyaç duymasıdır.

Örneğin)

İlk ___ Dizi: 3, 7, 8, 9

İkinci Dizi: 1, 2, 4, 5

Daha sonra 7, 8, 9 kendini her seferinde kendini birinciye yerleştirmek için ikinci diziyi yapar.

Dolayısıyla, buradaki kalemlerin değiştiği varsayımı, iki öğenin karşılaştırılmasına kıyasla önemsizdir.

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
Hem O (n ^ 2) hem de son derece okunamıyor (ara sıra sözdizimi hataları ve tutarsız / zayıf stil nedeniyle)
glaba
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.