두 개의 대기열을 사용하여 스택을 구현하는 요점은 무엇입니까?


34

다음과 같은 숙제가 있습니다.

두 개의 큐를 사용하여 스택 메소드 push (x) 및 pop ()을 구현하십시오.

이것은 나에게 이상하게 보입니다.

  • 스택 (LIFO) 대기열입니다
  • 구현하기 위해 두 개의 대기열이 필요한 이유를 모르겠습니다.

나는 주변을 검색했다 :

몇 가지 해결책을 찾았습니다. 이것이 내가 끝낸 것입니다.

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

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

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

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

        T popped = q1.pop();

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

        return popped;
    }
}

그러나 단일 대기열을 사용하는 것의 이점이 무엇인지 이해하지 못합니다. 두 대기열 버전은 무의미하게 복잡해 보입니다.

우리가 푸시를 2보다 효율적으로 선택하고 (위에서 한 것처럼) push동일하게 유지하고 pop단순히 마지막 요소를 반복하고 반환해야한다고 가정 해보십시오. 두 경우 모두는 pushO(1), 그리고이 pop될 것이다 O(n); 그러나 단일 대기열 버전은 훨씬 간단합니다. 단일 for 루프 만 필요합니다.

뭔가 빠졌습니까? 여기에 대한 통찰력이 있으면 감사하겠습니다.


17
대기열은 일반적으로 FIFO 구조를 참조하는 반면 스택은 LIFO 구조입니다. Java에서 LinkedList의 인터페이스는 FIFO 및 LIFO 액세스를 모두 허용하는 deque (double ended queue) 인터페이스입니다. LinkedList 구현이 아닌 Queue 인터페이스로 프로그래밍을 변경하십시오 .

12
더 일반적인 문제는 두 개의 스택을 사용하여 큐를 구현하는 것입니다. 순전히 기능적인 데이터 구조에 관한 Chris Okasaki의 책이 흥미로울 것입니다.
Eric Lippert 2016 년

2
Eric이 말한 것을 바탕으로, 때때로 스택 기반 언어로 자신을 찾을 수 있습니다 (예 : dc 또는 두 개의 스택이 있는 푸시 다운 오토 마톤 (터핑 머신과 동일하므로 더 많은 작업을 수행 할 수 있음)). 여러 스택이지만 대기열은 없습니다.

1
@MichaelT : 또는 당신은 또한 자신이 스택 기반의 CPU에서 실행 찾을 수 있습니다
slebetman

11
"스택은 (LIFO) 대기열입니다" ... 음, 대기열은 대기중인 회선입니다. 공중 화장실 사용 라인과 같습니다. 기다리는 줄이 LIFO 방식으로 동작합니까? "LIFO 대기열"이라는 용어 사용을 중지하십시오. 의미가 없습니다.
Mehrdad

답변:


44

장점은 없습니다 : 순전히 학업입니다.

매우 내가 신입생이 대학에있을 때 오래 전에 나는 유사한 운동했다 1 . for루프 카운터가있는 루프를 사용하여 반복 솔루션을 작성하는 대신 객체 지향 프로그래밍을 사용하여 알고리즘을 구현하는 방법을 학생들에게 가르치는 것이 목표였습니다 . 대신 기존 데이터 구조를 결합하고 재사용하여 목표를 달성하십시오.

Real World TM 에서는이 코드를 사용하지 않습니다 . 이 연습에서 제거해야 할 것은 "상자 밖에서 생각하고"코드를 재사용하는 방법입니다.


구현을 직접 사용하는 대신 코드에서 java.util.Queue 인터페이스 를 사용해야 합니다 .

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

이를 통해 Queue원하는 경우 다른 구현 을 사용할 수 있을뿐만 아니라 인터페이스 의 정신을 극복 할 수 있는 2 개의 메소드를 숨길 LinkedListQueue있습니다. 여기에는 get(int)pop()(코드를 컴파일하는 동안 할당의 제약이 주어진에서, 논리 오류가 있습니다. 귀하의 변수를 선언하는 Queue대신에 LinkedList그것을 발표 할 예정이다). 관련 읽기 : "인터페이스 프로그래밍"이해인터페이스가 유용한 이유는 무엇입니까?

1 여전히 기억합니다. 연습은 Stack 인터페이스의 메소드 사용 하고 java.util.Collections다른 "정적 전용"유틸리티 클래스 에는 유틸리티 메소드를 사용하지 않고 Stack 을 되 돌리는 것이 었습니다 . 올바른 솔루션은 다른 데이터 구조를 임시 보유 객체로 사용하는 것입니다. 다른 데이터 구조, 해당 속성 및이를 결합하는 방법을 알아야합니다. 이전에 프로그래밍 한 적이없는 대부분의 CS101 클래스를 발견했습니다.

2 메소드는 여전히 있지만 유형 캐스트 ​​나 리플렉션이 없으면 액세스 할 수 없습니다. 따라서 이러한 비큐 방법을 사용하는 것은 쉽지 않습니다 .


1
감사. 말이되는 것 같아요. 또한 위의 코드에서 "잘못된"연산을 사용하고 있다는 것을 깨달았지만 (FIFO의 앞쪽으로 밀림) 그게 아무것도 바뀌지 않는다고 생각합니다. 모든 작업을 취소했지만 여전히 의도 한대로 작동합니다. 다른 사람들이 의견을 제시하지 못하게하고 싶지 않기 때문에 동의하기 전에 조금 기다릴 것입니다. 그래도 고맙다.
Carcigenicate

19

이점이 없습니다. 대기열을 사용하여 스택을 구현하면 시간이 엄청나게 복잡해집니다. (유능한) 프로그래머는“실제 생활”에서 이와 같은 일을하지 않을 것입니다.

그러나 가능합니다. 하나의 추상화를 사용하여 다른 추상화를 구현할 수 있으며 그 반대도 가능합니다. 스택은 두 개의 대기열 측면에서 구현 될 수 있으며, 마찬가지로 두 개의 스택 측면에서 대기열을 구현할 수 있습니다. 이 연습 의 장점은 다음과 같습니다 .

  • 당신은 스택을 요약
  • 당신은 대기열을 요약
  • 알고리즘 사고에 익숙해집니다
  • 스택 관련 알고리즘을 배웁니다.
  • 알고리즘의 트레이드 오프에 대해 생각하게됩니다.
  • 대기열과 스택의 동등성을 실현함으로써 코스의 다양한 주제를 연결합니다
  • 실용적인 프로그래밍 경험을 얻습니다

실제로 이것은 훌륭한 운동입니다. 나는 지금 스스로해야한다 :)


3
@JohnKugelman 편집 해 주셔서 감사하지만 실제로 "끔찍한 시간 복잡성"을 의미했습니다. 링크 된리스트 기반 스택 push, peekpop조작은 O (1)에있다. 크기를 조정할 수있는 배열 기반 스택에 대해서도 동일하지만 pushO (n)이 상각되고 O (n)이 최악 인 경우를 제외하고 는 동일합니다 . 이에 비해 대기열 기반 스택은 O (n) 푸시, O (1) 팝 및 엿보기, 또는 O (1) 푸시, O (n) 팝 및 엿보기에 비해 열등합니다.
amon

1
"끔찍한 시간 복잡성"과 "대단히 열등한"은 정확히 맞지 않습니다. 상각 된 복잡성은 여전히 ​​푸시 팝에 대해 O (1)입니다 . TAOCP (vol1?)에는 재미있는 질문이 있습니다 (기본적으로 요소가 한 스택에서 다른 스택으로 전환 할 수있는 횟수가 일정하다는 것을 보여 주어야합니다). 하나의 작업에 대한 최악의 경우 성능은 다르지만 ArrayLists에서 푸시에 대한 O (N) 성능에 대해 이야기하는 사람은 거의 없습니다. 일반적으로 흥미로운 숫자 는 아닙니다 .
Voo

5

두 개의 스택으로 큐를 만드는 실제적인 목적이 있습니다. 기능적 언어에서 변경 불가능한 데이터 구조를 사용하는 경우 푸시 가능한 항목의 스택으로 푸시하고 팝업 가능한 항목 목록에서 가져올 수 있습니다. 팝 가능한 아이템은 모든 아이템이 튀어 나왔을 때 생성되며, 새로운 팝 가능한 스택은 푸시 가능한 스택의 반대이며, 이제 새로운 푸시 가능한 스택은 비어 있습니다. 효율적입니다.

두 개의 대기열로 구성된 스택은? 크고 빠른 대기열을 사용할 수있는 상황에서는 의미가있을 수 있습니다. 이런 종류의 자바 연습으로는 확실히 쓸모가 없습니다. 그러나 이것이 채널 또는 메시징 큐인 경우에는 의미가 있습니다. (즉 : 앞의 (N-1) 항목을 새 대기열로 이동하기 위해 O (1) 작업으로 N 개의 메시지가 대기열에 추가되었습니다.)


흠 .. 이것은 컴퓨팅의 기초로 시프트 레지스터를 사용하는 것과 벨트 / 밀 아키텍처에 대해 생각하게 만들었습니다.
slebetman

와우, 밀 CPU는 정말 흥미 롭습니다. 확실히 "큐 머신".
Rob

2

운동 실질적인 관점에서 불필요하게 고안되었습니다. 요점은 스택을 구현하기 위해 영리한 방식으로 큐의 인터페이스를 사용하도록하는 것입니다. 예를 들어 "One queue"솔루션을 사용하려면 스택 "pop"작업의 마지막 입력 값을 얻기 위해 큐를 반복해야합니다. 그러나 큐 데이터 구조는 값을 반복 할 수 없으므로 선입 선출 (FIFO)로 값에 액세스하도록 제한됩니다.


2

다른 사람들이 이미 언급했듯이 실제 이점은 없습니다.

어쨌든, 질문의 두 번째 부분에 대한 하나의 대답, 단순히 하나의 큐를 사용하지 않는 이유는 Java를 넘어서는 것입니다.

Java에서도 Queue인터페이스에는 size()메소드 가 있으며 해당 메소드의 모든 표준 구현은 O (1)입니다.

C / C ++ 프로그래머가 구현할 때 순진 / 정식 연결 목록에 반드시 해당되는 것은 아니며, 첫 번째 요소와 마지막 요소에 대한 포인터를 유지하고 각 요소는 다음 요소에 대한 포인터를 유지합니다.

이 경우 size()O (n)이며 루프에서는 피해야합니다. 아니면 구현이 불투명 만 최소한을 제공 add()하고 remove().

이러한 구현을 통해 먼저 요소 수를 두 번째 큐로 전송하여 요소 수를 세고 요소를 첫 번째 큐로 n-1다시 전송 한 다음 나머지 요소를 반환해야합니다.

즉, Java-land에 살면 아마도 이와 같은 것을 구성하지 않을 것입니다.


size()방법 에 대한 좋은 지적 . 그러나 O (1) size()메서드 가 없으면 스택이 현재 크기 자체를 추적하는 것이 쉽지 않습니다. 하나의 큐 구현을 막을 수있는 것은 없습니다.
cmaster

그것은 모두 구현에 달려 있습니다. 순방향 포인터와 첫 번째 요소와 마지막 요소에 대한 포인터로만 구현 된 대기열이있는 경우 여전히 요소를 제거하고 로컬 변수에 저장하고 해당 변수의 이전 요소를 동일한 대기열에 동일한 대기열에 추가하는 알고리즘을 작성할 수 있습니다 첫 번째 요소가 다시 나타납니다. 이것은 동일한 값을 가진 것이 아닌 요소를 고유하게 식별 할 수있는 경우에만 작동합니다 (예 : 포인터를 통해). O (n)이며 add () 및 remove () 만 사용합니다. 어쨌든 알고리즘에 대한 생각을 제외하고는 실제로 그렇게하는 이유를 찾는 것이 최적화하기가 더 쉽습니다.
Thraidh

0

그러한 구현에 대한 사용을 상상하기는 어렵지만 사실입니다. 그러나 대부분의 요점은 그것이 가능하다는 것을 증명하는 것 입니다.

그러나 이러한 것들에 대한 실제 사용의 관점에서, 나는 두 가지를 생각할 수 있습니다. 이를 위해 한 가지 용도는이를 위해 설계되지 않은 제한된 환경에서 시스템을 구현하는 것입니다 . 예를 들어, Minecraft의 레드 스톤 블록 은 사람들이 로직 회로 및 전체 CPU를 구현하는 데 사용되는 Turing-complete 시스템을 나타냅니다. 스크립트 기반 게임의 초기에는 많은 최초의 게임 봇도 이런 식으로 구현되었습니다.

그러나이 원칙을 반대로 적용하여 원하지 않는 시스템 에서는 불가능한 것을 보장 할 수 있습니다 . 예를 들어 강력한 구성 시스템은 자산이 될 수 있지만 여전히 사용자에게 제공하지 않을 정도의 전력이 있습니다. 이로 인해 공격자가 구성 언어를 훼손하지 않도록 구성 언어로 할 수있는 작업이 제한되지만이 경우에는 원하는 것입니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.