[algorithm] 3 개의 스택으로 큐를 구현하는 방법은 무엇입니까?

나는 알고리즘 책 ( Robert Sedgewick과 Kevin Wayne의 Algorithms, 4th Edition) 에서이 질문을 보았습니다.

3 개의 스택이있는 큐 각 대기열 작업이 일정한 수의 스택 작업을 수행하도록 3 개의 스택으로 대기열을 구현하십시오. 경고 : 난이도가 높습니다.

2 스택으로 대기열을 만드는 방법을 알고 있지만 3 스택으로 솔루션을 찾을 수 없습니다. 어떤 아이디어?

(오, 이것은 숙제가 아닙니다 :))



답변

요약

  • 6 개의 스택으로 알려진 O (1) 알고리즘
  • O (1) 알고리즘은 3 개의 스택으로 알려져 있지만 실제로 추가 내부 데이터 구조를 갖는 지연 평가를 사용하므로 솔루션을 구성하지 않습니다.
  • Sedgewick 근처의 사람들은 원래 질문의 모든 제약 조건 내에서 3- 스택 솔루션을 인식하지 못한다고 확인했습니다.

세부

이 링크 뒤에는 두 가지 구현이 있습니다. http://www.eecs.usma.edu/webs/people/okasaki/jfp95/index.html

그중 하나는 3 개의 스택이있는 O (1)이지만 지연 실행을 사용하여 실제로 추가 중간 데이터 구조 (클로저)를 만듭니다.

다른 하나는 O (1)이지만 SIX 스택을 사용합니다. 그러나 지연 실행없이 작동합니다.

업데이트 : 오카 사키의 논문은 여기에 있습니다 : http://www.eecs.usma.edu/webs/people/okasaki/jfp95.ps 그리고 그는 게으른 평가가있는 O (1) 버전에 실제로 2 개의 스택 만 사용하는 것으로 보입니다. 문제는 실제로 게으른 평가에 기반한다는 것입니다. 문제는 지연 평가없이 3 스택 알고리즘으로 변환 할 수 있는지입니다.

업데이트 : 또 다른 관련 알고리즘은 컴퓨팅 및 조합에 관한 제 7 차 연례 회의에서 출판 된 Holger Petersen의 논문 “Stacks vs Deques”에 설명되어 있습니다. Google 도서에서 기사를 찾을 수 있습니다. 225-226 페이지를 확인하십시오. 그러나이 알고리즘은 실제로 실시간 시뮬레이션이 아니라 3 개의 스택에서 이중 엔드 큐의 선형 시뮬레이션입니다.

gusbro : “@Leonel이 며칠 전에 말했듯이, 솔루션을 알고 있거나 실수가 있었다면 Sedgewick 교수에게 확인하는 것이 공정하다고 생각했습니다. 그래서 나는 그에게 편지를 보냈습니다. 자신을 제외하고 프린스턴의 동료로부터)) 나는 여러분 모두와 공유하고 싶습니다. 그는 기본적으로 그는 3 개의 스택을 사용하는 알고리즘은없고 다른 평가 (게으른 평가를 사용하지 않는 것)를 알고 있다고 알고있었습니다. 우리가 이미 답을보고있는 것으로 알고있는 6 개의 스택입니다. 따라서 알고리즘을 찾기 위해 질문이 아직 열려있는 것 같습니다.


답변

좋아, 이것은 정말로 어렵고, 내가 올 수있는 유일한 해결책은 Kobayashi Maru 테스트에 대한 Kirks 솔루션을 기억합니다 (어쨌든 속임수) : 아이디어는 스택 스택을 사용하고 목록을 모델링하는 데 사용한다는 것입니다 ). 나는 작업을 en / dequeue라고하고 밀어서 팝하면 다음과 같이됩니다.

queue.new() : Stack1 = Stack.new(<Stack>);
              Stack2 = Stack1;

enqueue(element): Stack3 = Stack.new(<TypeOf(element)>);
                  Stack3.push(element);
                  Stack2.push(Stack3);
                  Stack3 = Stack.new(<Stack>);
                  Stack2.push(Stack3);
                  Stack2 = Stack3;

dequeue(): Stack3 = Stack1.pop();
           Stack1 = Stack1.pop();
           dequeue() = Stack1.pop()
           Stack1 = Stack3;

isEmtpy(): Stack1.isEmpty();

(StackX = StackY는 내용의 복사가 아니라 참조의 사본 일뿐입니다. 쉽게 설명 할 수 있습니다. 또한 3 개의 스택 배열을 사용하고 인덱스를 통해 액세스 할 수 있습니다. 인덱스 변수의 값만 변경하면됩니다. ). 모든 것은 스택 작업 조건에서 O (1)에 있습니다.

그리고 네, 우리는 3 개 이상의 스택을 암시하기 때문에 논쟁의 여지가 있음을 알고 있지만 다른 사람들에게 좋은 아이디어를 줄 수 있습니다.

편집 : 설명 예 :

 | | | |3| | | |
 | | | |_| | | |
 | | |_____| | |
 | |         | |
 | |   |2|   | |
 | |   |_|   | |
 | |_________| |
 |             |
 |     |1|     |
 |     |_|     |
 |_____________|

Stack1을 보여주기 위해 작은 ASCII 아트로 여기에서 시도했습니다.

모든 요소는 단일 요소 스택으로 래핑됩니다 (따라서 형식이 안전한 스택 스택 만 있음).

먼저 첫 번째 요소 (여기서 요소 1과 2를 포함하는 스택)를 제거합니다. 그런 다음 다음 요소를 팝하고 1을 풉니 다. 이후 첫 번째 팝 스택이 이제 새 Stack1이라고 말합니다. 좀 더 기능적으로 말하면, 이들은 최상위 요소가 cdr 이고 첫 번째 / 아래 최상위 요소가 car 인 두 요소의 스택으로 구현 된 목록 입니다. 다른 2 개는 스택을 돕고 있습니다.

Esp tricky는 삽입입니다. 어떻게 든 다른 요소를 추가하기 위해 중첩 된 스택에 깊숙이 들어가야합니다. 그래서 Stack2가 있습니다. Stack2는 항상 가장 안쪽 스택입니다. 추가는 요소를 밀어 넣은 다음 새 Stack2를 맨 위에 밀어 넣는 것입니다 (따라서 우리는 대기열 제거 작업에서 Stack2를 만질 수 없습니다).


답변

나는 그것을 할 수 없다는 것을 증명하는 증거를 시도 할 것입니다.


3 개의 스택 A, B 및 C로 시뮬레이션되는 큐 Q가 있다고 가정하십시오.

주장

  • ASRT0 : = 또한 Q가 O (1)에서 {queue, dequeue} 연산을 시뮬레이션 할 수 있다고 가정합니다. 이는 시뮬레이션 할 모든 큐 / 큐 작업에 대해 특정 스택 푸시 / 팝 시퀀스가 ​​있음을 의미합니다.

  • 일반성이 손실되지 않으면 큐 작업이 결정적이라고 가정하십시오.

큐에 대기중인 요소는 큐 순서에 따라 1, 2, …로 번호가 매겨지며 Q에 대기중인 첫 번째 요소는 1로 정의되고 두 번째 요소는 2로 정의됩니다.

밝히다

  • Q(0) := Q에 0 개의 원소가있을 때 Q의 상태 (따라서 A, B 및 C에 0 개의 원소)
  • Q(1) := 큐 작업 1 회 후 Q (및 A, B 및 C)의 상태 Q(0)
  • Q(n) := n 큐 작업 후 Q 상태 (및 A, B 및 C) Q(0)

밝히다

  • |Q(n)| :=Q(n)(따라서 |Q(n)| = n) 의 요소 수
  • A(n) := Q 상태가 스택 A 인 경우의 상태 Q(n)
  • |A(n)| := 요소 수 A(n)

스택 B와 C에 대한 비슷한 정의.

사소한,

|Q(n)| = |A(n)| + |B(n)| + |C(n)|

---

|Q(n)| n에 분명히 제한이 없습니다.

따라서, 적어도 하나 |A(n)|, |B(n)|또는 |C(n)|N에 무제한이다.

WLOG1스택 A가 제한되지 않고 스택 B와 C가 제한된다고 가정합니다.

정의 * B_u :=B C_u :=의 상한 * C의 상한 *K := B_u + C_u + 1

WLOG2n에 대해서는 |A(n)| > K에서 K 개의 요소를 선택하십시오 Q(n). 이러한 요소 중 하나가 A(n + x)모두 에 있다고 가정 하십시오 x >= 0. 즉, 큐 조작이 얼마나 많이 수행 되든지 요소가 항상 스택 A에 있다고 가정하십시오 .

  • X := 그 요소

그런 다음 정의 할 수 있습니다

  • Abv(n) :=A(n)X보다 큰 스택의 항목 수
  • Blo(n) :=A(n)X보다 작은 스택의 요소 수

    | A (n) | = Abv (n) + Blo (n)

ASRT1 :=X를 대기열에서 빼는 데 필요한 팝 수는 Q(n)최소한Abv(n)

( ASRT0) 및 ( ASRT1)에서 ASRT2 := Abv(n)경계를 정해야합니다.

경우에 Abv(n)제한은 없습니다에서, 다음 20 대기열 해제는 디큐 X에 필요한 경우 Q(n)는 적어도이 필요합니다, Abv(n)/20아빠. 끝이 없습니다. 20은 상수 일 수 있습니다.

따라서,

ASRT3 := Blo(n) = |A(n)| - Abv(n)

제한이 없어야합니다.


WLOG3우리의 바닥에서 K 요소를 선택할 수 있습니다 A(n), 그리고 그들 중 하나에 A(n + x)모든x >= 0

X(n) := 주어진 n에 대한 해당 요소

ASRT4 := Abv(n) >= |A(n)| - K

요소가 대기열에 들어갈 때마다 Q(n)

WLOG4B와 C가 이미 상한으로 채워져 있다고 가정합니다. 위 요소의 상한 X(n)에 도달 했다고 가정하십시오 . 그런 다음 새 요소가 A로 들어갑니다.

WLOG5결과적으로 새 요소가 아래에 입력되어야한다고 가정합니다 X(n).

ASRT5 := 요소를 아래에 배치하는 데 필요한 팝 수 X(n) >= Abv(X(n))

에서 (ASRT4), Abv(n)N에 제한은 없습니다.

따라서 요소를 아래에 배치하는 데 필요한 팝 수 X(n)는 제한이 없습니다.


이로 ASRT1인해 O(1)3 개의 스택으로 큐 를 시뮬레이션 할 수 없습니다 .


하나 이상의 스택이 제한되지 않아야합니다.

해당 스택에있는 요소의 경우, 그 위의 요소 수를 바인드해야합니다. 그렇지 않으면 해당 요소를 제거하는 큐 제거 조작이 바인드되지 않습니다.

그러나 그 위의 요소 수가 제한되면 한계에 도달합니다. 어떤 시점에서 새로운 요소가 그 아래에 입력되어야합니다.

우리는 항상 스택의 가장 적은 수의 요소 중 하나에서 오래된 요소를 선택할 수 있기 때문에 그 위에는 무한한 수의 요소가있을 수 있습니다 (무제한 스택의 크기에 제한 없음).

그 아래에 새로운 요소를 입력하려면, 그 위에 무한한 수의 요소가 있으므로, 새로운 요소를 그 아래에 놓으려면 무한한 수의 팝이 필요합니다.

따라서 모순.


5 개의 WLOG (일반성을 잃지 않고) 진술이 있습니다. 어떤 의미에서, 그들은 직관적 인 것으로 직관적으로 이해 될 수 있습니다 (그러나 그들이 5 인 경우에는 시간이 걸릴 수 있습니다). 일반성이 손실되지 않는다는 공식적인 증거는 도출 될 수 있지만 매우 길다. 생략되었습니다.

그러한 누락이 WLOG 문을 남길 수 있음을 인정합니다. 버그에 대한 프로그래머의 편집증과 함께 원하는 경우 WLOG 문을 확인하십시오.

세 번째 스택도 관련이 없습니다. 중요한 것은 묶인 스택 세트와 묶이지 않은 스택 세트가 있다는 것입니다. 예제에 필요한 최소값은 2 스택입니다. 스택 수는 물론 유한해야합니다.

마지막으로 증거가 없다는 것이 옳다면, 더 쉬운 귀납적 증거가 있어야합니다. 아마도 모든 대기열 이후에 발생하는 상황을 기반으로 할 것입니다 (대기열의 모든 요소 집합이 주어지면 최악의 대기열 제외 사례에 어떻게 영향을 미치는지 추적하십시오).


답변

참고 : 이는 단일 링크 목록을 사용하여 실시간 (정시 최악의 경우) 큐의 기능 구현에 대한 주석입니다. 평판 때문에 의견을 추가 할 수는 없지만 누군가 antti.huima의 답변에 추가 된 의견으로 변경할 수 있다면 좋을 것입니다. 다시 말하지만, 의견이 다소 길다.

@ antti.huima : 연결된리스트는 스택과 다릅니다.

  • s1 = (1 2 3 4) — 각각 오른쪽에있는 노드를 가리키고 값 1, 2, 3 및 4를 보유하는 4 개의 노드가있는 링크 된 목록

  • s2 = 팝 (s1) — s2는 이제 (2 3 4)

이 시점에서 s2는 스택처럼 동작하는 popped (s1)와 같습니다. 그러나 s1은 여전히 ​​참조 가능합니다!

  • s3 = popped (popped (s1)) — s3은 (3 4)

우리는 여전히 s1을 들여다보고 1을 얻는 반면, 적절한 스택 구현에서는 요소 1이 s1에서 사라졌습니다!

이것은 무엇을 의미 하는가?

  • s1은 스택 상단에 대한 참조입니다.
  • s2는 스택의 두 번째 요소에 대한 참조입니다.
  • s3은 세 번째에 대한 참조입니다 …

이제 생성 된 추가 링크 목록은 각각 참조 / 포인터 역할을합니다! 유한 한 수의 스택은 그렇게 할 수 없습니다.

논문 / 코드에서 볼 수 있듯이 알고리즘은 모두 연결된 목록 의이 속성을 사용하여 참조를 유지합니다.

편집 : 2와 3의 링크 목록 알고리즘 만 참조합니다. 링크 목록을 처음 읽었을 때이 속성을 사용합니다 (더 간단하게 보임). 이는 링크 된 목록이 반드시 동일하지는 않다는 것을 설명하기 위해 해당하거나 적용 할 수 없음을 나타 내기위한 것이 아닙니다. 나는 자유로울 때 6으로 읽습니다. @ 웰 보그 : 수정 주셔서 감사합니다.


게으름도 비슷한 방식으로 포인터 기능을 시뮬레이션 할 수 있습니다.


연결 목록을 사용하면 다른 문제가 해결됩니다. 이 전략은 Lisp에서 실시간 대기열을 구현하는 데 사용할 수 있습니다 (또는 링크 된 목록에서 모든 것을 구축해야하는 Lisp). “순수한 Lisp의 실시간 대기열 작업”(anti.huima의 링크를 통해 연결됨)을 참조하십시오. 또한 O (1) 작업 시간과 공유 (불변) 구조로 불변 목록을 디자인하는 좋은 방법입니다.


답변

두 개의 스택으로 상각 된 일정한 시간에 할 수 있습니다.

------------- --------------
            | |
------------- --------------

추가 O(1) 하고 제거하는 것은 O(1)가져 가려는면이 비어 있지 O(n)않은 경우입니다 (다른 스택을 두 개로 나눕니다).

비결은 O(n) 매번 O(n)(예를 들어 반으로 나누면) 매번 작업이 수행되는 것 입니다. 따라서 작업의 평균 시간은 O(1)+O(n)/O(n) = O(1)입니다.

이것이 문제처럼 보일 수 있지만 배열 기반 스택 (가장 빠른)으로 명령형 언어를 사용하는 경우 어쨌든 일정한 시간 만 상각하게됩니다.


답변