2つのスタックを使用してキューを実装する方法は?


394

2つのスタックがあり、他の一時変数がないとします。

2つのスタックのみを使用してキューデータ構造を「構築」することは可能ですか?

回答:


701

2つのスタックを保持し、それらinboxを呼び出しましょうoutbox

エンキュー

  • 新しい要素を inbox

デキュー

  • outbox空の場合は、各要素をからポップしinboxて押して補充しますoutbox

  • 最上位の要素をポップして返す outbox

この方法を使用すると、各要素は各スタックに1回だけ存在します。つまり、各要素は2回プッシュされ、2回ポップされ、償却済みの一定時間操作を提供します。

Javaでの実装は次のとおりです。

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
最悪の場合の時間の複雑さは依然としてO(n)です。私はこれを言うことに固執します。私はそこにいる学生(これは宿題/教育の質問のように聞こえます)がこれをキューを実装するための許容できる方法であると思っていないことを願っています。
タイラー

26
単一のポップ操作の最悪の場合の時間はO(n)です(nはキューの現在のサイズです)。ただし、一連のn個のキュー操作の最悪の場合の時間もO(n)であり、償却後の一定の時間になります。この方法でキューを実装することはありませんが、それほど悪くはありません。
Dave L.

1
@Tylerスタックが配列ベースの場合、ほとんどの場合、単一の操作で常にO(n)の最悪のケースが発生します。
Thomas Ahle

2
@Tyler:チェックsgi.com/tech/stl/Deque.htmlを。Dequeは「要素へのランダムアクセスをサポート」します。したがって、両端キューとスタックはどちらも配列ベースです。これは、参照の局所性が向上するため、実際には高速であるためです。
Thomas Ahle

13
@Newtang a)キュー1,2,3 => Inbox [3,2,1] / Outbox []。b)デキュー。送信トレイは空なので、refill => Inbox [] / Outbox [1,2,3]送信トレイからポップし、1 => Inbox [] / Outbox [2,3]を返します。c)キュー4,5 => Inbox [5,4] / Outbox [2,3]。d)デキュー。送信トレイは空ではないため、送信トレイからポップし、2 => Inbox [5,4] / Outbox [3]を返します。それはもっと理にかなっていますか?
Dave L.

226

A-スタックを逆にする方法

2つのスタックを使用してキューを作成する方法を理解するには、スタックのクリスタルクリアを元に戻す方法を理解する必要があります。スタックがどのように機能するか覚えておいてください、それはあなたのキッチンの皿スタックと非常に似ています。最後の洗浄皿として呼ばれる、クリーンスタックの一番上になりL AST I N F IRST OコンピュータサイエンスのUT(LIFO)。

以下のようにスタックをボトルのように想像してみましょう。

ここに画像の説明を入力してください

整数1、2、3をそれぞれプッシュすると、3がスタックの一番上になります。1が最初にプッシュされるため、2が1の上に配置されます。最後に、3がスタックの最上部に配置され、ボトルとして表されるスタックの最新の状態は次のようになります。

ここに画像の説明を入力してください

これで、スタックがボトルとして表され、値3、2、1が入力されます。また、スタックの一番上の要素が1になり、スタックの一番下の要素が3になるように、スタックを逆にします。ボトルを取り、上下を逆にして、すべての値が順番に逆になるようにしますか?

ここに画像の説明を入力してください

はい、できますが、それはボトルです。同じプロセスを実行するには、最初のスタック要素を逆の順序で格納する2番目のスタックが必要です。設定済みのスタックを左側に、新しい空のスタックを右側に配置しましょう。要素の順序を逆にするために、左のスタックから各要素をポップし、それらを右のスタックにプッシュします。下の画像で、私たちがそうするときに何が起こるかを見ることができます。

ここに画像の説明を入力してください

スタックを逆にする方法を知っています。

B-キューとして2つのスタックを使用する

前の部分では、スタック要素の順序を逆にする方法を説明しました。要素をスタックにプッシュおよびポップすると、出力がキューの順序とまったく逆になるため、これは重要でした。例を考えて、整数の配列を{1, 2, 3, 4, 5}スタックにプッシュしましょう。要素をポップしてスタックが空になるまで出力すると、プッシュ順とは逆の順序で配列が取得されます。これは{5, 4, 3, 2, 1}、同じ入力に対して、キューが空になるまでキューをデキューすると、出力がになります{1, 2, 3, 4, 5}。したがって、要素の同じ入力順序の場合、キューの出力はスタックの出力とまったく逆になります。追加のスタックを使用してスタックを逆にする方法を知っているので、2つのスタックを使用してキューを構築できます。

キューモデルは2つのスタックで構成されます。1つのスタックがenqueue操作に使用され(左側のスタック#1、入力スタックと呼ばれます)、別のスタックがdequeue操作に使用されます(右側のスタック#2、出力スタックと呼ばれます)。以下の画像をご覧ください。

ここに画像の説明を入力してください

私たちの疑似コードは以下の通りです。


エンキュー操作

Push every input element to the Input Stack

デキュー操作

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}それぞれ整数をエンキューしましょう。整数は左側にある入力スタックスタック#1)にプッシュされます。

ここに画像の説明を入力してください

次に、デキュー操作を実行するとどうなりますか?デキュー操作が実行されるたびに、キューは出力スタックが空であるかどうかを確認します(上記の疑似コードを参照)出力スタックが空の場合、入力スタックは出力で抽出されるため、要素入力スタックの逆になります。値を返す前のキューの状態は次のようになります。

ここに画像の説明を入力してください

出力スタック(スタック#2)の要素の順序を確認します。要素を出力スタックからポップして、キューからデキューした場合と同じように出力できることは明らかです。したがって、2つのデキュー操作を実行すると、最初に{1, 2}それぞれ取得されます。次に、要素3が出力スタックの唯一の要素となり、入力スタックは空になります。要素4と5をエンキューすると、キューの状態は次のようになります。

ここに画像の説明を入力してください

これで出力スタックは空にならず、デキュー操作を実行すると、出力スタックから3つだけがポップアウトされます。次に、状態は次のように表示されます。

ここに画像の説明を入力してください

ここでも、2つ以上のデキュー操作を実行すると、最初のデキュー操作で、出力スタックが空であるかどうかがチェックされます。これはtrueです。次に、入力スタックの要素をポップアウトし、入力スタックが空になるまでそれらを出力スタックにプッシュすると、キューの状態は次のようになります。

ここに画像の説明を入力してください

見やすいように、2つのデキュー操作の出力は {4, 5}

C-2つのスタックで構築されたキューの実装

これはJavaでの実装です。Stackの既存の実装を使用するつもりはないので、ここでの例はホイールを再発明します。

C-1)MyStackクラス:単純なスタック実装

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クラス:2つのスタックを使用したキューの実装

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)デモコード

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)出力例

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

18
できれば、一日中+1します。それがどのように一定の期間で償却されたかを理解できませんでした。あなたのイラストは、特に出力スタックに残りの要素を残し、空になったときにのみ補充するという部分を明確にしました。
Shane McQuillan

1
これにより、ポップ中に発生していたタイムアウトエラーを防ぐことができました。要素を元のスタックに戻していましたが、その必要はありませんでした。賞賛!
Pranit Bankar 2016

2
すべてのコメントは、このコメントの後にモデル化する必要があります。
lolololol ol

4
私は本当にこれを解決する必要はなく、ブラウジングだけでした...しかし、私がこのような答えを見つけたとき、私はただ恋に落ちます。
マーベリック

80

1つのスタックのみを使用してキューをシミュレートすることもできます。2番目の(一時的な)スタックは、insertメソッドへの再帰呼び出しの呼び出しスタックによってシミュレートできます。

キューに新しい要素を挿入するときの原則は同じままです。

  • 順序を逆にするには、要素をあるスタックから別の一時スタックに転送する必要があります。
  • 次に、挿入する新しい要素を一時スタックにプッシュします
  • 次に、要素を元のスタックに戻します
  • 新しい要素はスタックの一番下にあり、最も古い要素が一番上にあります(最初にポップされます)

スタックを1つだけ使用するQueueクラスは次のようになります。

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
コードはエレガントに見えるかもしれませんが、非常に非効率的(O(n ** 2)挿入)であり、@ pythonquickが指摘するように、2つのスタック(ヒープに1つとコールスタックに1つ)が残っています。非再帰的アルゴリズムの場合、再帰をサポートする言語のコールスタックから常に1つの「追加」スタックを取得できます。
Antti Huima

1
@ antti.huimaそして、これがどのように二次挿入になるかを説明してくれませんか?!私が理解していることから、挿入はn回のポップとn回のプッシュ操作を行うので、完全に線形のO(n)アルゴリズムです。
LP_ 2014年

1
@LP_ n items上記のデータ構造を使用してキューに挿入するには、2次時間O(n ^ 2)が必要です。合計(1 + 2 + 4 + 8 + .... + 2(n-1))はになり~O(n^2)ます。要点を理解してほしい。
Ankit Kumar 2014

1
@ antti.huimaあなたは挿入関数の複雑さについて話していました(「O(n 2)挿入」と言いました-おそらく「O(n 2)フィル」を意味していました)。慣例により、「複雑さの挿入」は1回の挿入にかかる時間であり、ここではすでに存在する要素の数に比例します。n個のアイテムを挿入するのに必要な時間で話した場合、ハッシュテーブルには線形挿入があると言えます。そうではありません。
LP_ 2014年

2
基本的に、スタックとしてスタックを使用しています。つまり、スタックに多数のアイテムが含まれていると、スタックオーバーフローが発生する可能性があります。これは、このサイト用に設計されたソリューションとほぼ同じです。
UKMonkey 2016年

11

ただし、時間の複雑さはさらに悪化します。優れたキュー実装は、すべてを一定の時間で実行します。

編集する

なぜ私の回答がここで反対票を投じられたのかわかりません。プログラムする場合、時間の複雑さを気にし、2つの標準スタックを使用してキューを作成することは非効率的です。それは非常に有効で適切なポイントです。他の誰かがこれをさらに反対する必要があると感じた場合、その理由を知りたいと思います。

もう少し詳細:2つのスタックを使用することが単にキューよりも悪い理由:2つのスタックを使用していて、送信トレイが空のときに誰かがデキューを呼び出す場合、受信トレイの下部に到達するまで線形時間が必要です(ご覧のとおり)デイブのコードで)。

キューを単一リンクリストとして実装し(各要素が次に挿入される要素を指す)、プッシュのために最後に挿入された要素への追加のポインターを保持(または循環リストにする)できます。このデータ構造にキューとデキューを実装することは、一定の時間で非常に簡単に実行できます。これは最悪の場合の一定の時間であり、償却されません。そして、コメントがこの明確化を求めているように見えるので、最悪の場合の一定の時間は、償却された一定の時間よりも厳密に優れています。


平均的なケースではありません。ブライアンの答えは、定数のエンキューおよびデキュー操作を償却するキューについて説明しています。
Daniel Spiewak 2008

それは本当だ。平均的なケースと償却時間の複雑さは同じです。しかし、デフォルトは通常、操作ごとに最悪の場合であり、これはO(n)です。ここで、nは構造体の現在のサイズです。
タイラー

1
最悪の場合も償却できます。たとえば、高価なサイズ変更とコピーの操作が頻繁に必要になる場合でも、可変動的配列(ベクトル)は通常、挿入時間が一定であると見なされます。
Daniel Spiewak

1
「ワーストケース」と「償却」は、2つの異なるタイプの時間の複雑さです。「最悪の場合は償却可能」と言っても意味がありません。最悪の場合=償却することができれば、これは大幅な改善になります。平均化せずに、最悪のケースについて話すだけです。
タイラー

O(1)の最悪のケースがO(1)の最悪のケースとO(n)の最悪のケースの組み合わせよりも「厳密に良い」とはどういう意味かわかりません。一定の倍率が重要です。N個のアイテムが含まれている場合、Nマイクロ秒のN回の操作の後に再パックする必要があり、それ以外の場合は操作ごとに1マイクロ秒かかるデータ構造は、各操作に1ミリ秒かかるデータ構造よりもはるかに便利です。データサイズが数百万のアイテムに拡大する場合(つまり、一部の個々の操作に数秒かかることを意味します)。
スーパーキャット2012

8

実装するキューをq、qの実装に使用するスタックをstack1とstack2とします。

qは2つの方法で実装できます。

方法1(enQueue操作のコストを高くする)

このメソッドは、新しく入力された要素が常にスタック1の一番上にあることを確認するため、deQueue操作は単にstack1からポップされます。要素をstack1の最上部に配置するには、stack2を使用します。

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.

方法2(deQueue操作を高コストにする)

このメソッドでは、エンキュー操作で、新しい要素がstack1の最上部に入力されます。デキュー操作では、stack2が空の場合、すべての要素がstack2に移動され、最後にstack2の先頭が返されます。

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.

方法2は方法1よりも明らかに優れています。方法1はすべての要素をenQueue操作で2回移動し、方法2(deQueue操作)は要素を1回移動し、stack2が空の場合にのみ要素を移動します。


あなたの方法を除いて、私が理解した解決策はありません。2。エンキューとデキューの方法をステップで説明する方法が気に入っています。
theGreenCabbage


3

C#のソリューション

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

キュー内の2つのスタックは、stack1およびstack2として定義されています。

エンキュー: euqueued要素は常にスタック1にプッシュされます

Dequeue stack2が空でない場合にキューに挿入される最初の要素であるため 、stack2の上部をポップアウトできます。ときstack2が空である、我々はからすべての要素をポップstack1とにそれらをプッシュするstack2一つずつ。キューの最初の要素は、stack1の一番下にプッシュされます。これは、stack2の一番上にあるため、ポップ操作とプッシュ操作の直後にポップアウトできます。

以下は、同じC ++サンプルコードです。

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

このソリューションは私のブログから借用したものです。段階的な操作シミュレーションによるより詳細な分析は、私のブログWebページで入手できます。


2

一番下の要素を取得するには、最初のスタックからすべてをポップする必要があります。次に、「デキュー」操作ごとに、すべてを2番目のスタックに戻します。


3
はい、そうです。どうしてそんなに多くの反対票を獲得したのだろう。私はあなたの答えを賛成しました
Bharati

これが彼の最後の答えであり、それから10年が経過したことを確認するのは気味が悪い。
Shanu Gupta

2

C#開発者向けの完全なプログラムは次のとおりです。

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

スタックを使用してキューの次の操作を実装します。

push(x)-要素xをキューの後ろにプッシュします。

pop()-要素をキューの前から削除します。

peek()-フロント要素を取得します。

empty()-キューが空かどうかを返します。

ここに画像の説明を入力してください

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で2つのスタックを使用するキューの実装:

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

2つのスタックでキューを実装することに関連する多くの投稿を取得しますが、次のようになります。

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

上記の投稿から私が見つけた重要な方法の1つは、スタックデータ構造と再帰呼び出しスタックのみでキューを構築することでした。

文字通り、これはまだ2つのスタックを使用していると主張できますが、理想的には、これは1つのスタックデータ構造のみを使用しています。

以下は問題の説明です:

  1. データのエンキューおよびデキュー用に単一のスタックを宣言し、データをスタックにプッシュします。

  2. 一方、deQueueingには、スタックのサイズが1のときにスタックの要素がポップされる基本条件があります。これにより、deQueue再帰中にスタックオーバーフローが発生しなくなります。

  3. デキュー中に、最初にスタックの一番上からデータをポップします。理想的には、この要素はスタックの一番上にある要素です。これが完了したら、deQueue関数を再帰的に呼び出してから、上にポップした要素をスタックにプッシュします。

コードは次のようになります。

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;

このようにして、単一のスタックデータ構造と再帰呼び出しスタックを使用してキューを作成できます。


1

以下は、ES6構文を使用したJavaScript言語のソリューションです。

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

以下は使い方です:

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"

これにはバグがあります。デキュー後にさらに要素をエンキューする場合は、それらをに入れますstack1dequeue再びアクセスすると、アイテムがに移動されstack2、すでに存在していたアイテムよりも前に置かれます。
アレクサンダー-モニカの復活

0

Goの標準ライブラリには豊富なコレクションがないため、Goでこの質問に答えます。

スタックは非常に実装しやすいので、2つのスタックを使用してダブルエンドキューを実現しようと思いました。どのようにして私の答えに到達したかをよりよく理解するために、実装を2つの部分に分割しました。最初の部分は理解しやすいと思いますが、不完全です。

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:]
    }
}

これは基本的に2つのスタックであり、スタックの最下部を相互に操作できます。また、STLの命名規則を使用しました。スタックの従来のプッシュ、ポップ、ピーク操作には、キューの前と後ろのどちらを参照する場合でも、前/後ろのプレフィックスがあります。

上記のコードの問題は、メモリを非常に効率的に使用しないことです。実際には、スペースが足りなくなるまで、それは無限に成長します。それは本当に悪いです。これに対する修正は、可能な限りスタックスペースの下部を再利用することです。Goのスライスはいったん縮小すると前面で拡大できないため、これを追跡するためにオフセットを導入する必要があります。

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.")
        }
    }
}

たくさんの小さな機能ですが、6つの機能のうち3つは他の機能のミラーです。


ここでは配列を使用しています。スタックがどこにあるかわかりません。
メルポメン

@melpomeneわかりました。よく見ると、配列の最後の要素の追加/削除だけが操作を実行していることがわかります。言い換えれば、押すと飛び出る。すべての意図と目的のために、これらはスタックですが、配列を使用して実装されています。
John Leidegren

@melpomene実際には、それはちょうど半分に過ぎません、私はダブルエンドのスタックを想定しています。特定の条件下でスタックを標準的でない方法でボトムアップから変更できるようにしています。
John Leidegren

0

これがリンクリストを使用したJavaでの私の解決策です。

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

}

注:この場合、ポップ操作には非常に時間がかかります。したがって、2つのスタックを使用してキューを作成することはお勧めしません。


0

O(1) dequeue()、これはpythonquickの答えと同じです:

// 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()

O(1) enqueue()(これは、この答えので、この記事で言及されていない)、また、バブルまでバックトラックを使用して、一番下の項目を返しています。

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

明らかに、それは非効率的でありながらエレガントであるため、優れたコーディング演習です。


0

**簡単なJSソリューション**

  • 注:他の人のコメントからアイデアを取り入れました

/*

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


}

エンキュー操作ごとに、stack1の一番上に追加します。すべてのデキューについて、stack1のコンテンツをstack2に空にし、スタックの一番上の要素を削除します。stack1をstack2にコピーする必要があるため、デキューの時間の複雑さはO(n)です。エンキューの時間の複雑さは通常のスタックと同じです


このコードは非効率的(不要なコピー)で壊れています。コンストラクタでインスタンス化されるif (stack2 != null)ため、常にtrueになりstack2ます。
メルポメン

-2

2つのjava.util.Stackオブジェクトを使用したキューの実装:

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
このコードは、機能的にはDave Lの回答と同じです。新しいコードは追加されておらず、説明もありません。
メルポメン

isEmpty()およびsize()メソッドと基本的な例外処理を追加します。説明を追加して編集します。
realPK 2016

1
これらの追加のメソッドを要求する人はいませんでした。それらは簡単です(それぞれ1行):return inbox.isEmpty() && outbox.isEmpty()return inbox.size() + outbox.size()。Dave L.のコードは、空のキューからデキューすると例外をスローします。元々の質問はJavaに関するものではありませんでした。それは一般的にデータ構造/アルゴリズムについてでした。Java実装は単なる追加の説明でした。
メルポメン

1
これは、2つのスタックからキューを構築する方法を理解しようとしている人にとって素晴らしい情報源です。この図は、Daveの回答を読むよりも間違いなく役立ちました。
Kemal Tezer Dilsiz、2016年

@melpomene:それは些細なことであるが必要な方法についてではありません。JavaのQueueインターフェースは、必要なため、これらのメソッドをCollectionインターフェースから拡張します。
realPK 2017年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.