2009-09-07 47 views
13

Tôi có một dãy các giá trị gần như, nhưng không hoàn toàn được sắp xếp, với một vài giá trị được di dời (ví dụ 50 trong 100000). Làm thế nào để sắp xếp nó hiệu quả nhất? (hiệu suất là cực kỳ quan trọng ở đây và nên được cách nhanh hơn O (N)).Cách sắp xếp các mảng gần như sắp xếp trong thời gian nhanh nhất có thể? (Java)

Tôi biết về smoothsort, nhưng tôi không thể tìm thấy triển khai Java. Có ai biết liệu nó đã được triển khai chưa? Hoặc những gì tôi có thể sử dụng cho nhiệm vụ này thay vì smoothsort?

+33

Bạn không thể sắp xếp nhanh hơn O (N), vì đó là thời gian bạn cần xác định xem mảng của bạn có được sắp xếp hay không. – Botz3000

+4

Có thể có thêm thông tin về mảng. Nói rằng tất cả các thành viên di dời có thể là cuối cùng, sau đó bạn có thể sắp xếp chúng (O (m log m)) và sau đó hành động như thể chúng được chèn (O (m log log n) vào O (m log n) để tìm chèn vị trí). –

+1

nó có thể yêu cầu phần cứng khác nhau nhưng phân loại mạng có thể sắp xếp trong O ((log n)^2) .Hãy liên kết [sắp xếp mạng] (http://staff.ustc.edu.cn/~csli/graduate/algorithms/book6 /chap28.htm) –

Trả lời

18

Thực ra, Wikipedia chứa Java thực hiện mịn. Bạn có thể tìm thấy ở đây:

http://en.wikipedia.org/wiki/Smoothsort.

+1

Nó không hoạt động tốt trên điểm chuẩn mà tôi đã thực hiện. Nó có các thuộc tính thuật toán tốt đẹp, nhưng hành vi bộ nhớ cache là khủng khiếp. – deadalnix

+0

Việc triển khai Java đã bị xóa khỏi bài viết. Mã gốc [có sẵn bên dưới] (http://stackoverflow.com/questions/1390832/how-to-sort-nearly-sorted-array-in-the-fastest-time-possible-java/28352545#28352545). – 11101101b

7

Như Botz3000 đã lưu ý, bạn không thể thực hiện thao tác nhanh hơn O (N). Yếu tố cơ bản nhất của bất kỳ thuật toán nào là tìm các mục nhập đó trong mảng không đúng thứ tự. Điều này đòi hỏi O (N), ngay cả trước khi bạn tìm ra những gì để làm với họ.

Nếu thực sự là số nguyên tố "out-of-trật tự" là mệnh lệnh của cường độ dưới tổng số của các yếu tố, bạn có thể sử dụng các thuật toán sau (giả sử linked list):

  1. Tìm tất cả dùng ngoài trời các mục theo thứ tự và trích xuất từ ​​danh sách gốc sang một danh sách riêng biệt, O (N)
  2. Kết quả là hai danh sách: danh sách được sắp xếp và danh sách trích xuất ngắn
  3. Đối với mỗi phần tử được trích xuất, chèn chúng vào danh sách được sắp xếp. Đó sẽ là O (log (N)) cho mỗi, tổng là O (Xlog (N)), trong đó X là số phần tử được trích xuất. Nếu X là rất nhỏ so với N, bạn kết thúc với tổng số O (N).
+2

Thực ra không khó để chứng minh. Bạn có thể chứng minh nó bằng cách tính giới hạn của xlog (n) khi x/n -> 0. –

+1

Làm thế nào để bạn lấy được O (log N) ở bước 3? Khi sử dụng danh sách liên kết, tìm kiếm nhị phân có thể khó thực hiện ... – Dirk

+0

@Dirk: ngay cả khi bạn có thể thực hiện tìm kiếm nhị phân, bạn có thể chèn tất cả các phần tử trong thời gian O (XN), cho X nhỏ, tạo ra O (N). –

10

Cocktail Sắp xếp

Nếu bạn muốn có một thuật toán đơn giản đó là dễ dàng để thực hiện, bạn có thể làm một cocktail sort. Nó sẽ làm việc hợp lý tốt trên đầu vào gần như sắp xếp.

+1

+1 để dạy tôi về tên cho loại cocktail. –

+1

@Henk: cùng ở đây. Ở trường trung học, tôi đã thực hiện nó như là một cải tiến về loại bong bóng, nhưng tôi không bao giờ biết có một cái tên cho nó. –

2

Chỉ cần đặt nó lên bàn, một loại bong bóng được thực hiện tốt chắc chắn sẽ là thuật toán đơn giản nhất ở đây. Với trường hợp xấu nhất của O (n * m), m là số lượng chuyển vị. Phần m phụ thuộc rất nhiều vào mô hình chuyển vị, thường tổng độ phức tạp sẽ là O (n).

0

Bạn đúng về khả năng đạt O (N), nhưng giả sử một máy đa lõi (mà tôi có), chúng tôi có thể ăn gian một chút bằng cách sử dụng thuật toán sắp xếp song song.

+0

Vâng, thuật toán vẫn chạy trong O (N). O (N/2) là O (N). –

+3

@Martinho: trừ khi bạn có một số lõi phát triển với kích thước của đầu vào :) –

+0

@Rax: cái gì đó lợi dụng các vũ trụ song song, như bogosort lượng tử http://en.wikipedia.org/wiki/Bogosort# Quantum_Bogosort? –

0

Thực hiện những gì chúng tôi gọi ở trường là Loại vỏ. Đó là bong bóng phụ mảng. Một mảng phụ với bước k là một mảng các phần tử có chỉ số 0, k, 2k, 3k ...

Nếu bạn chọn k = 3i + 1 và thực hiện nhiều loại bong bóng, bắt đầu từ cao hơn là 0, thời gian sẽ nhỏ hơn trên mảng gần như sắp xếp.

4

[Sun] JDK7 có (hoặc sẽ có) triển khai Tim sort (từ Python). Đó là một loại hợp nhất tận dụng thứ tự đã tồn tại trong mảng.

+0

Bạn có thể vui lòng cung cấp liên kết hoặc giải thích cho mình cách sắp xếp theo thời gian hoạt động không? –

+0

Ok, đã tìm thấy và thêm liên kết. –

+0

Sắp xếp Tim lần đầu tiên được triển khai cho python, và khi một số anh chàng java (tôi nghĩ rằng đó là josh bloch) đã thấy nó, anh ta nói "chúng ta cần đưa nó vào java" http://svn.python.org/projects/python/ trunk/Objects/listsort.txt –

3

Smoothsort hoặc Timsort là các thuật toán tuyệt vời và sẽ là những điều hợp lý để sử dụng.

Tôi muốn thêm rằng những gì bạn có thể không nhận ra là khiêm tốn insertion sort là thích ứng.Thật vậy, đối với danh sách thực sự gần như sắp xếp, như bạn dường như có, sự hiểu biết của tôi (mà tôi không thể sao lưu với một tham chiếu) là nó nhanh hơn so với các thuật toán phức tạp hơn. Vấn đề là nếu đầu vào không được sắp xếp gần như, nó sẽ nhanh chóng giảm xuống O (n^2). Tuy nhiên, nó rất đơn giản để thực hiện một cách chính xác, vì vậy nếu bạn biết chắc chắn rằng đầu vào của bạn luôn luôn được sắp xếp gần như, nó sẽ là một lựa chọn tốt.

5

Có nhiều thuật toán tốt cho việc này.

Smoothsort là yêu thích cá nhân của tôi ... Tôi thực sự đã làm tất cả toán học ra here nếu bạn tò mò tại sao nó hoạt động rất tốt.

Thuật toán khá tốt cho dữ liệu đã được sắp xếp là sáp nhập tự nhiên, là phiên bản từ dưới cùng của hợp nhất hoạt động bằng cách xử lý đầu vào như một chuỗi các phân đoạn được sắp xếp, sau đó thực hiện nhiều lần vượt qua phạm vi kết hợp liền kề các dải ô được sắp xếp. Nó chạy trong thời gian O (n) nếu dữ liệu đã được sắp xếp (vì nó có thể phát hiện rằng chỉ có một dải được sắp xếp), và O (n lg n) trong trường hợp xấu nhất. Thuật toán này hoạt động khá tốt nếu dữ liệu là "khối được sắp xếp"; có nghĩa là, nó bao gồm rất nhiều khối được sắp xếp được đặt ngay cạnh nhau.

Sắp xếp chèn thẳng chắc chắn hoạt động tốt cho dữ liệu được sắp xếp chủ yếu, nhưng có thể làm suy giảm rất nặng trên nhiều đầu vào. Một số loại thực sự tốt (như introsort) thực sự sử dụng thuộc tính này của loại sắp xếp để làm một "bước dọn dẹp" trên đầu vào.

+0

+1000000 cho bài viết của bạn về smoothsort - như bạn nói, nó hấp dẫn nhưng woefully underdocumented (và lưu ý ban đầu của EWD trên nó là một đọc rất bực bội), do đó, bài viết của bạn là cả một bữa ăn nuôi dưỡng và * một balm nhẹ nhàng. –

+0

Tôi yêu bài viết của bạn. Công việc tuyệt vời làm cho điều này dễ hiểu. – deadalnix

0

Đây là triển khai thực hiện Java gốc của Smoothsort được sử dụng để có sẵn thông qua Wikipedia article.

// by keeping these constants, we can avoid the tiresome business 
// of keeping track of Dijkstra's b and c. Instead of keeping 
// b and c, I will keep an index into this array. 

static final int LP[] = { 1, 1, 3, 5, 9, 15, 25, 41, 67, 109, 
    177, 287, 465, 753, 1219, 1973, 3193, 5167, 8361, 13529, 21891, 
    35421, 57313, 92735, 150049, 242785, 392835, 635621, 1028457, 
    1664079, 2692537, 4356617, 7049155, 11405773, 18454929, 29860703, 
    48315633, 78176337, 126491971, 204668309, 331160281, 535828591, 
    866988873 // the next number is > 31 bits. 
}; 

public static <C extends Comparable<? super C>> void sort(C[] m, 
    int lo, int hi) { 
    int head = lo; // the offset of the first element of the prefix into m 

    // These variables need a little explaining. If our string of heaps 
    // is of length 38, then the heaps will be of size 25+9+3+1, which are 
    // Leonardo numbers 6, 4, 2, 1. 
    // Turning this into a binary number, we get b01010110 = 0x56. We represent 
    // this number as a pair of numbers by right-shifting all the zeros and 
    // storing the mantissa and exponent as "p" and "pshift". 
    // This is handy, because the exponent is the index into L[] giving the 
    // size of the rightmost heap, and because we can instantly find out if 
    // the rightmost two heaps are consecutive Leonardo numbers by checking 
    // (p&3)==3 

    int p = 1; // the bitmap of the current standard concatenation >> pshift 
    int pshift = 1; 

    while (head < hi) { 
    if ((p & 3) == 3) { 
     // Add 1 by merging the first two blocks into a larger one. 
     // The next Leonardo number is one bigger. 
     sift(m, pshift, head); 
     p >>>= 2; 
     pshift += 2; 
    } else { 
     // adding a new block of length 1 
     if (LP[pshift - 1] >= hi - head) { 
     // this block is its final size. 
     trinkle(m, p, pshift, head, false); 
     } else { 
     // this block will get merged. Just make it trusty. 
     sift(m, pshift, head); 
     } 

     if (pshift == 1) { 
     // LP[1] is being used, so we add use LP[0] 
     p <<= 1; 
     pshift--; 
     } else { 
     // shift out to position 1, add LP[1] 
     p <<= (pshift - 1); 
     pshift = 1; 
     } 
    } 
    p |= 1; 
    head++; 
    } 

    trinkle(m, p, pshift, head, false); 

    while (pshift != 1 || p != 1) { 
    if (pshift <= 1) { 
     // block of length 1. No fiddling needed 
     int trail = Integer.numberOfTrailingZeros(p & ~1); 
     p >>>= trail; 
     pshift += trail; 
    } else { 
     p <<= 2; 
     p ^= 7; 
     pshift -= 2; 

     // This block gets broken into three bits. The rightmost 
     // bit is a block of length 1. The left hand part is split into 
     // two, a block of length LP[pshift+1] and one of LP[pshift]. 
     // Both these two are appropriately heapified, but the root 
     // nodes are not necessarily in order. We therefore semitrinkle 
     // both of them 

     trinkle(m, p >>> 1, pshift + 1, head - LP[pshift] - 1, true); 
     trinkle(m, p, pshift, head - 1, true); 
    } 

    head--; 
    } 
} 

private static <C extends Comparable<? super C>> void sift(C[] m, int pshift, 
    int head) { 
    // we do not use Floyd's improvements to the heapsort sift, because we 
    // are not doing what heapsort does - always moving nodes from near 
    // the bottom of the tree to the root. 

    C val = m[head]; 

    while (pshift > 1) { 
    int rt = head - 1; 
    int lf = head - 1 - LP[pshift - 2]; 

    if (val.compareTo(m[lf]) >= 0 && val.compareTo(m[rt]) >= 0) 
     break; 
    if (m[lf].compareTo(m[rt]) >= 0) { 
     m[head] = m[lf]; 
     head = lf; 
     pshift -= 1; 
    } else { 
     m[head] = m[rt]; 
     head = rt; 
     pshift -= 2; 
    } 
    } 

    m[head] = val; 
} 

private static <C extends Comparable<? super C>> void trinkle(C[] m, int p, 
    int pshift, int head, boolean isTrusty) { 

    C val = m[head]; 

    while (p != 1) { 
    int stepson = head - LP[pshift]; 

    if (m[stepson].compareTo(val) <= 0) 
     break; // current node is greater than head. Sift. 

    // no need to check this if we know the current node is trusty, 
    // because we just checked the head (which is val, in the first 
    // iteration) 
    if (!isTrusty && pshift > 1) { 
     int rt = head - 1; 
     int lf = head - 1 - LP[pshift - 2]; 
     if (m[rt].compareTo(m[stepson]) >= 0 
      || m[lf].compareTo(m[stepson]) >= 0) 
     break; 
    } 

    m[head] = m[stepson]; 

    head = stepson; 
    int trail = Integer.numberOfTrailingZeros(p & ~1); 
    p >>>= trail; 
    pshift += trail; 
    isTrusty = false; 
    } 

    if (!isTrusty) { 
    m[head] = val; 
    sift(m, pshift, head); 
    } 
} 
Các vấn đề liên quan