Thuật toán tại chỗ để xen kẽ một mảng


62

Bạn được cung cấp một mảng gồm 2viết sai rồi phần tử

một1,một2,Giáo dục,mộtviết sai rồi,b1,b2,Giáo dụcbviết sai rồi

Nhiệm vụ là xen kẽ mảng, sử dụng thuật toán tại chỗ sao cho mảng kết quả trông giống như

b1,một1,b2,một2,Giáo dục,bviết sai rồi,mộtviết sai rồi

Nếu yêu cầu tại chỗ không có, chúng ta có thể dễ dàng tạo một mảng mới và sao chép các phần tử bằng thuật toán thời gian Ôi(viết sai rồi) .

Với yêu cầu tại chỗ, thuật toán chia và chinh phục sẽ nâng thuật toán thành θ(viết sai rồiđăng nhậpviết sai rồi) .

Vì vậy, câu hỏi là:

Có thuật toán thời gian Ôi(viết sai rồi) , cũng có tại chỗ không?

(Lưu ý: Bạn có thể giả sử mô hình RAM WORD chi phí thống nhất, do đó, tại chỗ chuyển thành giới hạn không gian Ôi(1) ).


1
Đây là trên stackoverflow nhưng họ không đưa ra một giải pháp chất lượng. Câu trả lời được đánh giá cao nhất là: "Vấn đề này không tầm thường như mọi người nghĩ. Bài tập về nhà? LOL. Có một giải pháp về arXiv " Nhưng giải pháp arxiv yêu cầu một số lý thuyết số + bằng chứng tham khảo trong các bài báo khác. Nó sẽ là tốt đẹp để có một giải pháp ngắn gọn ở đây.
Joe


Một chủ đề khác về Stack Overflow: stackoverflow.com/questions/15996288/
Kẻ

Câu trả lời:


43

Đây là câu trả lời dựa trên thuật toán từ bài báo được liên kết bởi Joe: http://arxiv.org/abs/0805.1598

Trước tiên chúng ta hãy xem xét một thuật toán Θ(viết sai rồiđăng nhậpviết sai rồi) sử dụng phép chia và chinh phục.

1) Chia rẽ và chinh phục

Chúng ta được cho

một1,một2,Giáo dục,b1,b2,Giáo dụcbviết sai rồi

Bây giờ sử dụng phân chia và chinh phục, đối với một số m= =Θ(viết sai rồi) , chúng tôi cố gắng để có được mảng

[một1,một2,Giáo dục,mộtm,b1,b2,Giáo dục,bm],[mộtm+1,Giáo dục,mộtviết sai rồi,bm+1,Giáo dụcbviết sai rồi]

và tái diễn.

Chú ý rằng phần

b1,b2,Giáo dụcbm,mộtm+1,Giáo dụcmộtviết sai rồi
là một sự thay đổi theo chu kỳ của

mộtm+1,Giáo dụcmộtviết sai rồi,b1,Giáo dụcbm

bởi m nơi.

Đây là một cổ điển và có thể được thực hiện tại chỗ bằng ba lần đảo ngược và trong thời gian Ôi(viết sai rồi) .

Do đó, phân chia và chinh phục mang đến cho bạn một Θ(viết sai rồiđăng nhậpviết sai rồi) thuật toán, với một đệ quy tương tự như T(viết sai rồi)= =2T(viết sai rồi/2)+Θ(viết sai rồi) .

2) Chu kỳ hoán vị

Bây giờ, một cách tiếp cận khác cho vấn đề là xem xét hoán vị như một tập hợp các chu kỳ rời rạc.

Hoán vị được đưa ra bởi (giả sử bắt đầu từ 1 )

j2jphép chia lấy phần dư2viết sai rồi+1

Nếu bằng cách nào đó chúng ta biết chính xác chu kỳ là gì, sử dụng không gian thừa không đổi, chúng ta có thể nhận ra hoán vị bằng cách chọn một phần tử Một , xác định phần tử đó đi đâu (sử dụng công thức trên), đặt phần tử vào vị trí đích vào không gian tạm thời, đặt phần tử Một vào vị trí mục tiêu đó và tiếp tục trong chu kỳ. Khi chúng ta hoàn thành một chu kỳ, chúng ta chuyển sang một phần tử của chu kỳ tiếp theo và tiếp tục theo chu trình đó.

Điều này sẽ cung cấp cho chúng tôi thuật toán thời gian Ôi(viết sai rồi) , nhưng nó giả định rằng chúng tôi "bằng cách nào đó biết chu kỳ chính xác là gì" và cố gắng thực hiện việc giữ sách này trong giới hạn không gian Ôi(1) là điều khiến vấn đề này trở nên khó khăn.

Đây là nơi bài báo sử dụng lý thuyết số.

Nó có thể được chỉ ra rằng, trong trường hợp khi 2viết sai rồi+1= =3k , các yếu tố tại các vị trí 1 , 3,32,Giáo dục,3k-1 là trong chu kỳ khác nhau và mỗi chu kỳ có chứa một phần tử ở vị trí 3m,m0 .

Này sử dụng thực tế là 2 là một máy phát điện của (Z/3k)* .

Do đó, khi 2viết sai rồi+1= =3k , cách tiếp cận theo chu kỳ cung cấp cho chúng tôi thuật toán thời gian Ôi(viết sai rồi) , vì với mỗi chu kỳ, chúng tôi biết chính xác bắt đầu từ đâu: sức mạnh của 3 (bao gồm 1 ) (chúng có thể được tính trong Ôi(1) không gian).

3) Thuật toán cuối cùng

Bây giờ chúng tôi kết hợp hai phần trên: Chu kỳ phân chia và chinh phục + hoán vị.

Chúng tôi chia và chinh phục, nhưng chọn m sao cho 2m+1 là lũy thừa của 3m=Θ(n) .

Vì vậy, thay vì đệ quy trên cả hai "nửa", chúng tôi chỉ tái diễn trên một và thực hiện thêm Θ(n) công việc phụ.

Điều này cho chúng ta sự tái diễn T(n)=T(cn)+Θ(n) (với một số 0<c<1 ) và do đó cung cấp cho chúng ta thuật toán không gian O(n) , O(1) !


4
Nó thật đẹp.
Raphael

1
Rất đẹp. Đi qua các ví dụ hoán vị, bây giờ tôi hiểu hầu hết về nó. Hai câu hỏi: 1. Làm thế nào để bạn thực sự tìm thấy giá trị m? Giấy yêu cầu phải mất O (log n), tại sao? 2. Có thể DE xen kẽ một mảng bằng cách sử dụng một cách tiếp cận tương tự không?
num3ric

2
@ num3ric: 1) Bạn tìm thấy công suất cao nhất trong < n . Vì vậy, nó sẽ là O ( log n ) . 2). Vâng, có thể, tôi tin rằng tôi đã thêm một câu trả lời trên stackoverflow ở đâu đó. Các nhà lãnh đạo chu kỳ trong trường hợp đó tôi tin rằng đã xuất hiện trong 2 a 3 b (cho 2 m + 1 = sức mạnh của 3 ). 3<nÔi(đăng nhậpviết sai rồi)2một3b2m+13
Aryabhata

@Aryabhata tại sao chúng ta chỉ tái diễn trên một "nửa", thay vì hai "nửa"?
sinoTrinity

1
@Aryabhata Thuật toán này có thể được mở rộng để xen kẽ nhiều hơn hai mảng không? Ví dụ chuyển vào c 1 , b 1 , một 1 , c 2 , b 2 , một 2 , ... ,một1,một2,Giáo dục,mộtviết sai rồi,b1,b2,Giáo dục,bviết sai rồi,c1,c2,Giáo dục,cviết sai rồi hoặc một cái gì đó tương tự. c1,b1,một1,c2,b2,một2,Giáo dục,cviết sai rồi,bviết sai rồi,mộtviết sai rồi
Nghi ngờ

18

Tôi khá chắc chắn rằng tôi đã tìm thấy một thuật toán không dựa trên lý thuyết số hoặc lý thuyết chu kỳ. Lưu ý rằng có một vài chi tiết để giải quyết (có thể là vào ngày mai), nhưng tôi khá tự tin rằng họ sẽ làm việc. Tôi rửa tay như tôi đang ngủ, không phải vì tôi đang cố che giấu vấn đề :)

Hãy Alà mảng đầu tiên, Bthứ hai |A| = |B| = Nvà giả sử N=2^kcho một số k, cho đơn giản. Hãy A[i..j]là subarray của Avới các chỉ số ithông qua j, bao gồm. Mảng là dựa trên 0. Đặt RightmostBitPos(i)trả về vị trí (dựa trên 0) của bit ngoài cùng bên phải là '1' i, tính từ bên phải. Các thuật toán hoạt động như sau.

GetIndex(i) {
    int rightPos = RightmostBitPos(i) + 1;
    return i >> rightPos;
}

Interleave(A, B, N) {
    if (n == 1) {
        swap(a[0], b[0]);
    }
    else {
        for (i = 0; i < N; i++)
            swap(A[i], B[GetIndex(i+1)]);

        for (i = 1; i <= N/2; i*=2)
            Interleave(B[0..i/2-1], B[i/2..i-1], i/2);

        Interleave(B[0..N/2], B[N/2+1..N], n/2);
    }
}

Chúng ta hãy lấy một dãy gồm 16 số và chúng ta hãy bắt đầu xen kẽ chúng bằng cách hoán đổi và xem điều gì sẽ xảy ra:

1 2 3 4 5 6 7 8    | 9 10 11 12 13 14 15 16
9 2 3 4 5 6 7 8    | 1 10 11 12 13 14 15 16
9 1 3 4 5 6 7 8    | 2 10 11 12 13 14 15 16
9 1 10 4 5 6 7 8   | 2 3 11 12 13 14 15 16
9 1 10 2 5 6 7 8   | 4 3 11 12 13 14 15 16
9 1 10 2 11 6 7 8  | 4 3 5 12 13 14 15 16
9 1 10 2 11 3 7 8  | 4 6 5 12 13 14 15 16
9 1 10 2 11 3 12 8 | 4 6 5 7 13 14 15 16
9 1 10 2 11 3 12 4 | 8 6 5 7 13 14 15 16

Quan tâm đặc biệt là phần đầu tiên của mảng thứ hai:

|
| 1
| 2
| 2 3
| 4 3
| 4 3 5
| 4 6 5
| 4 6 5 7
| 8 6 5 7

Mẫu phải rõ ràng: chúng tôi luân phiên thêm một số vào cuối và thay thế số thấp nhất bằng một số cao. Lưu ý rằng chúng tôi luôn thêm một số cao hơn một số cao nhất chúng tôi đã có. Nếu chúng ta bằng cách nào đó có thể tìm ra chính xác con số nào là thấp nhất tại bất kỳ thời điểm nào, chúng ta có thể làm điều đó một cách dễ dàng.

Bây giờ, chúng ta đi ví dụ lớn hơn để xem liệu chúng ta có thể thấy một mẫu không. Lưu ý rằng chúng ta không cần sửa kích thước của mảng để xây dựng ví dụ trên. Tại một số điểm, chúng tôi nhận được cấu hình này (dòng thứ hai trừ 16 từ mỗi số):

16 24 20 28 18 22 26 30 17 19 21 23 25 27 29 31
0   8  4 12  2  6 10 14  1  3  5  7  9 11 13 15

Bây giờ, điều này hiển thị rõ ràng một mẫu: "1 3 5 7 9 11 13 15" cách nhau 2, "2 6 10 14" cách nhau 4 và "4 12" cách nhau 8. Do đó, chúng ta có thể nghĩ ra một thuật toán cho chúng ta biết con số nhỏ nhất tiếp theo sẽ là gì: cơ chế hoạt động khá chính xác cách thức các số nhị phân hoạt động. Bạn có một chút cho nửa cuối của mảng, một chút cho quý thứ hai, v.v.

đăng nhậpviết sai rồiđăng nhậpviết sai rồiÔi(1)

Ôi(viết sai rồi)Ôi(viết sai rồi)

Ôi(viết sai rồi)Ôi(đăng nhậpviết sai rồi)Ôi(1)

Bây giờ, câu hỏi là: có một số mẫu trong phần chúng ta cần sắp xếp? Thử 32 số cho chúng tôi "16 12 10 14 9 11 13 15" để khắc phục. Lưu ý rằng chúng ta có cùng một mô hình chính xác ở đây! "9 11 13 15", "10 14" và "12" được nhóm lại với nhau theo cùng một kiểu mà chúng ta đã thấy trước đó.

Bây giờ, mẹo là để đệ quy xen kẽ các phần con này. Chúng tôi xen kẽ "16" và "12" đến "12 16". Chúng tôi xen kẽ "12 16" và "10 14" đến "10 12 14 16". Chúng tôi xen kẽ "10 12 14 16" và "9 11 13 15" thành "9 10 11 12 13 14 15 16". Điều này sắp xếp phần đầu tiên.

Ôi(viết sai rồi)Ôi(viết sai rồi)

Một ví dụ:

Interleave the first half:
1 2 3 4 5 6 7 8    | 9 10 11 12 13 14 15 16
9 2 3 4 5 6 7 8    | 1 10 11 12 13 14 15 16
9 1 3 4 5 6 7 8    | 2 10 11 12 13 14 15 16
9 1 10 4 5 6 7 8   | 2 3 11 12 13 14 15 16
9 1 10 2 5 6 7 8   | 4 3 11 12 13 14 15 16
9 1 10 2 11 6 7 8  | 4 3 5 12 13 14 15 16
9 1 10 2 11 3 7 8  | 4 6 5 12 13 14 15 16
9 1 10 2 11 3 12 8 | 4 6 5 7 13 14 15 16
9 1 10 2 11 3 12 4 | 8 6 5 7 13 14 15 16
Sort out the first part of the second array (recursion not explicit):
8 6 5 7 13 14 15 16
6 8 5 7 13 14 15 16
5 8 6 7 13 14 15 16
5 6 8 7 13 14 15 16
5 6 7 8 13 14 15 16
Interleave again:
5 6 7 8   | 13 14 15 16
13 6 7 8  | 5 14 15 16
13 5 7 8  | 6 14 15 16
13 5 14 8 | 6 7 15 16
13 5 14 6 | 8 7 15 16
Sort out the first part of the second array:
8 7 15 16
7 8 15 16
Interleave again:
7 8 | 15 16
15 8 | 7 16
15 7 | 8 16
Interleave again:
8 16
16 8
Merge all the above:
9 1 10 2 11 3 12 4 | 13 5 14 6 | 15 7 | 16 8

Hấp dẫn. Bạn có sẵn sàng thử và viết một bằng chứng chính thức? Tôi biết rằng có một thuật toán khác (được đề cập trong bài báo mà Joe tìm thấy) có liên quan đến các bit. Có lẽ bạn đã khám phá lại nó!
Aryabhata

1

Dưới đây là một thuật toán tại chỗ không đệ quy trong thuật toán thời gian tuyến tính để xen kẽ hai nửa của một mảng không có bộ nhớ bổ sung.

Ý tưởng chung rất đơn giản: Đi qua nửa đầu của mảng từ trái sang phải, hoán đổi các giá trị chính xác vào vị trí. Khi bạn tiến lên, các giá trị bên trái chưa được sử dụng sẽ bị hoán đổi vào khoảng trống bị bỏ trống bởi các giá trị bên phải. Bí quyết duy nhất là tìm ra cách kéo chúng ra một lần nữa.

Chúng tôi bắt đầu với một mảng có kích thước N chia thành 2 nửa gần bằng nhau.
[ left_items | right_items ]
Khi chúng tôi xử lý nó, nó trở thành
[ placed_items | remaining_left_items| swapped_left_items | remaining_right_items]

Không gian hoán đổi phát triển theo mẫu sau: A) tăng không gian bằng cách loại bỏ mục bên phải liền kề và hoán đổi trong một mục mới từ bên trái; B) hoán đổi vật phẩm cũ nhất với vật phẩm mới từ bên trái. Nếu các mục bên trái được đánh số 1..N, mẫu này trông giống như

step swapspace index changed
1    A: 1         0
2    B: 2         0
3    A: 2 3       1
4    B: 4 3       0     
5    A: 4 3 5     2
6    B: 4 6 5     1
7    A: 4 6 5 7   3
...

Trình tự thay đổi chỉ mục chính xác là OEIS A025480 , có thể được tính toán bằng một quy trình đơn giản. Điều này cho phép tìm thấy vị trí trao đổi chỉ với số lượng vật phẩm được thêm vào cho đến nay, đây cũng là chỉ số của vật phẩm hiện tại được đặt.

Đó là tất cả thông tin chúng ta cần để đưa vào nửa đầu của chuỗi theo thời gian tuyến tính.

Khi chúng ta đến điểm giữa, mảng sẽ có ba phần: [ placed_items | swapped_left_items | remaining_right_items] Nếu chúng ta có thể xắp xếp lại các mục bị tráo đổi, chúng ta đã giảm vấn đề xuống một nửa kích thước và có thể lặp lại.

Để xắp xếp lại không gian hoán đổi, chúng tôi sử dụng thuộc tính sau: Một chuỗi được xây dựng bằng cách Nxen kẽ các hoạt động chắp nối và hoán đổi sẽ chứa N/2các mục trong đó độ tuổi của chúng được đưa ra A025480(N/2)..A025480(N-1). (Phân chia số nguyên, giá trị nhỏ hơn cũ hơn).

Ví dụ: nếu nửa bên trái ban đầu giữ các giá trị 1..19, thì không gian hoán đổi sẽ chứa [16, 12, 10, 14, 18, 11, 13, 15, 17, 19]. A025480 (9..18) [2, 5, 1, 6, 3, 7, 0, 8, 4, 9], chính xác là danh sách các chỉ mục của các mục từ cũ nhất đến mới nhất.

Vì vậy, chúng ta có thể xắp xếp lại không gian hoán đổi của mình bằng cách tiến qua nó và hoán đổi S[i]với S[ A(N/2 + i)]. Đây cũng là thời gian tuyến tính.

Sự phức tạp còn lại là cuối cùng bạn sẽ đạt đến một vị trí mà giá trị chính xác phải ở một chỉ số thấp hơn, nhưng nó đã bị tráo đổi. Thật dễ dàng để tìm vị trí mới: chỉ cần thực hiện tính toán chỉ mục một lần nữa để khám phá nơi mục được hoán đổi. Có thể cần phải theo chuỗi một vài bước cho đến khi bạn tìm thấy một vị trí chưa được khai thác.

Tại thời điểm này, chúng tôi đã hợp nhất một nửa mảng và duy trì thứ tự của các phần không được trộn trong nửa còn lại, với các N/2 + N/4giao dịch hoán đổi chính xác . Chúng ta có thể tiếp tục thông qua phần còn lại của mảng cho tổng số N + N/4 + N/8 + ....giao dịch hoán đổi ít hơn 3N/2.

Cách tính A025480:
Điều này được định nghĩa trong OEIS là a(2n) = n, a(2n+1) = a(n).Công thức thay thế là a(n) = isEven(n)? n/2 : a((n-1)/2). Điều này dẫn đến một thuật toán đơn giản sử dụng các thao tác bitwise:

index_t a025480(index_t n){
    while (n&1) n=n>>1;
    return n>>1;  
}

Đây là một hoạt động O (1) được khấu hao trên tất cả các giá trị có thể có cho N. (1/2 cần 1 ca, 1/4 cần 2, 1/8 cần 3, ...) . Có một phương pháp thậm chí còn nhanh hơn sử dụng bảng tra cứu nhỏ để tìm vị trí của bit 0 có ý nghĩa nhỏ nhất.

Cho rằng, đây là một triển khai trong C:

static inline index_t larger_half(index_t sz) {return sz - (sz / 2); }
static inline bool is_even(index_t i) { return ((i & 1) ^ 1); }

index_t unshuffle_item(index_t j, index_t sz)
{
  index_t i = j;
  do {
    i = a025480(sz / 2 + i);
  }
  while (i < j);
  return i;
}

void interleave(value_t a[], index_t n_items)
{
  index_t i = 0;
  index_t midpt = larger_half(n_items);
  while (i < n_items - 1) {

    //for out-shuffle, the left item is at an even index
    if (is_even(i)) { i++; }
    index_t base = i;

    //emplace left half.
    for (; i < midpt; i++) {
      index_t j = a025480(i - base);
      SWAP(a + i, a + midpt + j);
    }

    //unscramble swapped items
    index_t swap_ct  = larger_half(i - base);
    for (index_t j = 0; j + 1 < swap_ct ; j++) {
      index_t k = unshuffle_item(j, i - base);
      if (j != k) {
        SWAP(a + midpt + j, a + midpt + k);
      }
    }
    midpt += swap_ct;
  }
}

Đây phải là một thuật toán khá thân thiện với bộ đệm, vì 2 trong số 3 vị trí dữ liệu được truy cập tuần tự và lượng dữ liệu đang được xử lý đang giảm nghiêm ngặt. Phương pháp này có thể được chuyển từ xáo trộn sang xáo trộn bằng cách phủ định is_eventhử nghiệm khi bắt đầu vòng lặp.

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.