Diziyi serpiştirmek için yerinde algoritma


62

Size elemanların bir dizi verilir2n

bir1,bir2,...,birn,b1,b2,...bn

Görev, ortaya çıkan dizinin göründüğü şekilde yerinde bir algoritma kullanarak diziyi serpiştirmektir.

b1,bir1,b2,bir2,...,bn,birn

Yerinde gereksinim orada olmasaydı kolayca yeni bir dizi oluşturabilir ve zaman algoritması veren öğeleri kopyalayabilirdik .Ö(n)

Yerinde bir gereksinim ile bir bölme ve fethetme algoritması, algoritmayı olacak şekilde .θ(nkütükn)

Yani soru şudur:

Bir var yerinde de zaman algoritması,?Ö(n)

(Not: Tekdüze maliyet WORD RAM modelini varsayabilirsiniz, bu nedenle yerinde alan kısıtlaması anlamına gelir).Ö(1)


1
Bu yığın akışı üzerinde ancak kaliteli bir çözüm sunmuyorlar. En çok oy alan cevap: "Bu sorun insanların yaptıkları kadar önemsiz değil. Ödev? LOL. ArXiv konusunda bir çözüm var " Burada özlü bir çözüm bulmak güzel olurdu.
Joe


Yığın Taşması ile ilgili başka bir konu: stackoverflow.com/questions/15996288/…
Nayuki

Yanıtlar:


43

İşte Joe ile bağlantılı makaleden algoritmayı ayrıntılandıran cevap: http://arxiv.org/abs/0805.1598

Öncelikle , böl ve yönet işlevini kullanan bir Θ(nkütükn) algoritmasını düşünelim .

1) Böl ve Yönet

Biz verilir

bir1,bir2,...,b1,b2,...bn

Şimdi böl ve kullan fethetmek için, bazı m=Θ(n) için diziyi elde etmeye çalışıyoruz

[bir1,bir2,...,birm,b1,b2,...,bm],[birm+1,...,birn,bm+1,...bn]

ve tekrarla.

Kısım dikkat edin

b1,b2,...bm,birm+1,...birn
bir çevrimsel kaydırma olan

birm+1,...birn,b1,...bm

m yerler tarafından .

Bu bir klasiktir ve yerinde üç tersine ve Ö(n) zamanında yapılabilir.

Böylelikle bölme ve fethetme, size T ( n ) = 2 T ( n / 2 ) + Θ ( n ) 'e benzer bir özyinelemeyle bir Θ(nkütükn) algoritması verir .T(n)=2T(n/2)+Θ(n)

2) Permütasyon Çevrimleri

Şimdi, soruna başka bir yaklaşım, permütasyonu bir ayrık döngü seti olarak düşünmektir.

Permütasyon (başlangıç varsayılarak verilmiştir 1 )

j2jşık2n+1

Bir şekilde döngülerin tam olarak ne olduğunu biliyorsak, sabit fazladan boşluk kullanarak, bir element bir seçerek bu elementin nereye gittiğini (yukarıdaki formülü kullanarak) belirleyerek elementi hedef konuma geçici uzaya koyabiliriz. Bu hedef konuma bir elemanı ve döngü boyunca devam edin. Bir döngü bittikten sonra bir sonraki döngünün bir öğesinin üzerine gideriz ve bu döngüyü izleriz.

Bu bize bir Ö(n) zaman algoritması verirdi , ancak bizim “bir şekilde tam döngülerin ne olduğunu bildiğimizi” ve bu kitap tutma işlemini Ö(1) alan sınırlandırması içinde yapmaya çalıştığımızı ve bu problemi zorlaştıran şeyin olduğunu varsayıyor .

Makalenin sayı teorisini kullandığı yer burasıdır.

Bu durumda, bu gösterilebilir zaman 2n+1=3k , pozisyonlarda elemanları 1 , 3,32,...,3k-1 farklı döngülerinde ve her çevrim pozisyonunda bir eleman içerir 3m,m0 .

Bu, 2(Z/3k)* bir jeneratör olduğu gerçeğini kullanır .

Bu nedenle, 2n+1=3k olduğunda, takip döngüsü yaklaşımı bize her döngü için olduğu gibi tam olarak nerede başlayacağımızı biliyoruz: Ö(n) zaman algoritmasını verir : 3 gücü ( 1 dahil ) (bunlar hesaplanabilir) Ö(1) boşluk).

3) Son Algoritma

Şimdi yukarıdaki ikisini birleştiriyoruz: Divide and Conquer + Permütasyon Çevrimleri.

Bir bölme yapmak ve ele ancak çekme m , böylece 2m+1 arasında bir güç 3 ve m=Θ(n) .

Bu yüzden, her iki "yarıya" da tekrarlamak yerine, sadece bir tanesine yineleniyoruz ve Θ(n) fazladan iş yapıyoruz .

Bu bize tekrarlama T(n)=T(cn)+Θ(n) (bazı 0<c<1 ) verir ve böylece bize Ö(n) zamanı, Ö(1) uzay algoritması verir!


4
Bu güzel.
Raphael

1
Çok hoş. Permütasyon örneklerinden geçerken, artık çoğunu anlıyorum. İki soru: 1. Aslında m değerini nasıl buluyorsunuz? Kağıt, O (log n) aldığını iddia ediyor, neden? 2. Benzer bir yaklaşımı kullanarak bir diziyi DE-araya sokmak mümkün mü?
num3ric

2
@ num3ric: 1) < n olan en yüksek gücünü buluyorsunuz . Böylece O ( log n ) olacaktır . 2). Evet, mümkün, bir yerde stackoverflow üzerine bir cevap eklediğime inanıyorum. İnanmak bu durumda devir adamları için olduğu ortaya çıktı 2 bir 3 b (boyunca 2 m + 1 arasında = güç 3 ). 3<nÖ(kütükn)2bir3b2m+13
Aryabhata

@Aryabhata neden iki "yarım" yerine sadece bir "yarı" nı tekrarlıyoruz?
sinoTrinity

1
@Aryabhata Bu algoritma ikiden fazla diziyi birleştirmek için genişletilebilir mi? Örneğin edecek içine c 1 , b 1 , bir 1 , c 2 , b 2 , bir 2 , ... ,bir1,bir2,...,birn,b1,b2,...,bn,c1,c2,...,cn veya benzeri bir şey. c1,b1,bir1,c2,b2,bir2,...,cn,bn,birn
Doub

18

Eminim ki sayı teorisi ya da döngü teorisine dayanmayan bir algoritma buldum. Dikkat etmeniz gereken birkaç ayrıntı olduğunu unutmayın (muhtemelen yarın), ancak işe yarayacaklarına eminim. Uyuyamam gerektiği gibi el sıkıntısı çekiyorum, sorunları gizlemeye çalıştığım için değil :)

Let Ailk dizi olması Bikinci, |A| = |B| = Nve farz N=2^kbazıları için kbasitlik için. Dahil A[i..j], Aile, endeksleri iile alt dizisi olsun j. Diziler 0 tabanlıdır. Sağdan sayarak RightmostBitPos(i), '1' olan en sağdaki bitin (0 tabanlı) konumunu gösterelim i. Algoritma aşağıdaki gibi çalışır.

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

16 sayı dizisini alalım ve bunları takas kullanarak aralarına eklemeye başlayalım ve ne olacağını görelim:

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

Özellikle ilgi çekici olan ikinci dizinin ilk kısmı:

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

Desen açık olmalıdır: dönüşümlü olarak sonuna bir sayı ekler ve en düşük sayıyı yüksek bir sayı ile değiştiririz. Her zaman sahip olduğumuz en yüksek rakamdan daha yüksek bir sayı eklediğimizi unutmayın. Herhangi bir zamanda hangi sayının en düşük olduğunu tam olarak çözebilsek, bunu kolayca yapabiliriz.

Şimdi, bir model görüp göremeyeceğimizi görmek için daha büyük örneklere gidiyoruz. Yukarıdaki örneği oluşturmak için dizinin boyutunu düzeltmemize gerek olmadığını unutmayın. Bir noktada, bu yapılandırmayı elde ederiz (ikinci satır her sayıdan 16 çıkarır):

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

Şimdi bu açıkça bir örüntü gösteriyor: "1 3 5 7 9 11 13 15" hepsi 2 ayrı, "2 6 10 14" 4 ayrı ve "4 12" 8 ayrı. Bu nedenle, bize en küçük sayının ne olacağını söyleyen bir algoritma geliştirebiliriz: mekanizma tam olarak ikili sayıların nasıl çalıştığını gösterir. Dizinin son yarısı için biraz, ikinci çeyrek için biraz, vb.

kütüknkütüknÖ(1)

Ö(n)Ö(n)

Ö(n)Ö(kütükn)Ö(1)

Şimdi, soru şu: sıralamamız gereken kısımda bir kalıp var mı? 32 sayıyı denemek bize "16 12 10 14 9 11 13 15" düzeltmemizi sağlıyor. Burada da aynı kalıbın bulunduğunu unutmayın! "9 11 13 15", "10 14" ve "12" daha önce gördüğümüz şekilde gruplandırılmıştır.

Şimdi, işin püf noktası, bu alt bölümleri yinelemeli olarak bir araya getirmektir. "16" ve "12" 'yi "12 16" ya ekleriz. "12 16" ve "10 14" ila "10 12 14 16" yu serpiştiriyoruz. "10 12 14 16" ve "9 11 13 15" ila "9 10 11 12 13 14 15 16" yı birleştiriyoruz. Bu ilk kısmı sıralar.

Ö(n)Ö(n)

Bir örnek:

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

İlginç. Resmi bir kanıt denemeye ve yazmaya istekli misiniz? Bitlerle ilgilenen başka bir algoritma (Joe'nun bulduğu makalede adı geçen) olduğunu biliyorum. Belki de yeniden keşfettiniz!
Aryabhata

1

İşte bir dizinin iki yarısını fazladan saklamaya gerek kalmadan ayırmak için doğrusal zaman algoritmasında yinelemeli olmayan bir yerinde.

Genel fikir basittir: Dizinin ilk yarısından soldan sağa doğru yürüyün ve doğru değerleri yerine yerleştirin. Siz ilerlerken, henüz kullanılmamış olan sol değerler, doğru değerler tarafından boşaltılan alana yerleştirilir. Tek numara, onları tekrar nasıl çıkaracağımızı bulmak.

N büyüklüğünde 2'ye eşit yarıya bölünmüş bir dizi N ile başlıyoruz.
[ left_items | right_items ]
Biz onu işlerken, olur
[ placed_items | remaining_left_items| swapped_left_items | remaining_right_items]

Takas alanı şu modelle büyür: A) bitişik sağ öğeyi kaldırarak ve soldan yeni bir öğeyle değiştirerek alanı büyütün; B) en eski öğeyi soldan yeni bir öğeyle değiştirin. Soldaki öğeler 1..N ile numaralandırılmışsa, bu desen şuna benzer:

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
...

Dizinin değiştiği dizi, basit bir işlemle hesaplanabilen OEIS A025480'dir . Bu, takas konumunun yalnızca şimdiye kadar eklenen öğelerin sayısı göz önüne alındığında bulunmasını sağlar; bu, yerleştirilen geçerli öğenin de dizinidir.

Dizinin ilk yarısını lineer zamanda doldurmak için ihtiyacımız olan tüm bilgi bu.

Orta noktaya ulaştığımızda, dizinin üç bölümü olacaktır: [ placed_items | swapped_left_items | remaining_right_items] Eğer değiştirilen öğeleri çözebilirsek, sorunu boyutun yarısına indirdik ve tekrarlayabiliriz.

Takas alanını çözmek için şu özelliği kullanıyoruz: NDeğişken ekleme ve takas_oldest işlemleriyle oluşturulan bir dizi N/2, yaşlarının belirtildiği öğeleri içerecektir A025480(N/2)..A025480(N-1). (Tamsayılı bölme, daha küçük değerler eski).

Örneğin, sol yarısı başlangıçta 1..19 değerlerini tutarsa, takas alanı içerir [16, 12, 10, 14, 18, 11, 13, 15, 17, 19]. A025480 (9..18), [2, 5, 1, 6, 3, 7, 0, 8, 4, 9]tam olarak en eskiden en yeniye doğru olan öğelerin indekslerinin listesidir.

Böylece takas alanımızı, içinde ilerleyerek ve takas S[i]ederek çözebiliriz S[ A(N/2 + i)]. Bu aynı zamanda doğrusal zamandır.

Kalan komplikasyon, nihayetinde doğru değerin daha düşük bir endekste olması gereken bir pozisyona ulaşacak olmanızdır, ancak zaten değiştirilmiştir. Yeni konumu bulmak kolaydır: öğenin yerini değiştirdiği yeri bulmak için dizin hesaplamasını tekrar yapın. Kayıp bir yer bulana kadar zinciri birkaç adım izlemeniz gerekebilir.

Bu noktada, dizinin yarısını birleştirdik ve diğer yarısındaki ayrılmamış parçaların sırasını tam olarak N/2 + N/4değiştirerek koruduk . Dizinin geri kalanı boyunca N + N/4 + N/8 + ....kesinlikle daha az olan toplam takas için devam edebiliriz 3N/2.

A025480 nasıl hesaplanır:
Bu, OEIS'tea(2n) = n, a(2n+1) = a(n). alternatif bir formülasyon olarak tanımlanmaktadır a(n) = isEven(n)? n/2 : a((n-1)/2). Bu, bitsel işlemleri kullanarak basit bir algoritmaya yol açar:

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

Bu N için mümkün olan tüm değerler üzerinden amortize edilmiş bir O (1) işlemidir (1/2 ihtiyaç 1 vardiya, 1/4 ihtiyaç 2, 1/8 ihtiyaç 3, ...) . En az anlamlı sıfır bitinin konumunu bulmak için küçük bir arama tablosu kullanan daha hızlı bir yöntem var.

Buna bakıldığında, işte 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;
  }
}

Bu, 3 veri konumundan 2'sine sıralı olarak erişildiği ve işlenen veri miktarının kesinlikle azaldığı için oldukça önbellek dostu bir algoritma olmalıdır. Bu yöntem, bir gelen açılabilir üzerinden shuffle olumsuzlayarak bir in-shuffle'ınıza is_evendöngüsünün başlangıcındaki testi.

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.