Üç yığın içeren bir kuyruk nasıl uygulanır?


136

Bu soruya bir algoritma kitabında rastladım ( Algorithms, 4. Baskı , Robert Sedgewick ve Kevin Wayne).

Üç yığın ile kuyruk. Her kuyruk işleminin sabit (en kötü durum) sayıda yığın işlemi almasını sağlamak için üç yığın içeren bir kuyruk uygulayın. Uyarı: yüksek zorluk derecesi.

Nasıl 2 yığın ile bir kuyruk yapmak biliyorum ama 3 yığın ile çözüm bulamıyorum. Herhangi bir fikir ?

(oh ve bu ödev değil :))


30
Sanırım bu bir Hanoi Kulesi varyantı.
Gumbo

14
@Jason: Bu soru yinelenmiyor, çünkü her işlem için O (1) amortisman süresi istenirken bu soru O (1) en kötü durumunu soruyor. DuoSRX'in iki yığınlı çözümü zaten operasyon başına O (1) itfa edilmiş zamandır.
interjay

15
Yazar "Uyarı: yüksek derecede zorluk" dediğinde şaka yapmıyordu.
BoltClock

9
@ Gumbo ne yazık ki Hanoi Kulesi'nin zaman karmaşıklığı hiçbir zaman sabit zamana yakın değil!
prusswan

12
Not: Metindeki soru şu şekilde güncellendi: Her kuyruk işleminin sabit (en kötü durum) sayıda yığın işlemi almasını sağlamak için sabit sayıda ( "3" değil) yığın içeren bir kuyruk uygulayın . Uyarı: yüksek zorluk derecesi. ( algs4.cs.princeton.edu/13stacks - Bölüm 1.3.43). Görünüşe göre Prof. Sedgewick asıl zorluğu kabul etti.
Mark Peters

Yanıtlar:


44

ÖZET

  • 6 yığın için O (1) algoritması bilinmektedir
  • O (1) algoritması 3 yığın için bilinir, ancak pratikte ekstra dahili veri yapılarına sahip olmakla karşılık gelen tembel değerlendirme kullanarak, bir çözüm oluşturmaz
  • Sedgewick yakınlarındaki insanlar, orijinal sorunun tüm kısıtlamaları içinde 3 katlı bir çözümün farkında olmadıklarını doğruladılar

DETAYLAR

Bu bağlantının arkasında iki uygulama vardır: http://www.eecs.usma.edu/webs/people/okasaki/jfp95/index.html

Bunlardan biri üç yığınlı O (1) AMA pratikte ekstra ara veri yapıları (kapaklar) oluşturan tembel yürütme kullanır.

Bir diğeri O (1) ama SIX yığınlarını kullanıyor. Ancak, tembel yürütme olmadan çalışır.

GÜNCELLEME: Okasaki'nin makalesi burada: http://www.eecs.usma.edu/webs/people/okasaki/jfp95.ps ve tembel değerlendirmeye sahip O (1) sürümü için aslında sadece 2 yığın kullanıyor gibi görünüyor. Sorun şu ki, gerçekten tembel değerlendirmeye dayanıyor. Soru, tembel değerlendirme olmadan 3 yığın algoritmaya çevrilip çevrilemeyeceğidir.

GÜNCELLEME: İlgili diğer bir algoritma, 7. Yıllık Bilgi İşlem ve Kombinatorik Konferansı'nda yayınlanan Holger Petersen tarafından yayınlanan "Stacks vs. Deques" makalesinde açıklanmıştır. Makaleyi Google Kitaplar'da bulabilirsiniz. 225-226 sayfalarını kontrol edin. Ancak algoritma aslında gerçek zamanlı simülasyon değil, üç yığın üzerinde çift uçlu bir kuyruğun doğrusal zamanlı simülasyonudur.

gusbro: "@Leonel'in birkaç gün önce söylediği gibi, bir çözüm bilip bilmediğini veya bir hata olup olmadığını Prof. Sedgewick ile kontrol etmenin adil olacağını düşündüm. Bu yüzden ona yazdım. Sadece bir yanıt aldım (ancak Kendisi ama Princeton'daki bir meslektaşımdan) bu yüzden hepinizle paylaşmak istiyorum.Temel olarak üç yığın kullanan hiçbir algoritma bilmediğini ve diğer kısıtlamaları (tembel değerlendirme kullanmamak gibi) bildiğini söyledi. Buradaki yanıtlara baktığımızı zaten bildiğimiz 6 yığın. Bu yüzden soru hala bir algoritma bulmak için açık (ya da birinin bulunamadığını kanıtlamak). "


Sadece bağlantınızdaki kağıtları ve programları gözden geçirdim. Ancak doğru görürsem yığın kullanmazlar, temel tip olarak listeler kullanırlar. Ve esp. Bu listelerde başlık ve dinlenme ile inşa edilmiştir, bu yüzden temelde benim çözümüme benziyor (ve doğru olmadığını düşünüyorum).
flolo

1
Merhaba, uygulamalar, işaretçiler paylaşılmadığı ve paylaşılmadığı sürece listelerin yığınlara karşılık geldiği işlevsel bir dildedir; altı-yığın versiyonu gerçekten altı "düz" yığın kullanılarak uygulanabilir. İki / üç yığın sürümüyle ilgili sorun, gizli veri yapıları (kapanışları) kullanmasıdır.
Antti Huima

Altı yığın çözümün işaretçileri paylaşmadığından emin misiniz? İçinderotate , benzediğini frontliste hem atanır oldfrontve fve bu daha sonra ayrı ayrı değiştirilir.
interjay

14
Algs4.cs.princeton.edu/13stacks adresindeki kaynak malzeme değiştirildi: 43. Her kuyruk işleminin sabit (en kötü durum) sayıda yığın alması için sabit sayıda [3 değil] yığın içeren bir kuyruk uygulayın operasyonlar. Uyarı: yüksek zorluk derecesi. Meydan okuma başlığı hala "Üç yığın ile kuyruk" diyor :-).
Mark Peters

3
@AnttiHuima Altı katlı bağlantı öldü, bunun bir yerde olup olmadığını biliyor musunuz?
Quentin Pradet

12

Tamam, bu gerçekten zor ve bulabileceğim tek çözüm, Kobayashi Maru testine Kirks çözümünü hatırlıyor (bir şekilde aldattı): Fikir, yığın yığınını kullanmamız (ve bunu bir liste modellemek için kullanmamız) ). Ben en / dequeue ve push ve pop operasyonlarını çağırırım, sonra şunu elde ederiz:

queue.new() : Stack1 = Stack.new(<Stack>);  
              Stack2 = Stack1;  

enqueue(element): Stack3 = Stack.new(<TypeOf(element)>); 
                  Stack3.push(element); 
                  Stack2.push(Stack3);
                  Stack3 = Stack.new(<Stack>);
                  Stack2.push(Stack3);
                  Stack2 = Stack3;                       

dequeue(): Stack3 = Stack1.pop(); 
           Stack1 = Stack1.pop();
           dequeue() = Stack1.pop()
           Stack1 = Stack3;

isEmtpy(): Stack1.isEmpty();

(StackX = StackY içeriği kopyalanması değil, sadece bir referans kopyasıdır. Bu sadece kolay açıklamak için. Ayrıca 3 yığın bir dizi kullanabilirsiniz ve dizin yoluyla erişebilirsiniz, orada sadece dizin değişkeninin değerini değiştirmek istiyorsunuz ). Her şey yığın işleminde O (1) 'dir.

Ve evet, tartışmalı olduğunu biliyorum, çünkü 3'ten fazla yığın örtük var, ama belki başkalarına iyi fikirler veriyor.

EDIT: Açıklama örneği:

 | | | |3| | | |
 | | | |_| | | |
 | | |_____| | |
 | |         | |
 | |   |2|   | |
 | |   |_|   | |
 | |_________| |
 |             |
 |     |1|     |
 |     |_|     |
 |_____________|

Burada Stack1 göstermek için küçük bir ASCII-sanat ile çalıştı.

Her öğe tek bir öğe yığınına sarılır (bu nedenle yalnızca güvenli yığın yığınlarımız vardır).

Önce ilk elemanı (burada eleman 1 ve 2'yi içeren yığın) patlattığımızı görüyorsunuz. Sonra bir sonraki elemanı açın ve 1'i açın. Daha sonra ilk açılan yığının artık yeni Stack1imiz olduğunu söylüyoruz. Biraz daha işlevsel konuşmak gerekirse - bunlar, üst öğenin istendiği 2 öğenin yığınları tarafından uygulanan listelerdir. cdr ve ilk / alt üst elemanın araba . Diğer ikisi yığınlara yardım ediyor.

Bir şekilde başka bir öğe eklemek için iç içe yığınlara derinlemesine dalmanız gerektiğinden, Esp zor bir eklemedir. Bu yüzden Stack2 orada. Stack2 her zaman en içteki yığadır. Ekleme, daha sonra sadece bir öğeyi içeri iter ve ardından yeni bir Stack2'yi iter (ve bu nedenle dequeue işlemimizde Stack2'ye dokunmamıza izin verilmez).


Nasıl çalıştığını açıklamak ister misiniz? Belki 'A', 'B', 'C', 'D' tuşlarına basıp 4 kez patlıyor musunuz?
MAK

1
@Iceman: Yığın 2 doğru değil. Kaybolmazlar, çünkü Stack her zaman Stack1'deki en içteki yığını ifade eder, bu nedenle Stack1'de hala örtülüdürler.
flolo

3
Ben hile :-) katılıyorum. Bu 3 yığın değil, 3 yığın referansı. Ama eğlenceli bir okuma.
Mark Peters

1
Bu akıllıca bir düzen, ama doğru anlarsam, kuyruğa itilen n öğe olduğunda n yığınına ihtiyaç duyacaksınız. Soru tam olarak 3 istif istiyor.
MAK

2
@MAK: Biliyorum, bu yüzden açıkça aldatıldığını belirtti (hatta bir ödül üzerinde itibar harcadım çünkü gerçek çözümü de merak ediyorum). Ama en azından prusswan yorumu cevaplanabilir: Yığın sayısı önemlidir, çünkü benim çözümüm gerçekten de geçerlidir, istediğiniz kadar kullanabilirsiniz.
flolo

4

Bunun yapılamayacağını göstermek için bir kanıt girişiminde bulunacağım.


A, B ve C olmak üzere 3 yığınla simüle edilmiş bir Q sırası olduğunu varsayalım.

İddialar

  • ASRT0: = Ayrıca, Q'nun O (1) 'deki {queue, dequeue} işlemlerini simüle edebileceğini varsayın. Bu, simüle edilecek her kuyruk / dequeue işlemi için belirli bir yığın push / pop dizisi olduğu anlamına gelir.

  • Genel kayıp olmadan, kuyruk işlemlerinin deterministik olduğunu varsayalım.

Q'ya sıralanan öğelerin sıra sıralarına göre 1, 2, ... olarak numaralandırılmasına izin verin, Q'ya sıralanan ilk öğe 1, ikincisi 2 olarak tanımlanır.

Tanımlamak

  • Q(0) := Q'da 0 eleman olduğunda Q'nun durumu (ve böylece A, B ve C'de 0 eleman)
  • Q(1) := 1 kuyruk işleminden sonra Q (ve A, B ve C) durumu Q(0)
  • Q(n) := N kuyruk işleminden sonra Q (ve A, B ve C) durumu Q(0)

Tanımlamak

  • |Q(n)| :=içindeki öğe sayısı Q(n)(bu nedenle|Q(n)| = n )
  • A(n) := Q durumu olduğunda A yığınının durumu Q(n)
  • |A(n)| := içindeki eleman sayısı A(n)

Ve B ve C yığınları için benzer tanımlar.

Önemsiz bir şekilde,

|Q(n)| = |A(n)| + |B(n)| + |C(n)|

---

|Q(n)| açıkçası n.

Bu nedenle, en azından biri |A(n)|, |B(n)|ya da |C(n)|, n ile sonsuz.

WLOG1, A yığını sınırsız ve B ve C yığınlarının bağlı olduğunu varsayalım.

* B_u :=B'nin üst sınırını tanımlayın * C_u :=C * 'nin üst sınırınıK := B_u + C_u + 1

WLOG2, böyle bir n |A(n)| > Kiçin K öğelerini seçin Q(n). Bu elemanlardan 1'inin A(n + x)herkes için olduğunu varsayalım x >= 0, yani kaç kuyruk işlemi yapılırsa yapılsın eleman her zaman A yığınındadır.

  • X := bu eleman

Sonra tanımlayabiliriz

  • Abv(n) :=yığındaki A(n)X sayısının üzerindeki öğe sayısı
  • Blo(n) :=yığındaki A(n)X'in altındaki öğe sayısı

    | A (n) | = Abv (n) + Blo (n)

ASRT1 :=X'i ayıklamak için gereken pop sayısı Q(n)en azAbv(n)

( ASRT0) Ve ( ASRT1) 'den,ASRT2 := Abv(n) öğelerinin sınırlanması gerekir.

Eğer Abv(n)sınırsızsa, X'i atlatmak için 20 dequeues gerekiyorsa Q(n), en azındanAbv(n)/20 pop gerekir. Bu sınırsız. 20 herhangi bir sabit olabilir.

Bu nedenle,

ASRT3 := Blo(n) = |A(n)| - Abv(n)

sınırsız olmalıdır.


WLOG3, altından K öğelerini seçebiliriz A(n)ve bunlardan biri A(n + x)herkes içinx >= 0

X(n) := herhangi bir n için

ASRT4 := Abv(n) >= |A(n)| - K

Bir öğe sıraya girdiğinde Q(n)...

WLOG4, B ve C'nin üst sınırlarına zaten doldurulmuş olduğunu varsayalım. Yukarıdaki öğeler için üst sınıra X(n)ulaşıldığını varsayalım . Ardından, yeni bir eleman A'ya girer.

WLOG5, sonuç olarak yeni öğenin aşağıya girmesi gerektiğini varsayalım X(n).

ASRT5 := Bir öğeyi aşağıya koymak için gereken pop sayısı X(n) >= Abv(X(n))

Gönderen (ASRT4), Abv(n)n'de sınırsızdır.

Bu nedenle, bir öğeyi aşağıya koymak için gereken pop sayısı X(n)sınırsızdır.


Bu ASRT1nedenle, bu nedenle, O(1)3 sıralı bir kuyruğu simüle etmek mümkün değildir .


yani

En az 1 yığın sınırsız olmalıdır.

Bu yığınta kalan bir öğe için, üstündeki öğelerin sayısı sınırlandırılmalıdır, yoksa o öğeyi kaldırmak için ayıklama işlemi sınırsız olacaktır.

Bununla birlikte, üstündeki elemanların sayısı sınırlanırsa, bir sınıra ulaşacaktır. Bir noktada, altına yeni bir eleman girilmelidir.

Eski öğeyi her zaman o yığının en düşük birkaç öğesinden birini seçebildiğimizden, üstünde sınırsız sayıda eleman olabilir (sınırsız yığının sınırsız boyutuna bağlı olarak).

Altına yeni bir eleman girmek için, üzerinde sınırsız sayıda eleman bulunduğundan, yeni elemanı altına koymak için sınırsız sayıda pop gerekir.

Ve böylece çelişki.


5 adet WLOG (Genellik kaybı olmadan) ifadesi bulunmaktadır. Bir anlamda sezgisel olarak doğru oldukları anlaşılabilir (ancak 5 oldukları göz önüne alındığında biraz zaman alabilir). Hiçbir genelliğin kaybolmadığına dair resmi kanıt elde edilebilir, ancak son derece uzundur. Bunlar çıkarıldı.

Bu ihmalin söz konusu WLOG ifadelerini bırakabileceğini itiraf ediyorum. Bir programcının hatalar için paranoyasıyla, isterseniz WLOG ifadelerini doğrulayın.

Üçüncü yığın da önemsizdir. Önemli olan bir dizi bağlı yığın ve bir dizi sınırsız yığın var. Bir örnek için gereken minimum miktar 2 istiftir. Yığın sayısı elbette sonlu olmalıdır.

Son olarak, kanıt olmadığına dair haklıysam, daha kolay bir endüktif kanıt olmalıdır. Muhtemelen her kuyruktan sonra ne olduğuna bağlı olarak (kuyruktaki tüm öğeler kümesi göz önüne alındığında en kötü dequeue durumunu nasıl etkilediğini takip edin).


2
Kanıtın bu varsayımlar için işe yaradığını düşünüyorum - ancak sıranın boş olması için tüm yığınların boş olması gerektiğinden veya yığınların boyutlarının toplamının kuyruğun boyutuna eşit olması gerektiğinden emin değilim.
Mikeb

3
"WLOG1, A yığını sınırsız ve B ve C yığınlarının bağlı olduğunu varsayalım." Bazı yığınların sınırlı olduğunu varsayamazsınız, çünkü bu onları değersiz hale getirecektir (O ​​(1) ek depolama ile aynı olacaktır).
interjay

3
Bazen önemsiz şeyler o kadar önemsiz değildir: | Q | = | A | + | B | + | C | yalnızca doğru, Q'daki her giriş için A, B veya C'ye tam bir tane eklediğinizi varsayarsanız, ancak onun her zaman iki yığına veya hatta üçüne de iki kez bir öğe ekleyen bir algoritma olabilir. Ve bu şekilde çalışırsa, WLOG1'in artık tutmadığı (örneğin C'nin A'nın bir kopyasını hayal edin (herhangi bir anlam ifade etmediği, ancak farklı bir düzende veya başka bir şekilde bir algoritma var ...)
flolo

@flolo ve @mikeb: İkiniz de haklısınız. | S, (n) | | A (n) | + | B (n) | + | C (n) | olarak tanımlanmalıdır. Ve sonra | Q (n) | > = n. Daha sonra, kanıt n ile çalışacak ve | Q (n) | daha büyük, sonuç geçerlidir.
Dingfeng Quek

@interjay: Sınırsız yığınınız olabilir ve sınırsız yığınınız olabilir. Sonra "B_u + C_u + 1" yerine, kanıt yalnızca "1" kullanabilir. Temel olarak, ifade "sınırlı yığınlar + 1'deki üst sınırın toplamını" temsil eder, bu nedenle sınırlı yığınların sayısı önemli olmaz.
Dingfeng Quek

3

Not: Bu, tekli bağlantılı listelerle gerçek zamanlı (sabit zamanlı en kötü durum) kuyrukların işlevsel uygulaması için bir yorum anlamına gelir. İtibar nedeniyle yorum ekleyemem, ancak birisi bunu antti.huima'nın cevabına eklenen bir yoruma değiştirebilirse iyi olur. Sonra tekrar, bir yorum için biraz uzun.

@ antti.huima: Bağlantılı listeler bir yığınla aynı değildir.

  • s1 = (1 2 3 4) --- her biri sağda olanı gösteren ve 1, 2, 3 ve 4 değerlerini tutan 4 düğümlü bağlantılı bir liste

  • s2 = attı (s1) --- s2 şimdi (2 3 4)

Bu noktada, s2, yığın gibi davranan patlatılmış (s1) 'e eşdeğerdir. Ancak, s1 referans için hala kullanılabilir!

  • s3 = attı (attı (s1)) - s3 (3 4)

1'i almak için hala s1'e bakabiliriz, oysa uygun bir yığın uygulamasında, element 1 s1'den gitti!

Ne anlama geliyor?

  • s1, yığının en üstüne yapılan referanstır
  • s2, yığının ikinci öğesine referanstır
  • s3 üçüncü referans ...

Şimdi oluşturulan ek bağlantılı listelerin her biri bir referans / işaretçi olarak hizmet vermektedir! Sonlu sayıda yığın bunu yapamaz.

Makalelerde / kodda gördüğüm kadarıyla, algoritmaların hepsi referansları korumak için bağlantılı listelerin bu özelliğini kullanır.

Düzenleme: Ben sadece onları okumak gibi 2 ve 3 bağlantılı liste algoritmaları bağlantılı listelerin bu özelliğini kullanır, (daha basit görünüyordu). Bu, bunların bağlantılı ya da geçerli olmadıklarını göstermek anlamına gelmez, sadece bağlantılı listelerin mutlaka aynı olmadığını açıklamak içindir. Özgür olduğumda 6 olanı okuyacağım. @Welbog: Düzeltme için teşekkürler.


Tembellik de işaretçi işlevselliğini benzer şekillerde simüle edebilir.


Bağlantılı listeyi kullanmak farklı bir sorunu çözer. Bu strateji, Lisp'de gerçek zamanlı kuyrukları uygulamak için kullanılabilir (veya en azından bağlantılı listelerden her şeyi oluşturmakta ısrar eden Lisps): "Antti.huima'nın bağlantılarıyla bağlantılı olan" Saf Lisp'te Gerçek Zamanlı Kuyruk İşlemleri "bölümüne bakın. Ayrıca O (1) çalışma süresi ve paylaşılan (değiştirilemez) yapılar ile değişmez listeler tasarlamak için de iyi bir yoldur.


1
Antti'nin cevabındaki diğer algoritmalarla konuşamam, ancak altı yığın sabit zamanlı çözüm ( eecs.usma.edu/webs/people/okasaki/jfp95/queue.hm.sml ) listelerin bu özelliğini kullanmaz Java kullanarak java.util.Stacknesneleri kullanarak yeniden uyguladım . Bu özelliğin kullanıldığı tek yer, değişmez yığınların sabit zamanda "çoğaltılmasına" izin veren, temel yığınların değişebilen yapılar oldukları için yapılamadığı (ancak Java'da uygulanabilen) bir optimizasyondur.
Welbog

Hesaplama karmaşıklığını azaltmayan bir optimizasyonsa, sonucu etkilememelidir. Sonunda bir çözüm bulduğum için mutluyum, şimdi doğrulamak için: Ama SML okumayı sevmiyorum. Java kodunuzu paylaşmak ister misiniz? (:
Dingfeng Quek

Ne yazık ki, üç yerine altı yığın kullandığından nihai bir çözüm değil. Ancak, altı
istifin

@Welbog! 6 yığınlı uygulamanızı paylaşabilir misiniz? :) Bunu görmek güzel olurdu :)
Antti Huima

1

İki istifle amortismanlı sabit zamanda yapabilirsiniz:

------------- --------------
            | |
------------- --------------

Eklemek O(1)ve kaldırmak, O(1)almak istediğiniz tarafın boş olmaması ve O(n)aksi takdirde (diğer yığını ikiye böl).

İşin püf noktası, O(n)operasyonun sadece her O(n)seferinde yapılacağını görmektir (örneğin yarıya bölerseniz). Bu nedenle, bir operasyon için ortalama süre O(1)+O(n)/O(n) = O(1).

Bu bir sorun gibi görünse de, dizi tabanlı bir yığınla (en hızlı) zorunlu bir dil kullanıyorsanız, yine de sadece amortismanlı sabit zamanınız olacaktır.


Orijinal soruya gelince: Bir yığının bölünmesi aslında fazladan bir ara yığın gerektirir. Bu yüzden görevinizde üç yığın bulunuyordu.
Thomas Ahle
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.