İki yığın kullanarak bir kuyruk nasıl uygulanır?


394

Varsayalım ki iki yığımız var ve başka geçici değişkenimiz yok.

Yalnızca iki yığını kullanarak kuyruk veri yapısını "oluşturmak" mümkün müdür?

Yanıtlar:


701

2 yığın tutun, diyelim inboxve outbox.

Enqueue :

  • Yeni elemanı üzerine itin inbox

Dequeue :

  • Eğer outboxboş, her elemanın haşhaş yeniden doldurmak inboxve üzerine itmeoutbox

  • Üstteki öğeyi pop ve döndür outbox

Bu yöntemi kullanarak, her bir eleman her bir istifte tam olarak bir kez olacaktır - yani her eleman iki kez itilecek ve iki kez patlayacaktır, böylece itfa edilmiş sabit zamanlı işlemler verilecektir.

İşte Java'da bir uygulama:

public class Queue<E>
{

    private Stack<E> inbox = new Stack<E>();
    private Stack<E> outbox = new Stack<E>();

    public void queue(E item) {
        inbox.push(item);
    }

    public E dequeue() {
        if (outbox.isEmpty()) {
            while (!inbox.isEmpty()) {
               outbox.push(inbox.pop());
            }
        }
        return outbox.pop();
    }

}

13
En kötü zaman karmaşıklığı hala O (n) 'dir. Bunu söylemeye devam ediyorum çünkü umarım dışarıda hiç öğrenci yoktur (bu bir ev ödevi / eğitim sorusu gibi görünür) bunun bir kuyruk uygulamak için kabul edilebilir bir yol olduğunu düşünmez.
Tyler

26
Tek bir pop işlemi için en kötü zamanın O (n) olduğu doğrudur (burada n, kuyruğun mevcut büyüklüğüdür). Bununla birlikte, bir dizi n sıra işlemi için en kötü zaman da O (n) 'dir ve bu da bize amortismanlı sabit zaman verir. Bir kuyruğu bu şekilde uygulamam, ama o kadar da kötü değil.
Dave L.

1
@Tyler Yığınız dizi tabanlıysa, çoğu gibi, tek bir işlem için her zaman O (n) en kötü duruma sahip olursunuz.
Thomas Ahle

2
@Tyler: sgi.com/tech/stl/Deque.html adresini kontrol edin . Deque "elemanlara rastgele erişimi destekler". Dolayısıyla hem deque hem de stack dizi tabanlıdır. Bunun nedeni, size daha iyi bir referans konumu vermesi ve dolayısıyla pratikte daha hızlı olmasıdır.
Thomas Ahle

13
@Newtang a) kuyruk 1,2,3 => Gelen Kutusu [3,2,1] / Giden Kutusu [] . b) sıyrılmak. giden kutusu boş olduğundan yeniden doldurma => Gelen Kutusu [] / Giden Kutusu [1,2,3] . Giden kutusundan çık, 1 => Gelen Kutusu [] / Giden Kutusu [2,3] döndür . c) sıra 4,5 => Gelen Kutusu [5,4] / Giden Kutusu [2,3] . d) sıyrılmak. giden kutusu boş değil, bu nedenle giden kutusundan çık, 2 => Gelen Kutusu [5,4] / Giden Kutusu [3] döndür . Bu daha mantıklı mı?
Dave

226

A - Bir Yığını Ters Çevirme

İki yığının kullanıldığı bir kuyruğun nasıl oluşturulacağını anlamak için bir yığının kristal berraklığında nasıl tersine çevrileceğini anlamalısınız. Yığın nasıl çalıştığını hatırlayın, mutfağınızdaki bulaşık yığınına çok benzer. Son yıkanmış çanak olarak adlandırılan temiz yığını üstüne olacak L ast I , n F IRST O ut çıkar (LIFO) içerisinde bilgisayar.

Yığımızı aşağıdaki gibi bir şişe gibi düşünelim;

resim açıklamasını buraya girin

Sırasıyla 1,2,3 tamsayılarını itersek, 3 yığının üstünde olacaktır. İlk önce 1 itilecek, sonra 2 1'in üstüne konacaktır. Son olarak, 3 yığının üstüne konacak ve bir şişe olarak temsil edilen yığımızın son durumu aşağıdaki gibi olacaktır;

resim açıklamasını buraya girin

Şimdi şişemiz olarak temsil edilen yığınımız 3,2,1 değerlerine sahip. Ve yığını tersine çevirmek istiyoruz, böylece yığının üst elemanı 1 ve yığının alt elemanı 3 olacak. Ne yapabiliriz? Şişeyi alıp baş aşağı tutabiliriz, böylece tüm değerler sırayla tersine dönmelidir?

resim açıklamasını buraya girin

Evet bunu yapabiliriz, ama bu bir şişe. Aynı işlemi yapmak için, ilk yığın öğelerini ters sırada depolayacak ikinci bir yığına sahip olmamız gerekir. Doldurulmuş yığınımızı sola, yeni boş yığınımızı sağa koyalım. Öğelerin sırasını tersine çevirmek için, her öğeyi sol yığından çıkaracağız ve onları sağ yığına iteceğiz. Aşağıdaki resimde yaptığımız gibi neler olduğunu görebilirsiniz;

resim açıklamasını buraya girin

Bir yığını nasıl tersine çevireceğimizi biliyoruz.

B - İki Yığını Kuyruk Olarak Kullanma

Önceki bölümde, yığın elemanlarının sırasını nasıl tersine çevirebileceğimizi açıklamıştım. Bu önemliydi, çünkü öğeleri yığına itip pop yaparsak, çıktı tam olarak bir sıranın tam tersi sırada olur. Bir örnek üzerinde düşünelim, tamsayı dizisini {1, 2, 3, 4, 5}bir yığına itelim. Elemanları patlatır ve yığın boşalana kadar basarsak, diziyi tersine itme sırasına göre {5, 4, 3, 2, 1}alırız. olacak {1, 2, 3, 4, 5}. Dolayısıyla, elemanların aynı girdi sırası için, kuyruğun çıktısının bir yığının çıktısının tam tersi olduğu açıktır. Ek bir yığın kullanarak bir yığını nasıl tersine çevireceğimizi bildiğimiz için, iki yığın kullanarak bir kuyruk oluşturabiliriz.

Kuyruk modelimiz iki desteden oluşacak. enqueueİşlem için bir yığın kullanılacak (soldaki yığın # 1, Giriş Yığını olarak adlandırılacaktır), işlem için başka bir yığın kullanılacak dequeue(sağdaki yığın # 2, Çıkış Yığını olarak adlandırılacaktır). Aşağıdaki resme bakın;

resim açıklamasını buraya girin

Sözde kodumuz aşağıdaki gibidir;


Enqueue Operasyonu

Push every input element to the Input Stack

Dequeue Operasyonu

If ( Output Stack is Empty)
    pop every element in the Input Stack
    and push them to the Output Stack until Input Stack is Empty

pop from Output Stack

{1, 2, 3}Sırasıyla tam sayıları sıralayalım . Tamsayılar solda bulunan Giriş Yığını'na ( Yığın # 1 ) itilir ;

resim açıklamasını buraya girin

Peki bir dequeue operasyonu yaparsak ne olur? Bir dequeue işlemi yürütüldüğünde, kuyruk Çıkış Yığının boş olup olmadığını kontrol edecektir (yukarıdaki sahte koda bakın) Çıkış Yığını boşsa, giriş Yığını çıktıda çıkartılacak Yığın Girişi ters çevrilir. Bir değer döndürülmeden önce, sıranın durumu aşağıdaki gibi olacaktır;

resim açıklamasını buraya girin

Çıktı Yığını'ndaki (Yığın # 2) öğelerin sırasını kontrol edin. Çıktı Yığını'ndaki öğeleri açabileceğimiz açıktır, böylece çıktı bir kuyruktan ayıklanmış gibi aynı olacaktır. Böylece, iki dequeue operasyonu yaparsak, önce {1, 2}sırasıyla alacağız . Daha sonra eleman 3, Çıkış Yığını'nın tek elemanı olacak ve Giriş Yığını boş olacaktır. 4 ve 5 elemanlarını sıralarsak, kuyruğun durumu aşağıdaki gibi olacaktır;

resim açıklamasını buraya girin

Şimdi Çıktı Yığını boş değil ve bir dequeue işlemi gerçekleştirirsek, Çıktı Yığını'ndan sadece 3 tane çıkar. Sonra devlet aşağıdaki gibi görülecektir;

resim açıklamasını buraya girin

Yine, iki dequeue işlemi daha yaparsak, ilk dequeue işleminde, sıra Çıkış Yığını'nın boş olup olmadığını kontrol eder, bu da doğrudur. Sonra Giriş Yığını'nın öğelerini dışarı çıkarın ve Giriş Yığını boş olana kadar bunları Çıkış Yığını'na itin, sonra Kuyruğun durumu aşağıdaki gibi olacaktır;

resim açıklamasını buraya girin

Görmesi kolay, iki dequeue işleminin çıktısı {4, 5}

C - İki Yığınla İnşa Edilen Kuyruğun Uygulanması

İşte Java'da bir uygulama. Mevcut Stack uygulamasını kullanmayacağım, bu yüzden buradaki örnek tekerleği yeniden icat edecek;

C - 1) MyStack sınıfı: Basit Bir Yığın Uygulaması

public class MyStack<T> {

    // inner generic Node class
    private class Node<T> {
        T data;
        Node<T> next;

        public Node(T data) {
            this.data = data;
        }
    }

    private Node<T> head;
    private int size;

    public void push(T e) {
        Node<T> newElem = new Node(e);

        if(head == null) {
            head = newElem;
        } else {
            newElem.next = head;
            head = newElem;     // new elem on the top of the stack
        }

        size++;
    }

    public T pop() {
        if(head == null)
            return null;

        T elem = head.data;
        head = head.next;   // top of the stack is head.next

        size--;

        return elem;
    }

    public int size() {
        return size;
    }

    public boolean isEmpty() {
        return size == 0;
    }

    public void printStack() {
        System.out.print("Stack: ");

        if(size == 0)
            System.out.print("Empty !");
        else
            for(Node<T> temp = head; temp != null; temp = temp.next)
                System.out.printf("%s ", temp.data);

        System.out.printf("\n");
    }
}

C - 2) MyQueue sınıfı: İki Yığın Kullanarak Kuyruk Uygulaması

public class MyQueue<T> {

    private MyStack<T> inputStack;      // for enqueue
    private MyStack<T> outputStack;     // for dequeue
    private int size;

    public MyQueue() {
        inputStack = new MyStack<>();
        outputStack = new MyStack<>();
    }

    public void enqueue(T e) {
        inputStack.push(e);
        size++;
    }

    public T dequeue() {
        // fill out all the Input if output stack is empty
        if(outputStack.isEmpty())
            while(!inputStack.isEmpty())
                outputStack.push(inputStack.pop());

        T temp = null;
        if(!outputStack.isEmpty()) {
            temp = outputStack.pop();
            size--;
        }

        return temp;
    }

    public int size() {
        return size;
    }

    public boolean isEmpty() {
        return size == 0;
    }

}

C - 3) Demo Kodu

public class TestMyQueue {

    public static void main(String[] args) {
        MyQueue<Integer> queue = new MyQueue<>();

        // enqueue integers 1..3
        for(int i = 1; i <= 3; i++)
            queue.enqueue(i);

        // execute 2 dequeue operations 
        for(int i = 0; i < 2; i++)
            System.out.println("Dequeued: " + queue.dequeue());

        // enqueue integers 4..5
        for(int i = 4; i <= 5; i++)
            queue.enqueue(i);

        // dequeue the rest
        while(!queue.isEmpty())
            System.out.println("Dequeued: " + queue.dequeue());
    }

}

C - 4) Numune Çıkışı

Dequeued: 1
Dequeued: 2
Dequeued: 3
Dequeued: 4
Dequeued: 5

18
Eğer yapabilseydim bunu bütün gün + 1'leyebilirdim. Sürekli zamana nasıl amortisman yapıldığını anlayamadım. Resimleriniz, özellikle çıktı yığınında kalan öğeleri bırakma ve yalnızca boşaltıldığında yeniden doldurma bölümü olan şeyleri gerçekten temizledi.
Shane McQuillan

1
Bu gerçekten pop sırasında aldığım zaman aşımı hatalarını önlemeye yardımcı oldu. Elemanları orijinal yığınına geri koyuyordum ama buna gerek yoktu. Kudos!
Pranit Bankar

2
Tüm yorumlar bundan sonra modellenmelidir.
lolololol ol

4
Bunun için gerçekten bir çözüme ihtiyacım yoktu, sadece göz atıyorum ... Ama böyle bir cevap gördüğümde sadece aşık oldum .. Harika cevap !!!
Maverick

80

Bir kuyruğu yalnızca bir yığın kullanarak bile simüle edebilirsiniz. İkinci (geçici) yığın, insert yöntemine yapılan yinelemeli çağrıların çağrı yığını tarafından simüle edilebilir.

Kuyruğa yeni bir öğe eklenirken ilke aynı kalır:

  • Sıralamalarını tersine çevirmek için öğeleri bir yığından başka bir geçici yığına aktarmanız gerekir.
  • Ardından eklenecek yeni öğeyi geçici yığının üzerine itin
  • Ardından öğeleri orijinal yığınına geri aktarın
  • Yeni öğe yığının altında olacak ve en eski öğe üstte olacak (ilk önce patlatılacak)

Yalnızca bir Yığın kullanan bir Queue sınıfı aşağıdaki gibi olacaktır:

public class SimulatedQueue<E> {
    private java.util.Stack<E> stack = new java.util.Stack<E>();

    public void insert(E elem) {
        if (!stack.empty()) {
            E topElem = stack.pop();
            insert(elem);
            stack.push(topElem);
        }
        else
            stack.push(elem);
    }

    public E remove() {
        return stack.pop();
    }
}

51
Belki kod zarif görünüyor ama çok verimsiz (O (n ** 2) insert) ve @pythonquick'in işaret ettiği gibi, biri yığın ve diğeri çağrı yığınında olmak üzere iki yığın var. Özyinelemesiz bir algoritma için, özyinelemeyi destekleyen dillerde her zaman çağrı yığınından bir "ekstra" yığın alabilirsiniz.
Antti Huima

1
@ antti.huima Ve bunun nasıl ikinci dereceden bir ek olabileceğini açıklar mısınız ?! Anladığım kadarıyla, insert n pop ve n push işlemleri yapıyor, bu yüzden mükemmel doğrusal bir O (n) algoritması.
LP_

1
@LP_ n itemsYukarıdaki veri yapısını kullanarak sıraya eklemek için karesel süre O (n ^ 2) gerekir . Toplam (1 + 2 + 4 + 8 + .... + 2(n-1))sonuç ~O(n^2). Umarım anlarsın.
Ankit Kumar

1
@ antti.huima Ekleme işlevinin karmaşıklığından bahsediyordunuz ("O (n 2) ek" dediniz - muhtemelen "O (n 2) dolgu" demek istediniz ). Kural olarak , "karmaşıklık eki", bir eklemenin aldığı zamandır , burada zaten mevcut olan elemanların sayısı doğrusaldır. N öğe eklemek için gereken sürede konuşursak, bir hashtable'ın lineer insert olduğunu söyleyebiliriz. Bu durum böyle değil.
LP_

2
Temel olarak yığını bir yığın olarak kullanıyorsunuz. Bu, yığınta çok sayıda öğe varsa, bir yığın taşmasıyla sonuçlanabileceğiniz anlamına gelir - neredeyse çözüm bu site için tasarlanmış gibi!
UKMonkey

11

Zaman karmaşıklıkları daha kötü olurdu. İyi bir kuyruk uygulaması her şeyi sabit zamanda yapar.

Düzenle

Cevabımın neden burada reddedildiğinden emin değilim. Program yaparsak, zaman karmaşıklığına önem veririz ve bir kuyruğu yapmak için iki standart yığın kullanmak verimsiz olur. Bu çok geçerli ve ilgili bir nokta. Eğer başka biri bunu daha aşağıya düşürme ihtiyacını hissederse, nedenini bilmek isterim.

Biraz daha ayrıntılı : iki yığının neden sadece bir sıradan daha kötü olduğuna dair: iki yığın kullanırsanız ve giden kutusu boşken birisi dequeue çağırırsa, gelen kutusunun altına ulaşmak için doğrusal zamana ihtiyacınız vardır (gördüğünüz gibi) Dave kodunda).

Bir kuyruğu tekli bağlantılı liste olarak uygulayabilirsiniz (her öğe sonraki eklenen öğeye işaret eder), itmeler için son eklenen öğeye fazladan bir işaretçi tutar (veya döngüsel bir liste haline getirir). Bu veri yapısı üzerinde kuyruk ve dequeue uygulamak, sabit zamanda yapmak çok kolaydır. Bu en kötü durum sabit zaman, itfa edilmez. Ve yorumlar bu açıklamayı istiyor gibi göründüğü gibi, en kötü durum sabit zamanı, itfa edilmiş sabit zamandan kesinlikle daha iyidir.


Ortalama durumda değil. Brian'ın cevabı, sabit enqueue ve dequeue operasyonlarını amorti edecek bir kuyruğu açıklıyor .
Daniel Spiewak

Bu doğru. Ortalama dava ve itfa edilmiş zaman karmaşıklığınız aynıdır. Ancak, varsayılan değer genellikle işlem başına en kötü durumdur ve bu, n (yapının mevcut boyutu) O (n) 'dir.
Tyler

1
En kötü durum da itfa edilebilir. Örneğin, değişebilir dinamik dizilerin (vektörlerin) her zaman sıklıkla pahalı bir yeniden boyutlandırma ve kopyalama işlemi gerekli olsa da, genellikle sabit ekleme süresine sahip olduğu düşünülür.
Daniel Spiewak

1
"En kötü durum" ve "itfa edilmiş" iki farklı tür zaman karmaşıklığıdır. "En kötü durum amortismana tabi tutulabilir" demek mantıklı değildir - en kötü durum = amortismana tabi tutulabilirseniz, bu önemli bir gelişme olacaktır; Ortalama olmadan, en kötü durumdan bahsederdiniz.
Tyler

O (1) en kötü durumun O (1) ortalama durum ve O (n) en kötü durumun birleşiminden "kesinlikle daha iyi" olması ile ne demek istediğinizden emin değilim. Sabit ölçeklendirme faktörleri önemlidir. N öğe içeriyorsa, N mikrosaniye zamanında N işleminden sonra yeniden paketlenmesi gerekebilecek ve aksi takdirde işlem başına bir mikrosaniye alan bir veri yapısı, her işlem için bir milisaniye alan bir sistemden çok daha yararlı olabilir, hatta veri boyutu milyonlarca öğeye genişleyecekse (bu nedenle bazı bireysel işlemlerin birkaç saniye alacağını ima eder).
supercat

8

Uygulanacak kuyruğun q ve q uygulamak için yığınlar stack1 ve stack2 olsun.

q iki şekilde uygulanabilir :

Yöntem 1 (enQueue işlemini maliyetli yaparak)

Bu yöntem, yeni girilen öğenin her zaman yığın 1'in üstünde olmasını sağlar, böylece deQueue işlemi yığın1'den çıkar. Elemanı stack1'in üstüne koymak için stack2 kullanılır.

enQueue(q, x)
1) While stack1 is not empty, push everything from stack1 to stack2.
2) Push x to stack1 (assuming size of stacks is unlimited).
3) Push everything back to stack1.
deQueue(q)
1) If stack1 is empty then error
2) Pop an item from stack1 and return it.

Yöntem 2 (deQueue işlemini maliyetli yaparak)

Bu yöntemde, en-queue işleminde yeni eleman stack1'in üstüne girilir. Kuyruktan çıkarma işleminde, yığın2 boşsa, tüm öğeler yığın2'ye taşınır ve son olarak yığın2'nin üst kısmı döndürülür.

enQueue(q,  x)
 1) Push x to stack1 (assuming size of stacks is unlimited).

deQueue(q)
 1) If both stacks are empty then error.
 2) If stack2 is empty
   While stack1 is not empty, push everything from stack1 to stack2.
 3) Pop the element from stack2 and return it.

Yöntem 2, yöntem 1'den kesinlikle daha iyidir. Yöntem 1, tüm öğeleri enQueue işleminde iki kez hareket ettirirken, yöntem 2 (deQueue işleminde) öğeleri bir kez taşır ve öğeleri yalnızca stack2 boşsa taşır.


Metodunuz haricinde anladığım çözümlerin hiçbiri 2. Adımlarla enqueue ve dequeue metoduyla açıklama şeklinizi seviyorum.
theGreenCabbage


3

C # içinde bir çözüm

public class Queue<T> where T : class
{
    private Stack<T> input = new Stack<T>();
    private Stack<T> output = new Stack<T>();
    public void Enqueue(T t)
    {
        input.Push(t);
    }

    public T Dequeue()
    {
        if (output.Count == 0)
        {
            while (input.Count != 0)
            {
                output.Push(input.Pop());
            }
        }

        return output.Pop();
    }
}

2

Kuyruktaki iki yığın yığın1 ve yığın2 olarak tanımlanır .

Enqueue: Euquued öğeleri her zaman yığın içine itilir1

Dequeue: Stack2 boş olmadığında sıraya eklenen ilk eleman olduğu için stack2'nin üst kısmı atlanabilir . Ne zaman stack2 boştur, biz gelen tüm unsurları pop stack1 ve içine itmek stack2 birer birer. Bir kuyruktaki ilk öğe yığının1 altına itilir . Yığının üstünde olduğu için, haşhaş ve itme işlemlerinden sonra doğrudan dışarı atılabilir2 .

Aşağıdaki aynı C ++ örnek kodu:

template <typename T> class CQueue
{
public:
    CQueue(void);
    ~CQueue(void);

    void appendTail(const T& node); 
    T deleteHead();                 

private:
    stack<T> stack1;
    stack<T> stack2;
};

template<typename T> void CQueue<T>::appendTail(const T& element) {
    stack1.push(element);
} 

template<typename T> T CQueue<T>::deleteHead() {
    if(stack2.size()<= 0) {
        while(stack1.size()>0) {
            T& data = stack1.top();
            stack1.pop();
            stack2.push(data);
        }
    }


    if(stack2.size() == 0)
        throw new exception("queue is empty");


    T head = stack2.top();
    stack2.pop();


    return head;
}

Bu çözüm ödünç bloguma . Adım adım işlem simülasyonları ile daha ayrıntılı analiz blog web sayfamda mevcuttur.


2

Alt öğeyi elde etmek için her şeyi ilk yığından çıkarmanız gerekir. Sonra her "dequeue" işlemi için hepsini tekrar ikinci istifin üzerine koyun.


3
Evet haklısın. Acaba, nasıl bu kadar çok aşağı oy aldınız. Cevabınızı iptal ettim
Binita Bharati

Bunun son cevabı olduğunu görmek ürkütücü ve o zamandan bu yana on yıl geçti.
Shanu Gupta

2

c # geliştirici için tam program:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace QueueImplimentationUsingStack
{
    class Program
    {
        public class Stack<T>
        {
            public int size;
            public Node<T> head;
            public void Push(T data)
            {
                Node<T> node = new Node<T>();
                node.data = data;
                if (head == null)
                    head = node;
                else
                {
                    node.link = head;
                    head = node;
                }
                size++;
                Display();
            }
            public Node<T> Pop()
            {
                if (head == null)
                    return null;
                else
                {
                    Node<T> temp = head;
                    //temp.link = null;
                    head = head.link;
                    size--;
                    Display();
                    return temp;
                }
            }
            public void Display()
            {
                if (size == 0)
                    Console.WriteLine("Empty");
                else
                {
                    Console.Clear();
                    Node<T> temp = head;
                    while (temp!= null)
                    {
                        Console.WriteLine(temp.data);
                        temp = temp.link;
                    }
                }
            }
        }

        public class Queue<T>
        {
            public int size;
            public Stack<T> inbox;
            public Stack<T> outbox;
            public Queue()
            {
                inbox = new Stack<T>();
                outbox = new Stack<T>();
            }
            public void EnQueue(T data)
            {
                inbox.Push(data);
                size++;
            }
            public Node<T> DeQueue()
            {
                if (outbox.size == 0)
                {
                    while (inbox.size != 0)
                    {
                        outbox.Push(inbox.Pop().data);
                    }
                }
                Node<T> temp = new Node<T>();
                if (outbox.size != 0)
                {
                    temp = outbox.Pop();
                    size--;
                }
                return temp;
            }

        }
        public class Node<T>
        {
            public T data;
            public Node<T> link;
        }

        static void Main(string[] args)
        {
            Queue<int> q = new Queue<int>();
            for (int i = 1; i <= 3; i++)
                q.EnQueue(i);
           // q.Display();
            for (int i = 1; i < 3; i++)
                q.DeQueue();
            //q.Display();
            Console.ReadKey();
        }
    }
}

2

Yığınları kullanarak bir sıranın aşağıdaki işlemlerini gerçekleştirin.

push (x) - X elemanını kuyruğun arkasına iter.

pop () - Öğeyi kuyruğun önünden kaldırır.

peek () - Ön elemanı al.

empty () - Sıranın boş olup olmadığını döndürür.

resim açıklamasını buraya girin

class MyQueue {

  Stack<Integer> input;
  Stack<Integer> output;

  /** Initialize your data structure here. */
  public MyQueue() {
    input = new Stack<Integer>();
    output = new Stack<Integer>();
  }

  /** Push element x to the back of queue. */
  public void push(int x) {
    input.push(x);
  }

  /** Removes the element from in front of queue and returns that element. */
  public int pop() {
    peek();
    return output.pop();
  }

  /** Get the front element. */
  public int peek() {
    if(output.isEmpty()) {
        while(!input.isEmpty()) {
            output.push(input.pop());
        }
    }
    return output.peek();
  }

  /** Returns whether the queue is empty. */
  public boolean empty() {
    return input.isEmpty() && output.isEmpty();
  }
}

1
// Two stacks s1 Original and s2 as Temp one
    private Stack<Integer> s1 = new Stack<Integer>();
    private Stack<Integer> s2 = new Stack<Integer>();

    /*
     * Here we insert the data into the stack and if data all ready exist on
     * stack than we copy the entire stack s1 to s2 recursively and push the new
     * element data onto s1 and than again recursively call the s2 to pop on s1.
     * 
     * Note here we can use either way ie We can keep pushing on s1 and than
     * while popping we can remove the first element from s2 by copying
     * recursively the data and removing the first index element.
     */
    public void insert( int data )
    {
        if( s1.size() == 0 )
        {
            s1.push( data );
        }
        else
        {
            while( !s1.isEmpty() )
            {
                s2.push( s1.pop() );
            }
            s1.push( data );
            while( !s2.isEmpty() )
            {
                s1.push( s2.pop() );
            }
        }
    }

    public void remove()
    {
        if( s1.isEmpty() )
        {
            System.out.println( "Empty" );
        }
        else
        {
            s1.pop();

        }
    }

1

Swift'te iki yığın kullanan bir kuyruk uygulaması:

struct Stack<Element> {
    var items = [Element]()

    var count : Int {
        return items.count
    }

    mutating func push(_ item: Element) {
        items.append(item)
    }

    mutating func pop() -> Element? {
        return items.removeLast()
    }

    func peek() -> Element? {
        return items.last
    }
}

struct Queue<Element> {
    var inStack = Stack<Element>()
    var outStack = Stack<Element>()

    mutating func enqueue(_ item: Element) {
        inStack.push(item)
    }

    mutating func dequeue() -> Element? {
        fillOutStack() 
        return outStack.pop()
    }

    mutating func peek() -> Element? {
        fillOutStack()
        return outStack.peek()
    }

    private mutating func fillOutStack() {
        if outStack.count == 0 {
            while inStack.count != 0 {
                outStack.push(inStack.pop()!)
            }
        }
    }
}

1

İki yığına sahip bir kuyruk uygulamakla ilgili çok sayıda ileti alırken: 1. Ya enQueue işlemini çok daha maliyetli hale getirerek 2. Veya deQueue işlemini çok daha maliyetli hale getirerek

https://www.geeksforgeeks.org/queue-using-stacks/

Yukarıdaki yazıdan öğrendiğim önemli bir yol, sadece yığın veri yapısı ve özyineleme çağrısı yığını ile kuyruk oluşturmaktı.

Biri tam anlamıyla bunun hala iki yığın kullandığını iddia edebilir, ancak ideal olarak bu sadece bir yığın veri yapısı kullanıyor.

Sorunun açıklaması aşağıdadır:

  1. Verileri enQueuing ve deQueing için tek bir yığın bildirin ve verileri yığının içine itin.

  2. deQueueing, yığının boyutu 1 olduğunda yığının öğesinin açıldığı bir temel koşula sahipken, deQueue özyineleme sırasında yığın taşmasının olmamasını sağlar.

  3. DeQueueing ilk önce verileri yığının üstünden çıkar. İdeal olarak bu eleman, yığının üstünde bulunan eleman olacaktır. Şimdi bu yapıldıktan sonra, deQueue işlevini özyinelemeli olarak çağırın ve sonra üste atlanan öğeyi yığına geri itin.

Kod aşağıdaki gibi görünecektir:

if (s1.isEmpty())
System.out.println("The Queue is empty");
        else if (s1.size() == 1)
            return s1.pop();
        else {
            int x = s1.pop();
            int result = deQueue();
            s1.push(x);
            return result;

Bu şekilde, tek bir yığın veri yapısı ve özyineleme çağrısı yığını kullanarak bir kuyruk oluşturabilirsiniz.


1

Aşağıda ES6 sözdizimini kullanan javascript dilinde çözüm bulunmaktadır.

Stack.js

//stack using array
class Stack {
  constructor() {
    this.data = [];
  }

  push(data) {
    this.data.push(data);
  }

  pop() {
    return this.data.pop();
  }

  peek() {
    return this.data[this.data.length - 1];
  }

  size(){
    return this.data.length;
  }
}

export { Stack };

QueueUsingTwoStacks.js

import { Stack } from "./Stack";

class QueueUsingTwoStacks {
  constructor() {
    this.stack1 = new Stack();
    this.stack2 = new Stack();
  }

  enqueue(data) {
    this.stack1.push(data);
  }

  dequeue() {
    //if both stacks are empty, return undefined
    if (this.stack1.size() === 0 && this.stack2.size() === 0)
      return undefined;

    //if stack2 is empty, pop all elements from stack1 to stack2 till stack1 is empty
    if (this.stack2.size() === 0) {
      while (this.stack1.size() !== 0) {
        this.stack2.push(this.stack1.pop());
      }
    }

    //pop and return the element from stack 2
    return this.stack2.pop();
  }
}

export { QueueUsingTwoStacks };

Aşağıda kullanımı:

index.js

import { StackUsingTwoQueues } from './StackUsingTwoQueues';

let que = new QueueUsingTwoStacks();
que.enqueue("A");
que.enqueue("B");
que.enqueue("C");

console.log(que.dequeue());  //output: "A"

Bu hata. Ayrıştırdıktan sonra daha fazla öğe sıralarsanız, onları yerleştirirsiniz stack1. dequeueTekrar gittiğinizde stack2, onları önceden mevcut olanın önüne koyarak öğeleri içine taşıyacaksınız .
Alexander - Monica'yı eski

0

Go'daki bu soruyu cevaplayacağım çünkü Go'nun standart kütüphanesinde çok fazla koleksiyon yok.

Bir yığının uygulanması gerçekten kolay olduğundan, çift uçlu bir kuyruğu gerçekleştirmek için iki yığını kullanmaya çalışacağımı düşündüm. Cevabıma nasıl geldiğimi daha iyi anlamak için uygulamayı iki parçaya ayırdım, ilk kısmı anlamak umarım daha kolay ama eksik.

type IntQueue struct {
    front       []int
    back        []int
}

func (q *IntQueue) PushFront(v int) {
    q.front = append(q.front, v)
}

func (q *IntQueue) Front() int {
    if len(q.front) > 0 {
        return q.front[len(q.front)-1]
    } else {
        return q.back[0]
    }
}

func (q *IntQueue) PopFront() {
    if len(q.front) > 0 {
        q.front = q.front[:len(q.front)-1]
    } else {
        q.back = q.back[1:]
    }
}

func (q *IntQueue) PushBack(v int) {
    q.back = append(q.back, v)
}

func (q *IntQueue) Back() int {
    if len(q.back) > 0 {
        return q.back[len(q.back)-1]
    } else {
        return q.front[0]
    }
}

func (q *IntQueue) PopBack() {
    if len(q.back) > 0 {
        q.back = q.back[:len(q.back)-1]
    } else {
        q.front = q.front[1:]
    }
}

Temel olarak, yığınların tabanının birbirleri tarafından manipüle edilmesine izin verdiğimiz iki yığın. Ayrıca, bir yığının geleneksel push, pop, peek işlemlerinin sıranın önüne veya arkasına atıfta bulunmalarına rağmen bir ön / arka önekinin bulunduğu STL adlandırma kurallarını kullandım.

Yukarıdaki kodla ilgili sorun, belleği çok verimli kullanmamasıdır. Aslında, siz alanınız bitene kadar sürekli büyür. Bu gerçekten kötü. Bunun düzeltmesi, yığın alanının altını mümkün olduğunda yeniden kullanmaktır. Go'daki bir dilim küçüldükten sonra öne doğru büyüyemediğinden, bunu izlemek için bir ofset tanıtmamız gerekiyor.

type IntQueue struct {
    front       []int
    frontOffset int
    back        []int
    backOffset  int
}

func (q *IntQueue) PushFront(v int) {
    if q.backOffset > 0 {
        i := q.backOffset - 1
        q.back[i] = v
        q.backOffset = i
    } else {
        q.front = append(q.front, v)
    }
}

func (q *IntQueue) Front() int {
    if len(q.front) > 0 {
        return q.front[len(q.front)-1]
    } else {
        return q.back[q.backOffset]
    }
}

func (q *IntQueue) PopFront() {
    if len(q.front) > 0 {
        q.front = q.front[:len(q.front)-1]
    } else {
        if len(q.back) > 0 {
            q.backOffset++
        } else {
            panic("Cannot pop front of empty queue.")
        }
    }
}

func (q *IntQueue) PushBack(v int) {
    if q.frontOffset > 0 {
        i := q.frontOffset - 1
        q.front[i] = v
        q.frontOffset = i
    } else {
        q.back = append(q.back, v)
    }
}

func (q *IntQueue) Back() int {
    if len(q.back) > 0 {
        return q.back[len(q.back)-1]
    } else {
        return q.front[q.frontOffset]
    }
}

func (q *IntQueue) PopBack() {
    if len(q.back) > 0 {
        q.back = q.back[:len(q.back)-1]
    } else {
        if len(q.front) > 0 {
            q.frontOffset++
        } else {
            panic("Cannot pop back of empty queue.")
        }
    }
}

Çok küçük fonksiyonlar var ama 6 fonksiyondan 3 tanesi sadece diğerinin aynası.


Burada diziler kullanıyorsunuz. Yığınlarının nerede olduğunu görmüyorum.
melpomene

@melpomene Tamam, daha yakından bakarsanız, gerçekleştirdiğim tek işlemin dizideki son öğeyi eklemek / kaldırmak olduğunu fark edeceksiniz. Başka bir deyişle, itme ve haşhaş. Tüm niyet ve amaçlar için bunlar yığınlardır, ancak diziler kullanılarak uygulanır.
John Leidegren

Aslında, bu sadece yarısı doğru, iki katına çıkmış yığınlar varsayıyorum. Yığın, belirli koşullar altında aşağıdan yukarıya standart olmayan bir şekilde değiştirilmesine izin veriyorum.
John Leidegren

0

Linkedlist kullanarak Java benim çözüm burada.

class queue<T>{
static class Node<T>{
    private T data;
    private Node<T> next;
    Node(T data){
        this.data = data;
        next = null;
    }
}
Node firstTop;
Node secondTop;

void push(T data){
    Node temp = new Node(data);
    temp.next = firstTop;
    firstTop = temp;
}

void pop(){
    if(firstTop == null){
        return;
    }
    Node temp = firstTop;
    while(temp != null){
        Node temp1 = new Node(temp.data);
        temp1.next = secondTop;
        secondTop = temp1;
        temp = temp.next;
    }
    secondTop = secondTop.next;
    firstTop = null;
    while(secondTop != null){
        Node temp3 = new Node(secondTop.data);
        temp3.next = firstTop;
        firstTop = temp3;
        secondTop = secondTop.next;
    }
}

}

Not: Bu durumda, pop işlemi çok zaman alır. Bu yüzden iki yığını kullanarak bir kuyruk oluşturmayı önermeyeceğim.


0

İle O(1) dequeue(), pythonquick'in cevabı ile aynı :

// time: O(n), space: O(n)
enqueue(x):
    if stack.isEmpty():
        stack.push(x)
        return
    temp = stack.pop()
    enqueue(x)
    stack.push(temp)

// time: O(1)
x dequeue():
    return stack.pop()

İle O(1) enqueue()(bu yazıda bu söz konusu değildir, bu yüzden bu cevap), aynı zamanda kabarmayı ve en alttaki öğeyi döndürmek için geri izlemeyi kullanır.

// O(1)
enqueue(x):
    stack.push(x)

// time: O(n), space: O(n)
x dequeue():
    temp = stack.pop()
    if stack.isEmpty():
        x = temp
    else:
        x = dequeue()
        stack.push(temp)
    return x

Açıkçası, yine de verimsiz ama zarif olduğu için iyi bir kodlama egzersizi.


0

** Kolay JS çözümü **

  • Not: Başkalarının fikirlerinden yorum aldım

/*

enQueue(q,  x)
 1) Push x to stack1 (assuming size of stacks is unlimited).

deQueue(q)
 1) If both stacks are empty then error.
 2) If stack2 is empty
   While stack1 is not empty, push everything from stack1 to stack2.
 3) Pop the element from stack2 and return it.

*/
class myQueue {
    constructor() {
        this.stack1 = [];
        this.stack2 = [];
    }

    push(item) {
        this.stack1.push(item)
    }

    remove() {
        if (this.stack1.length == 0 && this.stack2.length == 0) {
            return "Stack are empty"
        }

        if (this.stack2.length == 0) {

            while (this.stack1.length != 0) {
                this.stack2.push(this.stack1.pop())
            }
        }
        return this.stack2.pop()
    }


    peek() {
        if (this.stack2.length == 0 && this.stack1.length == 0) {
            return 'Empty list'
        }

        if (this.stack2.length == 0) {
            while (this.stack1.length != 0) {
                this.stack2.push(this.stack1.pop())
            }
        }

        return this.stack2[0]
    }

    isEmpty() {
        return this.stack2.length === 0 && this.stack1.length === 0;
    }

}

const q = new myQueue();
q.push(1);
q.push(2);
q.push(3);
q.remove()

console.log(q)


-1
public class QueueUsingStacks<T>
{
    private LinkedListStack<T> stack1;
    private LinkedListStack<T> stack2;

    public QueueUsingStacks()
    {
        stack1=new LinkedListStack<T>();
        stack2 = new LinkedListStack<T>();

    }
    public void Copy(LinkedListStack<T> source,LinkedListStack<T> dest )
    {
        while(source.Head!=null)
        {
            dest.Push(source.Head.Data);
            source.Head = source.Head.Next;
        }
    }
    public void Enqueue(T entry)
    {

       stack1.Push(entry);
    }
    public T Dequeue()
    {
        T obj;
        if (stack2 != null)
        {
            Copy(stack1, stack2);
             obj = stack2.Pop();
            Copy(stack2, stack1);
        }
        else
        {
            throw new Exception("Stack is empty");
        }
        return obj;
    }

    public void Display()
    {
        stack1.Display();
    }


}

Her enqueue işlemi için yığının üstüne ekler1. Her dequeue için, stack1'in içeriğini stack2'ye boşaltırız ve yığının en üstündeki öğeyi kaldırırız. enqueue zaman karmaşıklığı normal bir yığınla aynıdır


Bu kod verimsizdir (gereksiz kopyalama) ve bozuk: yapıcıda somutlaştırıldığı if (stack2 != null)için her zaman doğrudur stack2.
melpomene

-2

İki java.util.Stack nesnesi kullanarak kuyruk uygulaması:

public final class QueueUsingStacks<E> {

        private final Stack<E> iStack = new Stack<>();
        private final Stack<E> oStack = new Stack<>();

        public void enqueue(E e) {
            iStack.push(e);
        }

        public E dequeue() {
            if (oStack.isEmpty()) {
                if (iStack.isEmpty()) {
                    throw new NoSuchElementException("No elements present in Queue");
                }
                while (!iStack.isEmpty()) {
                    oStack.push(iStack.pop());
                }
            }
            return oStack.pop();
        }

        public boolean isEmpty() {
            if (oStack.isEmpty() && iStack.isEmpty()) {
                return true;
            }
            return false;
        }

        public int size() {
            return iStack.size() + oStack.size();
        }

}

3
Bu kod işlevsel olarak Dave L'nin cevabı ile aynıdır. Yeni bir şey eklemez, bir açıklama bile eklemez.
melpomene

Temel istisna işleme ile birlikte isEmpty () ve size () yöntemlerini ekler. Açıklama eklemek için düzenleyeceğim.
realPK

1
Kimse bu ekstra yöntemleri istemedi ve önemsiz (her biri bir satır): return inbox.isEmpty() && outbox.isEmpty()ve return inbox.size() + outbox.size()sırasıyla. Boş bir kuyruktan ayrıldığınızda Dave L.'nin kodu zaten bir istisna atar. Asıl soru Java hakkında bile değildi; genel olarak veri yapıları / algoritmaları ile ilgiliydi. Java uygulaması sadece ek bir örnekti.
melpomene

1
Bu, iki yığından nasıl kuyruk oluşturulacağını anlamak isteyen insanlar için harika bir kaynaktır, diyagramlar kesinlikle Dave'in cevabını okumaktan daha fazla yardımcı oldu.
Kemal Tezer Dilsiz

@melpomene: Bu yöntem önemsiz olmak değil, ihtiyaç duyulan yöntemlerle ilgili. Java'daki kuyruk arabirimi, gerekli oldukları için bu yöntemleri Koleksiyon arabiriminden genişletir.
realPK
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.