スタックが2つあり、他の一時変数がないとします。
2つのスタックだけを使用してキューデータ構造を「構築」することは可能ですか?
スタックを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();
}
}
2つのスタックを使用してキューを構成する方法を理解するには、スタックを逆にクリアする方法を理解しておく必要があります。積み重ねがどのように機能するかを覚えておいてください、それはあなたの台所の皿積み重ねに非常に似ています。最後に洗った皿は、LastInと呼ばれるクリーンスタックの一番上になります。 F最初Out(LIFO)コンピュータサイエンス。
下のように私たちのスタックがボトルのようになっていると想像してみてください。
整数1,2,3をそれぞれプッシュすると、3がスタックの一番上になります。 1が最初にプッシュされるので、2が1の一番上に配置されます。最後に、3がスタックの一番上に配置され、ボトルとして表されるスタックの最新の状態は以下のようになります。
今、私たちはボトルがボトルネックとして表されている私たちのスタックを持っています3,2,1。そして、スタックの一番上の要素が1、一番下の要素が3になるように、スタックを逆にしたいのですが。すべての値が順番に逆になるように、ボトルを取り出して逆さにすることができますか。
はい、できますが、それはボトルです。同じプロセスを実行するには、最初のスタック要素を逆の順序で格納する2番目のスタックが必要です。移入されたスタックを左側に、新しい空のスタックを右側に配置しましょう。要素の順序を逆にするために、各要素を左のスタックからポップし、それらを右のスタックにプッシュします。あなたは我々が下の画像でそうするとき何が起こるか見ることができます。
だから我々はスタックを反転する方法を知っています。
前の部分で、スタック要素の順序を逆にする方法を説明しました。要素をスタックにプッシュしてポップすると、出力はキューの逆順になるため、これは重要でした。例を考えて、整数の配列{1, 2, 3, 4, 5}
をスタックにプッシュしましょう。要素をポップしてスタックが空になるまでそれらを出力すると、プッシュ順と逆の順序で配列が得られます。これは{5, 4, 3, 2, 1}
になります。キューが空になるまでキューをデキューする場合、同じ入力に対して出力は{1, 2, 3, 4, 5}
になります。したがって、要素の入力順序が同じ場合、キューの出力がスタックの出力とまったく逆になることは明らかです。追加のスタックを使用してスタックを反転する方法を知っているので、2つのスタックを使用してキューを構築できます。
私たちのキューモデルは2つのスタックで構成されます。一方のスタックは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)にプッシュされます。
それでは、デキュー操作を実行するとどうなりますか。デキュー操作が実行されるときはいつでも、queueは出力スタックが空かどうかをチェックします(上記の擬似コードを参照)。入力スタックの逆転します。値を返す前は、キューの状態は次のようになります。
出力スタック(スタック#2)の要素の順序を確認してください。出力がキューからデキューされた場合と同じになるように、出力スタックから要素をポップできることは明らかです。したがって、2つのデキュー操作を実行すると、まず{1, 2}
がそれぞれ取得されます。その場合、要素3が出力スタックの唯一の要素になり、入力スタックは空になります。要素4と5をエンキューすると、キューの状態は次のようになります。
これで、出力スタックは空ではなくなり、デキュー操作を実行した場合、3つだけが出力スタックから飛び出します。すると、状態は以下のようになります。
繰り返しますが、最初のデキュー操作でさらに2つのデキュー操作を実行すると、queueは出力スタックが空かどうかをチェックします。これは真です。次に、入力スタックが空になるまで、入力スタックの要素を取り出し、それらを出力スタックにプッシュすると、キューの状態は次のようになります。
見やすいように、2つのデキュー操作の出力は{4, 5}
になります。
これがJavaでの実装です。私はStackの既存の実装を使用するつもりはないので、ここでの例は輪を再発明するつもりです。
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");
}
}
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;
}
}
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());
}
}
Dequeued: 1
Dequeued: 2
Dequeued: 3
Dequeued: 4
Dequeued: 5
スタックを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();
}
}
ただし、時間の複雑さはさらに悪化します。良いキュー実装は、すべてを一定の時間で実行します。
編集
なぜ私の答えがここで軽視されたのかわからない。我々がプログラムするならば、我々は時間の複雑さを気にします、そしてキューを作るのに2つの標準的なスタックを使うことは非効率的です。それは非常に有効で適切な点です。他の誰かがこれをもっと引き下げる必要があると感じたら、私はその理由を知りたいと思います。
もう少し詳しく:なぜ2つのスタックを使うのが単なるキューより悪いのか:あなたが2つのスタックを使うと、送信トレイが空のときに誰かがデキューを呼び出す場合(あなたがDaveのコードで見ることができるように)受信箱の一番下に行きなさい。
キューを片方向リンクリスト(各要素が次に挿入された要素を指す)として実装し、最後に挿入された要素への追加ポインタをプッシュ用に保持する(または循環リストにする)ことができます。このデータ構造でキューとデキューを実装することは、非常に簡単に一定の時間内に行うことができます。それは償却されない、最悪の場合の一定時間です。そして、コメントがこの説明を求めているように思われるので、最悪の場合の一定時間は償却された一定時間より厳密に良いです。
実装されるキューを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が空の場合にのみ要素を移動します。
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番目のスタックに戻します。
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つのスタックは、次のように定義されています。 stack1 そして stack2。
エンキュー:エンキューされた要素は常にプッシュインされます。 stack1
デキュー:トップ 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ページにあります。
// 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();
}
}
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()!)
}
}
}
}
2つのスタックでキューを実装することに関する投稿はたくさんありますが:
https://www.geeksforgeeks.org/queue-using-stacks/
私が上記の記事から見つけた1つの重要な方法はスタックデータ構造と再帰呼び出しスタックだけで待ち行列を構築することでした。
文字通りこれはまだ2つのスタックを使用していると主張できますが、理想的にはこれは1つのスタックデータ構造のみを使用しています。
以下は問題の説明です。
データのエンキューおよびデキューのために単一のスタックを宣言し、データをスタックにプッシュします。
スタックのサイズが1の場合、スタックの要素がポップされる基本条件があります。これにより、deQueue再帰中にスタックオーバーフローが発生しなくなります。
デキュー中に、まずスタックの先頭からデータをポップします。理想的には、この要素はスタックの一番上に存在する要素になります。これが完了したら、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;
これにより、単一スタックのデータ構造と再帰呼び出しスタックを使用してキューを作成できます。
これがリンクリストを使った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つのスタックを使用してキューを作成することをお勧めしません。
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つは他のものの単なる鏡です。
以下は、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"
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)です。エンキューの時間的複雑さは通常のスタックと同じです
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
明らかに、それは非効率的ではあるがそれにもかかわらずエレガントなコーディングの練習です。