두 개의 스택을 사용하여 큐를 구현하는 방법은 무엇입니까?


394

두 개의 스택이 있고 다른 임시 변수가 없다고 가정하십시오.

두 스택 만 사용하여 큐 데이터 구조를 "구성"할 수 있습니까?

답변:


701

스택 2 개를 유지 inbox하고로 호출합니다 outbox.

대기열 :

  • 새로운 요소를 밀어 inbox

대기열에서 제외 :

  • outbox비어 있으면 각 요소를 튀어 나와 inbox밀어서 다시 채우십시오.outbox

  • 상단 요소를 팝하고 반환 outbox

이 방법을 사용하면 각 요소가 각 스택에 정확히 한 번만 표시됩니다. 즉, 각 요소가 두 번 밀리고 두 번 터져서 상시 일정 시간 작업이 수행됩니다.

다음은 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)입니다. 나는 (이것은 숙제 / 교육적인 질문처럼 들린다) 이것이 큐를 구현하는 데 수용 가능한 방법이라고 생각하지 않기를 희망하기 때문에 이것을 계속 말하고있다.
Tyler

26
단일 팝 조작의 최악의 시간은 O (n)입니다 (여기서 n은 큐의 현재 크기 임). 그러나 일련의 n 큐 작업에 대한 최악의 시간도 O (n)이므로 상각 된 상수 시간을 제공합니다. 이 방법으로 대기열을 구현하지는 않지만 그렇게 나쁘지는 않습니다.
Dave L.

1
@Tyler 스택이 배열 기반이라면 대부분의 경우 단일 작업에서 항상 최악의 경우 O (n)를 얻게됩니다.
Thomas Ahle

2
@Tyler : 체크 sgi.com/tech/stl/Deque.html를 . "요소에 대한 임의 액세스를 지원합니다." 따라서 deque와 stack은 모두 배열 기반입니다. 이는 더 나은 참조 위치를 제공하므로 실제로 더 빠르기 때문입니다.
Thomas Ahle

13
@Newtang a) queue 1,2,3 => Inbox [3,2,1] / Outbox [] . b) 대기열에서 제외 보낼 편지함이 비어 있으므로 리필 => 받은 편지함 [] / Outbox [1,2,3] . 보낼 편지함에서 팝하고 1 => 받은 편지함 [] / Outbox [2,3]을 반환 합니다. c) 대기열 4,5 => 받은 편지함 [5,4] / Outbox [2,3] . d) 대기열에서 제외 발신, 그래서 발신으로부터 팝 비우 2 => 반환되지 않는다 함 [5,4] / 발신 [3] . 더 이해가 되나요?
Dave L.

226

A-스택을 뒤집는 방법

두 개의 스택을 사용하여 큐를 구성하는 방법을 이해하려면 스택 수정을 취소하는 방법을 이해해야합니다. 스택 작동 방식을 기억하십시오. 주방의 식기 스택과 매우 유사합니다. 마지막 세정 접시로 불리는 깨끗한 스택의 최상위에있을 것이다 L AST I N F는 IRST O UT (LIFO)에서 컴퓨터 과학.

스택을 아래와 같이 병처럼 상상해 봅시다.

여기에 이미지 설명을 입력하십시오

정수 1,2,3을 각각 푸시하면 3이 스택의 맨 위에 있습니다. 1이 먼저 눌려지기 때문에 2가 1의 맨 위에 놓입니다. 마지막으로, 3이 스택의 맨 위에 놓이고 병으로 표시된 스택의 최신 상태는 다음과 같습니다.

여기에 이미지 설명을 입력하십시오

이제 병이 3,2,1 값으로 채워진 스택을 표시했습니다. 스택의 맨 위 요소가 1이되고 스택의 맨 아래 요소가 3이되도록 스택을 뒤집고 싶습니다. 우리는 병을 가져 가서 거꾸로 잡고 모든 값이 순서대로 바뀌어야합니까?

여기에 이미지 설명을 입력하십시오

예, 우리는 할 수 있지만 그것은 병입니다. 동일한 프로세스를 수행하려면 첫 번째 스택 요소를 역순으로 저장할 두 번째 스택이 필요합니다. 채워진 스택을 왼쪽에 배치하고 새 빈 스택을 오른쪽에 배치합시다. 요소의 순서를 반대로하려면 왼쪽 스택에서 각 요소를 팝하고 오른쪽 스택으로 밉니다. 아래 이미지에서 어떻게되는지 확인할 수 있습니다.

여기에 이미지 설명을 입력하십시오

따라서 스택을 뒤집는 방법을 알고 있습니다.

B-대기열로 두 개의 스택 사용

이전 부분에서는 스택 요소의 순서를 바꾸는 방법에 대해 설명했습니다. 요소를 스택으로 푸시 및 팝하면 출력이 대기열과 정확히 반대 순서이므로 중요합니다. 예를 생각해 보면 정수 배열을 {1, 2, 3, 4, 5}스택에 넣습니다 . 스택이 비워 질 때까지 요소를 팝하고 인쇄하면, 순서가 반대 인 순서로 배열을 가져옵니다 {5, 4, 3, 2, 1}. 동일한 입력에 대해 큐가 비워 질 때까지 큐를 대기열에서 빼면 출력이됩니다. 될 것 {1, 2, 3, 4, 5}입니다. 따라서 요소의 동일한 입력 순서에 대해 큐의 출력은 스택의 출력과 정확히 반대입니다. 추가 스택을 사용하여 스택을 뒤집는 방법을 알고 있으므로 두 스택을 사용하여 큐를 구성 할 수 있습니다.

큐 모델은 두 개의 스택으로 구성됩니다. 하나의 스택이 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)에서 요소의 순서를 확인하십시오. 출력 스택에서 요소를 팝업하여 출력이 대기열에서 대기열에서 제외 된 것과 동일하게되도록 할 수 있습니다. 따라서 두 개의 대기열 제거 작업을 실행하면 먼저 얻을 것 {1, 2}입니다. 그러면 요소 3이 출력 스택의 유일한 요소가되고 입력 스택이 비어있게됩니다. 요소 4와 5를 큐에 넣으면 큐의 상태는 다음과 같습니다.

여기에 이미지 설명을 입력하십시오

이제 출력 스택이 비어 있지 않으며, dequeue 작업을 실행하면 출력 스택에서 3 개만 튀어 나옵니다. 그러면 상태는 다음과 같습니다.

여기에 이미지 설명을 입력하십시오

다시 말하지만, 두 개의 추가 대기열 제거 작업을 실행하면 첫 번째 대기열 제거 작업에서 대기열이 출력 스택이 비어 있는지 확인합니다. 그런 다음 입력 스택의 요소를 꺼내서 입력 스택이 비어있을 때까지 출력 스택으로 밀어 넣습니다. 그러면 대기열의 상태는 다음과 같습니다.

여기에 이미지 설명을 입력하십시오

알기 쉽게, 두 대기열 제거 작업의 출력은 {4, 5}

C-두 개의 스택으로 구성된 큐 구현

다음은 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 클래스 : 두 스택을 사용한 큐 구현

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

2
모든 의견은이 의견을 따라 모델링해야합니다.
lolololol ol

4
나는 이것에 대한 해결책이 필요하지 않았다. 단지 브라우징 ... 그러나 이와 같은 대답을 볼 때 나는 단순히 사랑에 빠진다.
Maverick

80

하나의 스택 만 사용하여 대기열을 시뮬레이션 할 수도 있습니다. 두 번째 (임시) 스택은 insert 메소드에 대한 재귀 호출의 호출 스택으로 시뮬레이션 할 수 있습니다.

대기열에 새 요소를 삽입 할 때 원칙은 동일하게 유지됩니다.

  • 순서를 바꾸려면 한 스택에서 다른 임시 스택으로 요소를 전송해야합니다.
  • 그런 다음 삽입 할 새 요소를 임시 스택에 밀어 넣습니다.
  • 그런 다음 요소를 원래 스택으로 다시 전송하십시오.
  • 새 요소는 스택의 맨 아래에 있고 가장 오래된 요소는 맨 위에 있습니다 (먼저 팝)

하나의 스택 만 사용하는 큐 클래스는 다음과 같습니다.

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이 지적한 것처럼 여전히 스택에 하나는 힙에 있고 스택에는 하나가 있습니다. 비 재귀 알고리즘의 경우 항상 재귀를 지원하는 언어로 호출 스택에서 하나의 "추가"스택을 가져올 수 있습니다.
Antti Huima

1
@ antti.huima 그리고 어떻게 이차 삽입 법이 될 수 있는지 설명해 주시겠습니까?! 내가 이해 한 바에 따르면, insert는 n pop 및 n push 연산을 수행하므로 완벽하게 선형 O (n) 알고리즘입니다.
LP_

1
@LP_ n items위의 데이터 구조를 사용하여 큐 에 삽입하는 데 2 ​​차 시간 O (n ^ 2)가 걸립니다 . 합계 (1 + 2 + 4 + 8 + .... + 2(n-1))~O(n^2)입니다. 나는 당신이 요점을 얻길 바랍니다.
Ankit Kumar

1
@ antti.huima 인서트 함수의 복잡성에 대해 이야기했습니다 ( "O (n 2) 인서트" 라고 말했 을 것입니다. 아마도 "O (n 2) 채우기"를 의미했을 것입니다 ). 관례 상 "복잡성 삽입"은 한 번의 삽입에 걸리는 시간이며, 여기에는 이미 존재하는 요소의 수가 선형입니다. n 개의 항목 을 삽입하는 데 필요한 시간에 이야기 하면 해시 테이블에 선형 삽입이 있다고 말할 수 있습니다. 그렇지 않습니다.
LP_

2
본질적으로 스택을 스택으로 사용하고 있습니다. 이는 많은 수의 항목이 스택에 있으면 스택 오버플로로 끝날 수 있음을 의미합니다.이 사이트를 위해 솔루션이 설계된 것과 거의 같습니다!
UKMonkey

11

그러나 시간 복잡성은 더 나쁠 것입니다. 좋은 큐 구현은 모든 것을 일정한 시간에 수행합니다.

편집하다

왜 내 대답이 다운 다운되었는지 확실하지 않습니다. 프로그래밍하는 경우 시간 복잡성을 염두에두고 대기열을 만들기 위해 두 개의 표준 스택을 사용하는 것은 비효율적입니다. 매우 타당하고 관련성이 높은 요점입니다. 다른 사람이 이것을 더 공감해야 할 필요가 있다고 생각하면 이유를 알고 싶습니다.

좀 더 자세한 내용 : 두 개의 스택을 사용하는 것이 대기열보다 나쁜 이유 : 두 개의 스택을 사용하고 보낼 편지함이 비어있는 동안 누군가 대기열에서 전화를 걸면받은 편지함의 맨 아래에 도착하는 데 선형 시간이 필요합니다 (보시다시피) Dave의 코드에서).

큐를 단일 링크 목록 (각 요소가 다음에 삽입 된 요소를 가리킴)으로 구현하여 푸시를 위해 마지막으로 삽입 된 요소에 대한 추가 포인터를 유지하거나 순환 목록으로 만들 수 있습니다. 이 데이터 구조에서 큐 및 큐를 구현하는 것은 일정한 시간에 수행하기가 매우 쉽습니다. 그것은 최악의 일정한 시간이며 상각되지 않습니다. 그리고 의견 이이 설명을 요구하는 것처럼 보이는 최악의 일정 시간은 상각 일정 시간보다 엄격하게 좋습니다.


보통은 아닙니다. Brian의 답변은 지속적인 대기열 대기열 제거 작업을 상각 한 대기열을 설명 합니다.
Daniel Spiewak

사실입니다. 평균 대소 문자 및 상각 시간 복잡도는 동일합니다. 그러나 기본값은 일반적으로 작업 당 최악의 경우이며, 이것은 O (n)이며 여기서 n은 구조의 현재 크기입니다.
Tyler

1
최악의 경우도 상각 될 수 있습니다. 예를 들어, 가변 크기의 동적 배열 (벡터)은 값 비싼 크기 조정 및 복사 작업이 자주 필요한 경우에도 일반적으로 삽입 시간이 일정한 것으로 간주됩니다.
Daniel Spiewak

1
"최악의 경우"와 "암 스트 된"은 서로 다른 두 가지 유형의 시간 복잡성입니다. "가장 최악의 경우가 상각 될 수있다"고 말하는 것은 이치에 맞지 않습니다. 만약 최악의 경우 = 할부 상환을 할 수 있다면, 이것은 상당히 개선 될 것입니다. 평균화없이 최악의 경우에 대해서만 이야기합니다.
Tyler

나는 O (1) 최악의 경우가 O (1) 평균적인 경우와 O (n) 최악의 경우의 조합보다 "엄격히 우수하다"는 것이 무엇을 의미하는지 잘 모르겠습니다. 일정한 스케일링 요소가 중요합니다. N 개 항목을 포함하는 경우 N 마이크로 초의 시간에 N 개 작업 후에 재 포장해야 할 수도 있고, 그렇지 않으면 작업 당 1 마이크로 초가 걸리는 데이터 구조는 각 작업마다 밀리 초가 걸리는 것보다 훨씬 유용 할 수 있습니다. 데이터 크기가 수백만 개의 항목으로 확장 될 경우 (일부 개별 작업에 몇 초가 걸릴 수 있음)
supercat

8

큐를 q로 구현하고 q를 구현하는 데 사용 된 스택을 stack1 및 stack2로 둡니다.

q는 가지 방법 으로 구현 될 수 있습니다 .

방법 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 (deQueue 작업에서)는 요소를 한 번만 이동하고 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

큐에 두 스택은 다음과 같이 정의된다 stack1stack2 .

대기열에 추가 : 대기열에 추가 된 요소는 항상 stack1 로 푸시됩니다 .

Dequeue : stack2 가 비어 있지 않은 경우 queue2 가 큐에 삽입 된 첫 번째 요소이므로 stack2 의 상단 이 튀어 나올 수 있습니다 . 때 stack2가 비어 있습니다, 우리는 모든 요소 팝업 stack1를 하고로 밀어 stack2 하나 하나. 큐의 첫 번째 요소는 stack1 의 맨 아래로 푸시됩니다 . 스택 2 의 맨 위에 있기 때문에 팝핑 및 푸시 조작 직후에 팝 아웃 될 수 있습니다 .

다음은 동일한 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;
}

이 솔루션은 내 블로그 에서 빌 렸습니다 . 단계별 운영 시뮬레이션을 통한 자세한 분석은 내 블로그 웹 페이지에서 볼 수 있습니다.


2

맨 아래 요소를 얻으려면 첫 번째 스택에서 모든 것을 팝해야합니다. 그런 다음 모든 "dequeue"작업을 위해 두 번째 스택에 모두 다시 넣습니다.


3
그래, 너가 맞아. 당신이 얼마나 많은 투표를 받았는지 궁금합니다. 나는 당신의 대답을 upvoted 한
Binita 바라 티에게

이것이 그의 마지막 대답이고 그 이후 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에서 두 개의 스택을 사용하는 큐 구현 :

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

두 개의 스택으로 대기열을 구현하는 것과 관련된 많은 게시물을 얻을 수 있지만 : 1. enQueue 프로세스를 훨씬 더 비싸게 만들거나 2. deQueue 프로세스를 훨씬 더 비싸게함으로써

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

위의 게시물에서 찾은 중요한 한 가지 방법은 스택 데이터 구조와 재귀 호출 스택 만 사용하여 대기열을 구성하는 것입니다.

말 그대로 이것은 여전히 ​​두 개의 스택을 사용하고 있지만 이상적으로는 하나의 스택 데이터 구조 만 사용하는 것이 좋습니다.

아래는 문제에 대한 설명입니다.

  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 구문을 사용하는 자바 스크립트 솔루션입니다.

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"

이것은 버그가 있습니다. 대기열에서 제외 후 더 많은 요소를 대기열에 넣으면에 넣습니다 stack1. dequeue다시 가면 항목을로 이동하여 stack2이미 있던 항목 보다 우선합니다.
Alexander-복원 모니카

0

Go에는 표준 라이브러리에 많은 컬렉션이 없기 때문에 Go 에서이 질문에 대답하겠습니다.

스택은 실제로 구현하기 쉽기 때문에 두 번 스택을 사용하여 이중 종료 큐를 달성하려고 생각했습니다. 내 답변에 어떻게 도달했는지 이해하기 위해 구현을 두 부분으로 나누었습니다. 첫 번째 부분은 이해하기 쉽지만 불완전합니다.

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

기본적으로 스택의 하단을 서로 조작 할 수있는 두 개의 스택입니다. 또한 스택의 전통적인 푸시, 팝, 엿보기 작업이 대기열의 앞면 또는 뒷면을 참조하든 앞 / 뒤 접두사가있는 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

@melpomene OK, 자세히 살펴보면 내가 수행하는 유일한 작업은 배열의 마지막 요소를 추가 / 제거하는 것입니다. 다시 말해, 밀고 터지는 것입니다. 모든 의도와 목적을 위해 이들은 스택이지만 배열을 사용하여 구현됩니다.
John Leidegren

@melpomene 사실, 그것은 절반 밖에 안됩니다. 나는 두 배로 된 스택을 가정하고 있습니다. 특정 조건에서 스택을 비표준 방식으로 상향식으로 수정할 수 있습니다.
John Leidegren

0

다음은 linkedlist를 사용하는 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;
    }
}

}

참고 : 이 경우 팝 작업에는 시간이 많이 걸립니다. 따라서 두 스택을 사용하여 대기열을 만들 것을 제안하지 않습니다.


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의 맨 위에 추가합니다. 모든 대기열에서 스택 1의 내용을 스택 2에 비우고 스택의 맨 위에있는 요소를 제거합니다. 스택 1을 스택 2에 복사해야하므로 대기열에서 시간 복잡도는 O (n)입니다. 대기열의 시간 복잡도는 일반 스택과 동일


이 코드는 비효율적이며 (불필요한 복사) 끊어 졌습니다. 생성자에서 인스턴스화 if (stack2 != null)되기 때문에 항상 참 stack2입니다.
melpomene

-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의 답변과 동일합니다. 설명이 아니라 새로운 내용은 추가하지 않습니다.
melpomene

기본 예외 처리와 함께 isEmpty () 및 size () 메소드를 추가합니다. 설명을 추가하기 위해 편집하겠습니다.
realPK

1
: 아무도 그 여분의 방법을 요구하지 않으며, 그들은 (한 줄마다) 사소한 것 return inbox.isEmpty() && outbox.isEmpty()return inbox.size() + outbox.size()각각. 빈 대기열에서 대기열을 해제하면 Dave L.의 코드에서 이미 예외가 발생합니다. 원래 질문은 Java에 대한 것이 아닙니다. 그것은 일반적으로 데이터 구조 / 알고리즘에 관한 것입니다. Java 구현은 추가 설명 일뿐입니다.
melpomene

1
이것은 두 개의 스택에서 대기열을 작성하는 방법을 이해하려는 사람들에게 훌륭한 소스입니다. 다이어그램은 Dave의 답변을 읽는 것보다 더 도움이되었습니다.
Kemal Tezer Dilsiz

@melpomene : 사소한 방법이 아니라 필요합니다. Java의 큐 인터페이스는 이러한 메소드가 필요하므로 콜렉션 인터페이스에서 이러한 메소드를 확장합니다.
realPK
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.