병합 정렬 알고리즘을 사용하여 내부 정렬 방법?


244

질문이 너무 구체적이지 않다는 것을 알고 있습니다.

내가 원하는 것은 일반 병합 정렬을 내부 병합 정렬 (또는 일정한 여분의 공간 오버 헤드가있는 병합 정렬)으로 변환하는 방법을 알려주는 사람입니다.

인터넷에서 찾을 수있는 것은 "너무 복잡합니다"또는 "이 텍스트의 범위를 벗어났습니다"라는 페이지입니다.

추가 공간없이 적절한 위치에 병합하는 유일한 방법은 실제 프로그램으로 축소하기에는 너무 복잡합니다. ( 여기에서 찍은 )

너무 복잡하더라도 병합 정렬을 적절 하게 만드는 방법의 기본 개념은 무엇입니까?


좋은 질문, 나는 어제 질문을 읽을 때 나 자신에게 물었다 : stackoverflow.com/questions/2566459/…
Chris Lercher

여기에 설명 된 상당히 간단한 방법이 있습니다. xinok.wordpress.com/2014/08/17/…
Branko Dimitrijevic

답변:


140

크 누스는 이것을 운동으로 남겼다 (Vol 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와 조만간 병합해야한다는 것입니다.

A와 B를 합치기에 충분한 1/4 요소 만 포함하는 작업 영역이 남아 있습니까? 불행히도 그렇지 않습니다.

그러나 위에서 언급 한 두 번째 제약 조건은 병합되지 않은 요소를 덮어 쓰지 않는 병합 시퀀스를 보장 할 수 있으면 작업 영역을 하위 배열과 겹치도록 배치하여 활용할 수 있다는 힌트를줍니다.

실제로 작업 영역의 후반을 정렬하는 대신 첫 번째 절반을 정렬하고 다음과 같이 정렬 된 두 배열 사이에 작업 영역을 배치 할 수 있습니다.

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

이 설정은 작업 영역이 하위 배열 A와 겹치도록 효과적으로 정렬합니다.이 아이디어는 [Jyrki Katajainen, Tomi Pasanen, Jukka Teuhola에서 제안됩니다. ``실용적인 현장 병합 병합 ''. 북유럽 컴퓨팅 저널, 1996].

따라서 남은 것은 작업 영역을 1/2, 1/4, 1/8에서 줄이는 위의 단계를 반복하는 것입니다.… 작업 영역이 충분히 작아지면 (예 : 두 요소 만 남음) 이 알고리즘을 끝내려면 간단한 삽입 정렬로 전환하십시오.

다음은이 백서를 기반으로 한 ANSI C의 구현입니다.

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 은 교실 상황에서 용어 프로젝트로 적합 할 수있는 상당히 어렵거나 긴 문제를 나타냅니다 . )
greybeard

4
나는 penguin.ew 사이트에서 언급 한 in-place 알고리즘의 시간 복잡성이 O (log n * n ^ 2)라고 생각합니다 .log n이 병합되고 각 병합은 순서가 O (n ^ 2)입니다. 그렇지 않습니까?
code4fun

1
이 알고리즘은 여전히 ​​안정적이며 n log n 시간입니까?
Paul Stelian

1
@PaulStelian-안정적이지 않습니다. 작업 영역의 요소는 정렬 된 영역의 요소에 대한 순서 지정 작업에 따라 재 배열됩니다. 이는 동일한 값을 가진 작업 영역 요소가 재 배열되어 더 이상 원래 순서로 정렬되지 않음을 의미합니다.
rcgldr

1
@PaulStelian-Wiki에는 블록 병합 정렬에 대한 기사 가 있습니다. 최소 2 · sqrt (n)의 고유 한 값이있는 경우 가장 잘 작동하여 배열의 작업 영역을 제공하고 안정적으로 유지되도록 재정렬 할 수 있습니다.
rcgldr

59

본 문서에서는 "큰 결과"를 포함하여 몇 가지 변형 된 내부 병합 정렬 (PDF)을 설명합니다.

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

적은 이동으로 전체 정렬

지르 키 카타자인, 토미 에이 파사 넨

O (1) 여분의 공간, O (n log n / log log n) 요소 이동 및 n log 2 n + O (n log log n) 비교를 사용하여 n 요소의 배열을 정렬 할 수 있음이 표시 됩니다. 이것은 O (n log n) 비교를 보장하면서 최악의 경우 o (n log n) 이동을 필요로하는 첫 번째 인플레 이스 정렬 알고리즘이지만 관련된 상수 요인으로 인해 알고리즘은 이론적으로 관심이 있습니다.

나는 이것도 관련이 있다고 생각합니다. 나는 그 주위에 거짓말을하고 동료에 의해 나에게 전달되었지만 그것을 읽지 못했습니다. 기본 이론을 다루는 것처럼 보이지만 주제가 얼마나 포괄적인지 판단하기에는 충분하지 않습니다.

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

최적의 안정적인 병합

안토니오 교향곡

이 논문은 O (m + n) 대입, O (mlog (n / m + 1)) 비교 및 ​​상수만을 사용하여 각각 크기 m과 n, m ≤ n의 두 시퀀스 A와 B를 안정적으로 병합하는 방법을 보여줍니다. 추가 공간 이 결과는 알려진 모든 하한과 일치합니다 ...


12

실제로 쉽지 않거나 효율적이지 않으므로 실제로해야 할 경우가 아니라면 수행하지 않는 것이 좋습니다 (인플레 이스 병합의 응용 프로그램이 대부분 이론적이므로 숙제가 아니라면 아마 필요하지 않습니다). 퀵 정렬을 대신 사용할 수 없습니까? Quicksort는 몇 가지 간단한 최적화로 어쨌든 더 빠를 것이며 추가 메모리는 O (log N) 입니다.

어쨌든, 당신이 그것을해야한다면 당신은해야합니다. 여기 내가 찾은 것이 있습니다 : one and two . 인플레 이스 병합 정렬에 익숙하지 않지만 기본 아이디어는 회전을 사용하여 추가 메모리를 사용하지 않고 두 배열을 쉽게 병합하는 것 같습니다.

이것은 제자리에없는 일반적인 병합 정렬보다 느립니다.


9
퀵 정렬이 안정적이지 않습니다. 그것은 실제로 많은 프로덕션 코드에 중요합니다.
Donal Fellows

7
quicksort는 안정적 일 수 있으며, iirc 병합 정렬은 제자리에있을 경우 반드시 안정적 일 필요는 없습니다
jk.

4
@ jk : Quicksort가 안정적이지 않습니다. 그것은 속도에서 비롯되며 달리 주장해서는 안됩니다. 매우 좋은 절충점입니다. 그렇습니다. 원래 색인을 나머지 키와 연관 시켜서 두 개의 요소가 동일하지 않아 안정적인 정렬을 제공 할 수 있습니다. 성능을 떨어 뜨리는 여분의 요소 이동에 의지하지 않으면 서 "동등한"요소의 상대적 순서를 유지할 수 없기 때문에 추가 요소의 공간 (필요한 요소 수의 선형)에 필요한 비용이 발생합니다.
Donal Fellows

4
Quicksort는 특수 제작 된 입력에 대해 O (n ^ 2) 최악의 경우도 있습니다
HoboBen

4
@DonalFellows : jk가 정확히 맞습니다. Quicksort는 추가 공간 비용없이 안정적으로 구현 될 수 있습니다. Wikipedia를 확인하십시오.
Rusty

10

중요한 단계는 병합 자체를 제자리에 두는 것입니다. 이러한 소스를 만드는 것만 큼 어렵지는 않지만 시도 할 때 무언가를 잃습니다.

병합의 한 단계를 살펴보십시오.

[... 목록 정렬 ... | x ... 목록 -A ... | y ... 목록 -B ...]

우리는 알고 정렬 순서가 적은 다른 모든 것들보다, 그 X는 에서 다른 모든 것들보다 작 , 그리고 y는 적은에서 다른 모든 것보다 B . xy 보다 작거나 같은 경우 포인터를 A 의 시작 부분으로 이동하면 됩니다. 경우 y는 보다 작은 X를 , 당신은 셔플있어 y로 전체지나 에 분류 . 마지막 단계는 이것이 비싸게 만드는 것입니다 (퇴화 사례 제외).

일반적으로 시간이 지남에 따라 일부 공간을 교환하고 별도의 임시 배열을 정렬하는 것이 저렴합니다 (특히 배열에 실제로 요소 당 단일 단어 (예 : 문자열 또는 구조에 대한 포인터) 만 포함하는 경우).


5
내부 병합의 경우 최악의 복잡도 는 O (m n)입니다. 여기서 m은 A 크기이고 n은 B 크기입니다. A의 첫 번째 항목이 B의 마지막 항목보다 큰 경우입니다. 복잡도는 O (k log (k) + m + n) 로 향상 될 수 있습니다 . 여기서 k는 min (m, n)입니다. 이 힙은 A의 항목을 포함해야합니다. A의 항목은 B의 나머지 항목보다 크지 만 A의 나머지 항목보다 작습니다. A가 먼저 소진되면 힙을 B의 끝으로 이동해야합니다. 그렇지 않으면 힙을 A의 시작 부분으로 이동해야합니다. 그런 다음 힙 항목을 제자리에서 튀어 나와서 뒤집어서 병합을 완료해야합니다.
valyala

2
@valyala 힙을 사용할 때 정렬이 더 이상 안정적이지 않습니다. 또한 힙을 사용하는 경우 병합 정렬 대신 힙 정렬을 사용할 수 있습니다.
martinkunev


4

C의 버퍼없는 병합 소트의 예

#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
이거 쓰셨어요? 다른 답변에서 표현 된 어려움을 어떻게 극복합니까? 러닝 타임은?
Thomas Ahle

이것은 내 자신의 사용자 정의 라이브러리 에서 조정되었지만 원하는대로 알고리즘을 만들지 않았습니다. 보조 기억이없는 성장은 O (n (log n) ^ 2)이며; 전체 버퍼가있는 O (n log n). 이것은 실제적인 구현을 시도하고 구성 알고리즘을 보여 주도록 구성되어 있습니다.
Johnny Cage

정렬 된 두 목록을 병합하기 위해 재귀 또는 추가 버퍼가 필요한 이유는 무엇입니까? 왼쪽 포인터가 오른쪽보다 큰 경우 두 포인터를 앞으로 이동하고 스왑하여 수행 할 수 있다고 생각합니다.
jack

3

이 답변 에는 Bing-Chao Huang과 Michael A. Langston의 Practical In-Place Merging 논문에 설명 된 알고리즘을 구현 하는 코드 예제가 있습니다. 세부 사항을 이해하지 못한다는 것을 인정해야하지만 병합 단계의 주어진 복잡성은 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);

그렇지 않으면 추가 메모리가 필요하지 않지만 O (n) 시간에 더 이상 실행되지 않는 구현 ( __merge_without_buffer )으로 돌아갑니다 .

2) libc ++ (Clang 코드베이스의 일부) : std :: inplace_merge

비슷해 보인다. 버퍼할당 하려고 시도 하는 함수에 위임합니다 . 충분한 요소가 있는지 여부에 따라 구현을 선택합니다. 상수 메모리 폴백 함수를 __buffered_inplace_merge 라고 합니다 .

아마도 폴백조차도 여전히 O (n) 시간이지만 요점은 임시 메모리가 사용 가능한 경우 구현을 사용하지 않는다는 것입니다.


C ++ 표준은 구현에 필요한 복잡성을 O (n)에서 O (N log N)으로 낮춰이 방법을 선택할 수있는 자유를 명시 적으로 제공합니다.

복잡성 : 충분한 추가 메모리가 사용 가능한 경우 정확히 N-1 비교. 메모리가 부족하면 O (N log N)를 비교합니다.

물론 이것은 O (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 log n) 시간이 걸립니다.
martinkunev

1

Kronrod의 원래 기술을 사용하여 비교적 간단한 인플레 이스 병합 정렬을 구현하지만 구현은 더 간단합니다. 이 기술을 보여주는 그림 예제는 http://www.logiccoder.com/TheSortProblem/BestMergeInfo.htm 에서 찾을 수 있습니다 .

이 링크와 관련된 동일한 저자의보다 상세한 이론적 분석에 대한 링크도 있습니다.


403에서이 링크 결과
샬롯 탄

3
링크가 수정되었습니다. 거기에 문서화는 남용에 대한 비밀이 있습니다. 흥미로운 아이디어가 있다는 인상을 얻었지만 알고리즘은 없으며 일련의 다이어그램과 다소 약한 설명이 있습니다. 약한 설명을 다이어그램에 흥미로운 방식으로 묶을 수 없었기 때문에 포기했습니다.
Ira Baxter

-6

방금 삽입 정렬 알고리즘을 사용하여 다음 단계를 사용하여 JAVA 에서 병합 정렬을위한 병합 알고리즘을 시도했습니다 .
1) 2 개의 정렬 된 배열을 사용할 수 있습니다.
2) 각 배열의 첫 번째 값을 비교하십시오. 가장 작은 값을 첫 번째 배열에 넣습니다.
3) 삽입 정렬 (왼쪽에서 오른쪽으로 이동)을 사용하여 더 큰 값을 두 번째 배열에 배치하십시오.
4) 그런 다음 첫 번째 배열의 두 번째 값과 두 번째 배열의 첫 번째 값을 다시 비교하고 동일하게 수행하십시오. 그러나 스와핑이 발생하면 추가 항목을 건너 뛰는 몇 가지 단서가 있지만 스왑 만하면됩니다.

나는 여기에 약간의 최적화를 만들었다; 삽입 정렬에서 더 적은 비교를 유지합니다.
이 솔루션에서 찾은 유일한 단점은 두 번째 배열에서 배열 요소를 더 많이 교환해야한다는 것입니다.

예)

첫 번째 배열 : 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
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.