Làm thế nào để sắp xếp tại chỗ bằng thuật toán sắp xếp hợp nhất?


244

Tôi biết câu hỏi không quá cụ thể.

Tất cả những gì tôi muốn là ai đó chỉ cho tôi cách chuyển đổi một loại hợp nhất thông thường thành một loại hợp nhất tại chỗ (hoặc một loại hợp nhất với chi phí không gian thêm không đổi).

Tất cả những gì tôi có thể tìm thấy (trên mạng) là các trang có nội dung "quá phức tạp" hoặc "nằm ngoài phạm vi của văn bản này".

Các cách duy nhất được biết để hợp nhất tại chỗ (không có thêm không gian) là quá phức tạp để được giảm xuống thành chương trình thực tế. (lấy từ đây )

Ngay cả khi nó quá phức tạp, khái niệm cơ bản về cách thực hiện sắp xếp hợp nhất tại chỗ là gì?


Câu hỏi hay, tôi đã tự hỏi mình khi đọc qua một câu hỏi từ ngày hôm qua: stackoverflow.com/questions/2566459/iêu
Chris Lercher

Có một phương pháp khá đơn giản được mô tả ở đây: xinok.wordpress.com/2014/08/17/ mẹo
Branko Dimitrijevic

Câu trả lời:


140

Knuth để lại điều này như một bài tập (Tập 3, 5.2.5). Có tồn tại các loại hợp nhất tại chỗ. Chúng phải được thực hiện cẩn thận.

Đầu tiên, hợp nhất tại chỗ ngây thơ như được mô tả ở đây không phải là giải pháp phù hợp. Nó hạ hiệu suất xuống O (N 2 ) .

Ý tưởng là sắp xếp một phần của mảng trong khi sử dụng phần còn lại làm vùng làm việc để hợp nhất.

Ví dụ như hàm hợp nhất sau.

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

Nó lấy mảng xs, hai mảng con được sắp xếp được biểu diễn dưới dạng phạm vi [i, m)[j, n)tương ứng. Khu vực làm việc bắt đầu từ w. So sánh với thuật toán hợp nhất tiêu chuẩn được đưa ra trong hầu hết các sách giáo khoa, cái này trao đổi nội dung giữa mảng con được sắp xếp và vùng làm việc. Kết quả là, vùng làm việc trước đó chứa các phần tử được sắp xếp hợp nhất, trong khi các phần tử trước đó được lưu trữ trong vùng làm việc được chuyển đến hai mảng con.

Tuy nhiên, có hai ràng buộc phải được thỏa mãn:

  1. Khu vực làm việc nên nằm trong giới hạn của mảng. Nói cách khác, nó phải đủ lớn để chứa các phần tử được trao đổi mà không gây ra bất kỳ lỗi ngoài giới hạn nào.
  2. Vùng làm việc có thể được chồng lấp với một trong hai mảng được sắp xếp; tuy nhiên, nó phải đảm bảo rằng không có phần tử không được trộn nào được ghi đè.

Với thuật toán hợp nhất này được xác định, thật dễ dàng để tưởng tượng một giải pháp, có thể sắp xếp một nửa mảng; Câu hỏi tiếp theo là, làm thế nào để đối phó với phần còn lại của phần chưa được lưu trữ trong khu vực làm việc như dưới đây:

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

Một ý tưởng trực quan là phân loại đệ quy một nửa khu vực làm việc, do đó chỉ có 1/4 phần tử chưa được sắp xếp.

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

Điểm mấu chốt ở giai đoạn này là chúng ta phải hợp nhất 1/4 phần tử B được sắp xếp với 1/2 phần tử A được sắp xếp sớm hay muộn.

Là khu vực làm việc còn lại, chỉ chứa 1/4 phần tử, đủ lớn để hợp nhất A và B? Thật không may, nó không phải là.

Tuy nhiên, ràng buộc thứ hai được đề cập ở trên cho chúng ta một gợi ý, rằng chúng ta có thể khai thác nó bằng cách sắp xếp vùng làm việc chồng lấp với một mảng con nếu chúng ta có thể đảm bảo chuỗi hợp nhất mà các phần tử không được trộn sẽ không bị ghi đè.

Trên thực tế, thay vì sắp xếp nửa sau của vùng làm việc, chúng ta có thể sắp xếp nửa đầu và đặt vùng làm việc giữa hai mảng được sắp xếp như sau:

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

Thiết lập này sắp xếp hiệu quả khu vực làm việc chồng chéo với mảng con A. Ý tưởng này được đề xuất trong [Jyrki Katajainen, Tomi Pasanen, Jukka Teuhola. `` Thực tế sáp nhập tại chỗ ''. Tạp chí điện toán Bắc Âu, 1996].

Vì vậy, điều duy nhất còn lại là lặp lại bước trên, làm giảm vùng làm việc từ 1/2, 1/4, 1/8, khi Khi vùng làm việc trở nên đủ nhỏ (ví dụ: chỉ còn hai yếu tố), chúng ta có thể chuyển sang một loại chèn không đáng kể để kết thúc thuật toán này.

Đây là cách triển khai trong ANSI C dựa trên bài báo này.

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

Trường hợp wmerge được xác định trước đó.

Mã nguồn đầy đủ có thể được tìm thấy ở đây và giải thích chi tiết có thể được tìm thấy ở đây

Nhân tiện, phiên bản này không phải là loại hợp nhất nhanh nhất vì nó cần nhiều hoạt động trao đổi hơn. Theo thử nghiệm của tôi, nó nhanh hơn phiên bản tiêu chuẩn, phân bổ thêm không gian trong mỗi lần đệ quy. Nhưng nó chậm hơn phiên bản tối ưu hóa, làm tăng gấp đôi mảng ban đầu và sử dụng nó để hợp nhất thêm.


6
Knuth left this as an exercise (Vol 3, 5.2.5).đề cập đến ex. 13. [40] Thực hiện phương pháp nội bộ sắp xếp đề xuất [tại kết thúc phần này], sản xuất mà phân loại dữ liệu ngẫu nhiên trong O (N) đơn vị thời gian mith chỉ O (sqrt (N)) thêm vị trí nhớ. ? ( 40 chỉ ra Khá nhiều vấn đề khó khăn hoặc kéo dài có lẽ phù hợp như một dự án dài hạn trong các tình huống trong lớp học. )
greybeard

4
Tôi nghĩ rằng độ phức tạp thời gian của thuật toán tại chỗ được đề cập trong trang web penguin.ew là O (log n * n ^ 2). Vì vậy, chúng tôi có log n hợp nhất và mỗi hợp nhất là theo thứ tự O (n ^ 2). Có đúng không?
code4fun

1
Là thuật toán này vẫn ổn định và trong thời gian n log n?
Paul Stelian

1
@PaulStelian - nó không ổn định. Các phần tử trong vùng làm việc được sắp xếp lại theo các thao tác sắp xếp trên các phần tử trong vùng được sắp xếp. Điều này có nghĩa là các phần tử vùng làm việc có giá trị bằng nhau sẽ được sắp xếp lại để chúng không còn theo thứ tự ban đầu.
RCgldr

1
@PaulStelian - Wiki có một bài viết để sắp xếp khối hợp nhất , như bạn nhận xét là ổn định. Nó hoạt động tốt nhất nếu có ít nhất 2 giá trị duy nhất sqrt (n), cho phép chúng được sắp xếp lại để cung cấp các vùng làm việc của một mảng và vẫn ổn định.
RCgldr

59

Bao gồm cả "kết quả lớn" của nó, bài viết này mô tả một vài biến thể của loại hợp nhất tại chỗ (PDF):

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

Sắp xếp tại chỗ với ít di chuyển hơn

Jyrki Katajainen, Tomi A. Pasanen

Nó được chỉ ra rằng một mảng gồm n phần tử có thể được sắp xếp bằng cách sử dụng không gian thừa O (1), phần tử O (n log n / log log n) di chuyển và so sánh n log 2 n + O (n log log n). Đây là thuật toán sắp xếp tại chỗ đầu tiên yêu cầu di chuyển o (n log n) trong trường hợp xấu nhất trong khi đảm bảo so sánh O (n log n), nhưng do các yếu tố liên tục liên quan đến thuật toán chủ yếu là lợi ích lý thuyết.

Tôi nghĩ rằng điều này có liên quan quá. Tôi có một bản in của nó nằm xung quanh, được một đồng nghiệp truyền lại cho tôi, nhưng tôi chưa đọc nó. Nó dường như bao gồm lý thuyết cơ bản, nhưng tôi không đủ quen thuộc với chủ đề để đánh giá mức độ toàn diện:

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

Sáp nhập ổn định tối ưu

Antonios Symvonis

Bài viết này cho thấy cách hợp nhất ổn định hai chuỗi A và B có kích thước m và n, m ≤ n, tương ứng, với các phép gán O (m + n), so sánh O (mlog (n / m + 1)) và chỉ sử dụng một hằng số số lượng không gian bổ sung. Kết quả này phù hợp với tất cả các giới hạn dưới đã biết ...


12

Nó thực sự không dễ dàng và hiệu quả, và tôi khuyên bạn không nên làm điều đó trừ khi bạn thực sự phải làm (và có lẽ bạn không phải làm thế trừ khi đây là bài tập về nhà vì các ứng dụng sáp nhập tại chỗ chủ yếu là lý thuyết). Bạn không thể sử dụng quicksort thay thế? Quicksort dù sao cũng sẽ nhanh hơn với một vài tối ưu hóa đơn giản hơn và bộ nhớ thêm của nó là O (log N) .

Dù sao, nếu bạn phải làm điều đó thì bạn phải. Đây là những gì tôi tìm thấy: mộthai . Tôi không quen thuộc với loại hợp nhất tại chỗ, nhưng có vẻ như ý tưởng cơ bản là sử dụng các phép quay để tạo điều kiện hợp nhất hai mảng mà không cần sử dụng thêm bộ nhớ.

Lưu ý rằng điều này chậm hơn thậm chí so với loại hợp nhất cổ điển không có tại chỗ.


9
Quicksort không ổn định. Điều đó thực sự quan trọng đối với rất nhiều mã sản xuất.
Donal Fellows

7
quicksort có thể ổn định và sắp xếp hợp nhất iirc không nhất thiết phải ổn định nếu tại chỗ
jk.

4
@jk: Quicksort không ổn định; đó là tốc độ bắt nguồn từ đó và bạn không nên cố gắng yêu cầu khác. Đó là một sự đánh đổi rất tốt. Có, có thể liên kết chỉ mục gốc với phần còn lại của khóa để bạn không bao giờ có hai yếu tố giống nhau, tạo ra một loại ổn định; đi kèm với chi phí cần thiết của một số không gian bổ sung (tuyến tính theo số lượng phần tử) bởi vì bạn không thể duy trì thứ tự tương đối của các phần tử tương đương với nhau mà không cần dùng đến các chuyển động của phần tử bổ sung phá hủy hiệu suất.
Donal Fellows

4
Quicksort cũng có trường hợp xấu nhất O (n ^ 2) cho đầu vào được chế tạo đặc biệt
HoboBen

4
@DonalFellows: jk hoàn toàn chính xác; quicksort CÓ THỂ được thực hiện để ổn định, không có chi phí không gian thêm. Kiểm tra Wikipedia.
Rusty

10

Bước quan trọng là làm cho chính sự hợp nhất được đặt đúng chỗ. Điều đó không khó như những nguồn đó đưa ra, nhưng bạn sẽ mất thứ gì đó khi thử.

Nhìn vào một bước của sự hợp nhất:

[... liệt kê- sắp xếp ... | x ... danh sách- A ... | y ... danh sách- B ...]

Chúng ta biết rằng sắp xếp thứ tự là ít hơn mọi thứ khác, đó x là ít hơn mọi thứ khác trong Một , và rằng y là ít hơn mọi thứ khác trong B . Trong trường hợp x nhỏ hơn hoặc bằng y , bạn chỉ cần di chuyển con trỏ đến điểm bắt đầu của A trên một. Trong trường hợp y nhỏ hơn x , bạn phải xáo trộn y qua toàn bộ A để sắp xếp . Bước cuối cùng đó là những gì làm cho đắt tiền này (trừ trường hợp thoái hóa).

Nó thường rẻ hơn (đặc biệt là khi các mảng chỉ thực sự chứa các từ đơn cho mỗi phần tử, ví dụ: một con trỏ tới một chuỗi hoặc cấu trúc) để đánh đổi một khoảng không gian theo thời gian và có một mảng tạm thời riêng biệt mà bạn sắp xếp qua lại.


5
Hợp nhất tại chỗ của bạn có độ phức tạp trong trường hợp xấu nhất O (m n), trong đó m là kích thước A và n là kích thước B. Đây là trường hợp khi mục đầu tiên trong A lớn hơn mục cuối cùng trong B. Độ phức tạp có thể được cải thiện thành O (k log (k) + m + n), trong đó k = min (m, n) bằng cách thêm a Heap giữa A và B. Heap này nên chứa các mục từ A, lớn hơn các mục còn lại trong B, nhưng nhỏ hơn các mục còn lại trong A. Nếu A hết trước, thì heap phải được chuyển đến cuối B. Mặt khác, heap phải được chuyển đến đầu A. Sau đó, các mục heap phải được bật ra tại chỗ và đảo ngược để hoàn thành việc hợp nhất.
valyala

2
@valyala Lưu ý rằng khi sử dụng một đống, sắp xếp không còn ổn định. Ngoài ra, nếu bạn sử dụng một đống, bạn có thể đi với sắp xếp heap thay vì sắp xếp hợp nhất.
martinkunev


4

Một ví dụ về sáp nhập không đệm trong 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);
     }
}

Một ví dụ về sáp nhập thích ứng (tối ưu hóa).

Thêm mã hỗ trợ và sửa đổi để tăng tốc hợp nhất khi có sẵn bộ đệm phụ có kích thước bất kỳ (vẫn hoạt động mà không cần thêm bộ nhớ). Sử dụng hợp nhất tiến và lùi, xoay vòng, hợp nhất và sắp xếp chuỗi nhỏ và hợp nhất lặp lại.

#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
Bạn đã viết bài này chưa? Làm thế nào để nó vượt qua những khó khăn thể hiện trong các câu trả lời khác? Thời gian chạy của nó là gì?
Thomas Ahle

Điều này được điều chỉnh từ thư viện tùy chỉnh của riêng tôi , nhưng tôi đã không tạo ra các thuật toán này nếu đó là những gì bạn đang hỏi. Tăng trưởng là O (n (log n) ^ 2) không có bộ nhớ phụ; O (n log n) với bộ đệm đầy đủ. Điều này cố gắng trở thành một triển khai thực tế và được cấu trúc để hiển thị các thuật toán cấu thành.
Johnny Lồng

Tại sao bạn cần đệ quy hoặc bộ đệm bổ sung để hợp nhất hai danh sách được sắp xếp? Tôi nghĩ rằng nó có thể được thực hiện bằng cách di chuyển hai con trỏ về phía trước và trao đổi nếu trái lớn hơn bên phải.
jack

3

Câu trả lời này có một ví dụ mã , thực hiện thuật toán được mô tả trong bài viết Sáp nhập tại chỗ thực tế của Bing-Chao Huang và Michael A. Langston. Tôi phải thừa nhận rằng tôi không hiểu chi tiết, nhưng độ phức tạp nhất định của bước hợp nhất là O (n).

Từ góc độ thực tế, có bằng chứng cho thấy việc triển khai tại chỗ thuần túy không hoạt động tốt hơn trong các kịch bản trong thế giới thực. Ví dụ, tiêu chuẩn C ++ định nghĩa std :: inplace_merge , giống như tên ngụ ý một hoạt động hợp nhất tại chỗ.

Giả sử rằng các thư viện C ++ thường được tối ưu hóa rất tốt, thật thú vị khi xem cách nó được triển khai:

1) libstdc ++ (một phần của cơ sở mã GCC): std :: inplace_merge

Việc triển khai ủy quyền cho __inplace_merge , giúp tránh sự cố bằng cách phân bổ bộ đệm tạm thời:

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

Mặt khác, nó rơi trở lại triển khai ( __merge_without_buffer ), không yêu cầu thêm bộ nhớ, nhưng không còn chạy trong thời gian O (n).

2) libc ++ (một phần của cơ sở mã Clang): std :: inplace_merge

Trông tương tự. Nó ủy nhiệm cho một chức năng , cũng cố gắng phân bổ một bộ đệm . Tùy thuộc vào việc nó có đủ các yếu tố hay không, nó sẽ chọn thực hiện. Hàm dự phòng bộ nhớ không đổi được gọi là __buffered_inplace_merge .

Có thể ngay cả dự phòng vẫn là thời gian O (n), nhưng vấn đề là họ không sử dụng triển khai nếu có bộ nhớ tạm thời.


Lưu ý rằng tiêu chuẩn C ++ rõ ràng cho phép triển khai tự do lựa chọn phương pháp này bằng cách hạ thấp độ phức tạp cần thiết từ O (n) xuống O (N log N):

Độ phức tạp: So sánh chính xác N-1 nếu có đủ bộ nhớ bổ sung. Nếu bộ nhớ không đủ, O (N log N) so sánh.

Tất nhiên, điều này không thể được coi là một bằng chứng cho thấy không gian không đổi tại chỗ hợp nhất trong thời gian O (n) không bao giờ nên được sử dụng. Mặt khác, nếu nó nhanh hơn, các thư viện C ++ được tối ưu hóa có thể sẽ chuyển sang loại thực hiện đó.


2

Đây là phiên bản C của tôi:

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

Lưu ý rằng việc thực hiện này mất (n ^ 2 log n) trong trường hợp xấu nhất (mảng đảo ngược).
martinkunev

1

Có một cách thực hiện tương đối đơn giản về sắp xếp hợp nhất tại chỗ bằng cách sử dụng kỹ thuật ban đầu của Kronrod nhưng với việc thực hiện đơn giản hơn. Một ví dụ bằng hình ảnh minh họa kỹ thuật này có thể được tìm thấy ở đây: http://www.logiccoder.com/TheSortProbols/BestMergeInfo.htmlm .

Ngoài ra còn có các liên kết đến phân tích lý thuyết chi tiết hơn bởi cùng một tác giả liên quan đến liên kết này.


liên kết này dẫn đến kết quả 403
Charlotte Tan

3
Liên kết là cố định. Các tài liệu có mật mã đến mức khó hiểu. Tôi có ấn tượng rằng có một ý tưởng thú vị ở đó, nhưng không có thuật toán nào được trình bày, chỉ có một bộ sơ đồ và một số mô tả khá yếu. Tôi đã không thể buộc các mô tả yếu vào sơ đồ một cách thú vị, vì vậy tôi đã từ bỏ.
Ira Baxter

-6

Tôi vừa thử thuật toán hợp nhất tại chỗ để sắp xếp hợp nhất trong JAVA bằng cách sử dụng thuật toán sắp xếp chèn, sử dụng các bước sau.
1) Hai mảng được sắp xếp có sẵn.
2) So sánh các giá trị đầu tiên của mỗi mảng; và đặt giá trị nhỏ nhất vào mảng đầu tiên.
3) Đặt giá trị lớn hơn vào mảng thứ hai bằng cách sử dụng sắp xếp chèn (di chuyển từ trái sang phải).
4) Sau đó, một lần nữa so sánh giá trị thứ hai của mảng thứ nhất và giá trị đầu tiên của mảng thứ hai và làm tương tự. Nhưng khi trao đổi xảy ra, có một số đầu mối bỏ qua so sánh các mục khác, nhưng chỉ cần trao đổi.

Tôi đã thực hiện một số tối ưu hóa ở đây; để giữ các so sánh ít hơn trong sắp xếp chèn.
Hạn chế duy nhất tôi tìm thấy với các giải pháp này là nó cần hoán đổi các phần tử mảng lớn hơn trong mảng thứ hai.

ví dụ)

Đầu tiên __Array: 3, 7, 8, 9

Mảng thứ hai: 1, 2, 4, 5

Sau đó, 7, 8, 9 làm cho mảng thứ hai hoán đổi (di chuyển sang trái một) tất cả các phần tử của nó mỗi lần để đặt chính nó vào vị trí cuối cùng.

Vì vậy, giả định ở đây là trao đổi vật phẩm là không đáng kể so với so sánh hai mặt hàng.

https://github.com/skanagavelu/alerskams/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
Đó là cả O (n ^ 2) và cũng rất khó đọc (vì thỉnh thoảng xảy ra lỗi cú pháp và kiểu không nhất quán / kém)
glaba
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.