İki kuyruk kullanarak bir Stack uygulamanın amacı nedir?


34

Aşağıdaki ev ödevi sorum var:

İki kuyruk kullanarak push (x) ve pop () yöntemlerini uygulayın.

Bu bana tuhaf geliyor çünkü:

  • Bir yığın olan bir çıkar (LIFO) sıra
  • Neden uygulamak için iki kuyruğa ihtiyaç duyduğunuzu anlamıyorum

Etrafı araştırdım:

ve birkaç çözüm buldum. Ben de bununla bitirdim:

public class Stack<T> {
    LinkedList<T> q1 = new LinkedList<T>();
    LinkedList<T> q2 = new LinkedList<T>();

    public void push(T t) {
        q1.addFirst(t);
    }

    public T pop() {
        if (q1.isEmpty()) {
            throw new RuntimeException(
                "Can't pop from an empty stack!");
        }

        while(q1.size() > 1) {
            q2.addFirst( q1.removeLast() );
        }

        T popped = q1.pop();

        LinkedList<T> tempQ = q1;
        q1 = q2;
        q2 = tempQ;

        return popped;
    }
}

Ancak, tek bir sıra kullanmanın avantajının ne olduğunu anlamıyorum; iki sıra sürümü anlamsız bir şekilde karmaşık görünüyor.

Diyelim ki, ittirmenin 2'nin daha verimli olması için (yukarıda yaptığım gibi), pushaynı kalacaktı ve popsadece son öğeye yinelemeyi ve geri döndürmeyi gerektireceğini söyledik. Her iki durumda da, pusholur O(1)ve popolur O(n); ancak tek kuyruk sürümü çok daha basit olurdu. Döngü için sadece bir tek gerekir.

Bir şey mi kaçırıyorum? Buradaki herhangi bir fikir takdir edilecektir.


17
Yığın bir LIFO yapısı iken bir kuyruk tipik olarak bir FIFO yapısına atıfta bulunur. LinkedList'in Java'daki arayüzü, FIFO ve LIFO erişimine izin veren bir deque (çift uçlu sıra) 'dır. LinkedList uygulaması yerine programlamayı Queue arayüzüne değiştirmeyi deneyin .

12
Daha sık karşılaşılan sorun, iki yığın kullanarak bir sıra uygulamaktır. Chris Okasaki'nin kitabını tamamen işlevsel veri yapıları hakkında ilginç bulabilirsiniz.
Eric Lippert

2
Eric’in söylediklerini inşa ederek, bazen kendinizi yığın destekli bir dilde ( dc veya aşağı doğru itme otomatı gibi iki yığınlı (bir turing makinesine eşdeğer, çünkü daha fazlasını yapabilirsiniz)) bulabilirsiniz. çoklu yığınlar, ancak sıra yok.

1
@MichaelT: Ayrıca kendinizi yığın tabanlı bir işlemcide çalışırken bulabilirsiniz
slebetman

11
"Yığın bir (LIFO) kuyruğu" ... uhm, sıra bir bekleme çizgisi. Umumi bir tuvalet kullanmak için hat gibi. Beklediğiniz çizgiler hiç LIFO tarzında davranıyor mu? "LIFO kuyruğu" terimini kullanmayı bırak, saçmalık.
Mehrdad

Yanıtlar:


44

Avantaj yok: Bu tamamen akademik bir alıştırma.

Bir çok ben bir çaylak üniversitedeyken uzun zaman önce ben de benzer bir egzersiz vardı 1 . Amaç, öğrencilere fordöngü sayaçlı döngüleri kullanarak yinelemeli çözümler yazmak yerine algoritmaları uygulamak için nesne yönelimli programlamayı kullanmayı öğretmekti . Bunun yerine, hedeflerinize ulaşmak için mevcut veri yapılarını birleştirin ve yeniden kullanın.

Bu kodu hiçbir zaman Real World TM içinde kullanmayacaksınız . Bu alıştırmadan kurtulmanız gereken şey "kutunun dışında düşünmek" ve kodu yeniden kullanmaktır.


Uygulamayı doğrudan kullanmak yerine, kodunuzda java.util.Queue arabirimini kullanmanız gerektiğini lütfen unutmayın :

Queue<T> q1 = new LinkedList<T>();
Queue<T> q2 = new LinkedList<T>();

Bu, Queueeğer istenirse diğer uygulamaları kullanmanın yanı sıra , arayüzün ruhunu aşabilecek 2 yöntemi gizlemenize izin verir . Bu şunları içerir ve (kodunuz derlenirken, ödevinizin kısıtlamaları verilen orada bir mantık hatası vardır. Değişkenlerinizi bunun yerine açıklamak). İlgili okuma: “Bir arayüze programlama” kavramını anlamak ve arayüzler neden faydalı?LinkedListQueueget(int)pop()QueueLinkedList

1 Hala hatırlıyorum: Alıştırma, Stack arayüzünde sadece yöntemleri kullanarak ve java.util.Collectionsdiğer "sadece statik" yardımcı program sınıflarında veya başka hiçbir "statik" yardımcı programda yöntem kullanılmadan bir Stack tersine çevrilmekti . Doğru çözüm, diğer veri yapılarının geçici tutma nesneleri olarak kullanılmasını içerir: farklı veri yapılarını, özelliklerini ve bunları yapmak için nasıl birleştireceğinizi bilmeniz gerekir. Daha önce hiç program yapmayan CS101 sınıfımın çoğuna çarptım.

2 Yöntemler hala orada, ancak yazım ya da yansıtma olmadan bunlara erişemezsiniz. Dolayısıyla, bu sıra dışı yöntemleri kullanmak kolay değildir .


1
Teşekkürler. Sanırım bu mantıklı. Ayrıca, yukarıdaki kodda (bir FIFO'nun önüne iterek) "yasadışı" işlemler kullandığımı fark ettim, ancak bunun hiçbir şeyi değiştirdiğini sanmıyorum. Tüm işlemleri tersine çevirdim ve hala istenildiği gibi çalışıyor. Başkalarının giriş yapmalarını engellemek istemediğim için kabul etmeden önce biraz bekleyeceğim. Yine de teşekkürler.
Carcigenicate

19

Avantaj yok. Bir Stack uygulamak için Queues kullanmanın korkunç zaman karmaşıklığına yol açtığını doğru anladınız. Hiçbir (yetkin) programcı “gerçek hayatta” böyle bir şey yapmaz.

Ama bu mümkün. Bir soyutlamayı diğerini uygulamak için kullanabilirsiniz, bunun tersi de geçerlidir. Bir yığın iki sıra açısından uygulanabilir ve aynı şekilde iki sıra açısından bir sıra da uygulayabilirsiniz. Bu alıştırmanın avantajı :

  • Yığınlar
  • sıraları yeniden
  • algoritmik düşünceye alışırsın
  • yığınla ilgili algoritmaları öğrenirsiniz
  • Algoritmalardaki değişimler hakkında düşünebilirsin
  • Sıralar ve Yığınların denkliğini fark ederek, kursunuzun çeşitli konularını bağlarsınız.
  • pratik programlama deneyimi kazanırsınız

Aslında, bu harika bir egzersiz. Şu an kendim yapmalıyım :)


3
@JohnKugelman Düzenlemeniz için teşekkürler, ama gerçekten “korkunç zaman karmaşıklığı” demek istedim. Bağlantılı bir listesine dayalı istif için push, peekve popişlemleri, O (1) olarak ifade edilmiştir. Aynı şekilde yeniden boyutlandırılabilir dizi bazlı bir yığın için de geçerlidir, ancak pushO (1) 'de, O (n) en kötü durumuyla itfa edilir. Bununla karşılaştırıldığında, sıraya dayalı bir yığın, O (n) push, O (1) pop ve peek veya alternatif olarak O (1) push, O (n) pop ve peek ile çok düşüktür.
amon

1
"korkunç zaman karmaşıklığı" ve "çok düşük" tam olarak doğru değil. İtfa edilmiş karmaşıklık hala itme ve pop için O (1) . TAOCP'da (vol1?) Bununla ilgili eğlenceli bir soru var (temelde bir elemanın bir yığından diğerine geçme sayısının sabit olduğunu göstermek zorundasınız). Bir işlem için en kötü durum performansı farklıdır, ancak daha sonra genellikle ilginç sayı değil, ArrayLists öğesine basmak için O (N) performansı hakkında konuşan nadiren duyuyorum .
Voo

5

İki yığından bir kuyruk yapmak için kesinlikle gerçek bir amaç var. İşlevsel bir dilden değişmez veri yapıları kullanıyorsanız, itilebilir öğelerin bir yığınına girebilir ve açılan öğeler listesinden çekebilirsiniz. Tüm öğeler çıkarıldığında, poppable öğeler oluşturulur ve yeni poppable stack, yeni pusched yığının şimdi boş olduğu, basılabilir yığının tersidir. Verimli.

İki kuyruktan oluşan bir yığın için mi? Bu, bir sürü büyük ve hızlı kuyruğun mevcut olduğu bir bağlamda anlamlı olabilir. Bu tür bir Java egzersizi olarak kesinlikle işe yaramaz. Ancak bu kanallar veya mesajlaşma sıraları ise mantıklı olabilir. (yani: Öndeki (N-1) öğeleri yeni bir sıraya taşımak için bir O (1) işlemiyle N mesajları sıkıştırıldı.)


Hmm .. bu bana hesaplama için vardiya kayıtlarını kullanmayı ve kemer / değirmen mimarisini kullanmayı
düşündürttü

vay, değirmen cpu gerçekten ilginç. Herhalde bir "Kuyruk Makinesi".
Rob

2

Alıştırma , pratik bir açıdan gereksiz bir şekilde gerçekleştirilir. Amaç, kuyruğun arabirimini, yığını uygulamak için akıllı bir şekilde kullanmaya zorlamak. Örneğin, "Bir sıra" çözümünüz, yığın "pop" işlemi için son giriş değerini almak için sıra üzerinde yineleme yapmanızı gerektirir. Bununla birlikte, bir sıra veri yapısı değerler üzerinde yineleme yapmanıza izin vermez, bunlara ilk giren ilk çıkar (FIFO) içinden erişmeniz kısıtlanır.


2

Diğerlerinin de belirttiği gibi: gerçek dünyanın bir avantajı yok.

Her neyse, sorunuzun ikinci kısmına bir cevap, neden sadece bir sıra kullanmak değil, Java'nın ötesinde.

Java'da bile Queuearayüzün bir size()yöntemi vardır ve bu yöntemin tüm standart uygulamaları O (1) 'dir.

Bu bir C / C ++ programcısı olarak uygulanacak olan naif / kanonik bağlı liste için mutlaka doğru değildir; bu, sadece ilk ve son öğeye işaretçileri ve her öğeyi bir sonraki öğeye işaretçi olarak tutar.

Bu durumda size()O (n) 'dir ve döngülerden kaçınılmalıdır. Veya uygulama opaktır ve sadece minimum add()ve sağlar remove().

Böyle bir uygulamada, ilk olarak öğelerin sayısını ikinci kuyruğa aktararak saymanız, öğeleri ilk kuyruğa n-1geri aktarmanız ve kalan öğeyi geri vermeniz gerekir .

Bununla birlikte, eğer Java ülkesinde yaşıyorsanız, muhtemelen böyle bir şeyi telafi etmeyeceğini söyledi.


Bu size()yöntem hakkında iyi bir nokta . Bununla birlikte, bir O (1) size()yönteminin yokluğunda , yığının mevcut boyutunun kendisini takip etmesi önemsizdir. Tek sıralı bir uygulamayı durduracak hiçbir şey yok.
cmaster

Hepsi uygulamaya bağlıdır. İlk ve son öğeye yalnızca ileriye dönük işaretçilerle ve işaretçilerle uygulanan bir sıranız varsa, yine de bir öğeyi kaldıran, yerel bir değişkene kaydeden, öğeyi daha önce bu değişkende aynı kuyruğa ekleyen öğeyi ekleyen ve algoritma yazdırabilirsiniz. ilk eleman tekrar görülür. Bu, yalnızca aynı değere sahip bir şeyi değil, bir öğeyi benzersiz şekilde tanımlayabiliyorsanız (örn. İşaretçi aracılığıyla) çalışır. O (n) ve sadece add () ve remove () kullanır. Her neyse, algoritmalar hakkında düşünmek dışında, bunu yapmak için bir neden bulmak, optimize etmek daha kolaydır.
Thraidh

0

Böyle bir uygulama için bir kullanım hayal etmek zor, doğru. Ancak asıl mesele bunun yapılabileceğini kanıtlamak .

Ancak bunlar için gerçek kullanımlar açısından iki tane düşünebilirim. Bunun bir kullanımı, sistemleri kendileri için tasarlanmayan kısıtlı ortamlarda uygulamaktır : örneğin, Minecraft'ın redstone blokları , insanların mantık devreleri ve hatta tüm CPU'ları uygulamak için kullandıkları bir Turing-complete sistemini temsil ediyor. Senaryo oyunlarının ilk günlerinde, ilk oyun botlarının çoğu da bu şekilde uygulandı.

Ancak bu ilkeyi tersten uygulayarak, olmasını istemediğiniz bir sistemde bir şeyin mümkün olmadığından emin olabilirsiniz . Bu, güvenlik bağlamlarında ortaya çıkabilir: örneğin, güçlü yapılandırma sistemleri bir varlık olabilir, ancak kullanıcılara vermeyi tercih etmeyeceğiniz hala güç dereceleri vardır. Bu, yapılandırma dilinin yapmasına izin verebileceğiniz şeyi bir saldırgan tarafından altüst edilmek yerine kısıtlar, ancak bu durumda istediğiniz budur.

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.