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


136

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

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

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

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


30
하노이 타워 변형 이라고 생각합니다 .
Gumbo

14
@ 제이슨 : 그 질문은 중복되지 않습니다. 왜냐하면 O (1) 상각 시간을 요구하는 반면 각 질문에 대해 O (1) 최악의 경우를 요구하기 때문입니다. DuoSRX의 2- 스택 솔루션은 이미 작업 당 O (1)의 상각 시간입니다.
interjay April

15
저자는 "경고 : 고난이도"라고 말했을 때 농담이 아니었다.
BoltClock

9
@Gumbo 불행히도 하노이 타워의 시간 복잡도는 거의 일정하지 않습니다!
prusswan

12
참고 : 텍스트의 질문이 다음 같이 업데이트되었습니다. 각 대기열 작업이 일정한 (최악의 경우) 스택 작업 수를 갖도록 일정 개수의 스택 ( "3"아님) 으로 대기열을 구현하십시오 . 경고 : 난이도가 높습니다. ( algs4.cs.princeton.edu/13stacks- 섹션 1.3.43). Sedgewick 교수는 원래의 도전을 인정한 것 같습니다.
Mark Peters

답변:


44

요약

  • 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 개의 스택입니다. 따라서 알고리즘을 찾기 위해 질문이 아직 열려있는 것 같습니다.


나는 단지 당신의 링크에있는 논문과 프로그램을 통해 날았습니다. 그러나 내가 올바르게 보면 스택을 사용하지 않으면 목록을 기본 유형으로 사용합니다. 그리고 특히. 이 목록에서 헤더와 휴식으로 구성되므로 기본적으로 내 솔루션과 비슷하게 보입니다 (그리고 나는 옳지 않다고 생각합니다).
flolo

1
안녕, 구현은 포인터가 공유되지 않는 한리스트가 스택에 대응하고 기능하지 않는 기능적인 언어로되어있다. 6 스택 버전은 실제로 6 개의 "일반"스택을 사용하여 구현할 수 있습니다. 2/3 스택 버전의 문제점은 숨겨진 데이터 구조 (클로저)를 사용한다는 것입니다.
Antti Huima

6 스택 솔루션이 포인터를 공유하지 않습니까? 에서은 rotate, 그것은과 같은 front목록이 모두 할당됩니다 oldfront하고 f,이 후 개별적으로 수정됩니다.
interjay

14
algs4.cs.princeton.edu/13stacks 의 소스 자료가 다음과 같이 변경되었습니다. 43. 각 "3"이 아닌 일정한 수의 스택 으로 대기열을 구현하여 각 대기열 작업이 일정한 (최악의) 스택 수를 갖도록하십시오. 작업. 경고 : 난이도가 높습니다. 도전 과제의 제목에는 여전히 "3 개의 스택이있는 큐"가 표시됩니다.
Mark Peters

3
@AnttiHuima 6 스택 링크가 작동하지 않습니다. 이것이 어딘가에 있는지 알고 있습니까?
Quentin Pradet

12

좋아, 이것은 정말로 어렵고, 내가 올 수있는 유일한 해결책은 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를 만질 수 없습니다).


작동 방식을 설명 하시겠습니까? 어쩌면 'A', 'B', 'C', 'D'를 누른 다음 4 번 터지는 걸까요?
MAK

1
@ Iceman : No Stack2가 맞습니다. Stack은 항상 Stack1의 가장 안쪽 스택을 참조하므로 손실되지 않습니다. 따라서 여전히 Stack1에 내재되어 있습니다.
flolo

3
나는 그것이 속이는 것에 동의합니다 :-). 그것은 3 개의 스택이 아니며 3 개의 스택 참조입니다. 그러나 즐거운 읽을 거리.
Mark Peters

1
영리한 체계이지만 올바르게 이해하면 대기열에 n 개의 요소가 푸시 될 때 n 개의 스택이 필요합니다. 이 질문은 정확히 3 개의 스택을 요구합니다.
MAK

2
@MAK : 나는 그것이 속임수라고 명시 적으로 언급 한 이유를 알고 있습니다 (실제 솔루션에 대해 궁금하기 때문에 현상금으로 명성을 얻었습니다). 그러나 적어도 prusswan 의견은 대답 할 수 있습니다 : 스택의 수는 중요합니다. 원하는만큼 사용할 수있을 때 내 솔루션이 실제로 유효하기 때문입니다.
flolo

4

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


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 스택입니다. 스택 수는 물론 유한해야합니다.

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


2
증거가 그러한 가정에 효과적이라고 생각하지만 대기열이 비어 있기 위해서는 모든 스택이 비어 있어야하거나 스택 크기의 합계가 대기열 크기와 같아야한다고 확신하지 않습니다.
Mikeb

3
"WLOG1, 스택 A가 제한되지 않고 스택 B와 C가 제한되어 있다고 가정합니다." 스택의 일부가 제한되어 있다고 가정 할 수는 없습니다 (따라서 추가 스토리지 O (1)와 동일 함).
interjay

3
때로는 사소한 것들이 그다지 사소하지 않습니다. | Q | = | A | + | B | + | C | Q의 모든 항목에 대해 A, B 또는 C에 정확히 하나를 추가한다고 가정하면 알고리즘이 항상 두 스택 또는 두 스택에 요소를 두 번 추가하는 알고리즘 일 수 있습니다. 이 방법으로 작동하면 WLOG1이 더 이상 보유하지 않습니다 (예 : C에 A의 사본이 있다고 상상해보십시오 (어떤 의미는 없지만 다른 순서 또는 다른 방식으로 알고리즘이있을 수 있음)
flolo

@flolo와 @mikeb : 둘 다 맞습니다. | Q (n) | | A (n) | + | B (n) | + | C (n) |로 정의해야합니다. 그리고 | Q (n) | > = n. 결과적으로 증명은 n과 함께 작동하며 | Q (n) | 더 큰 결론은 적용됩니다.
Dingfeng Quek

@interjay : 3 개의 무제한 스택과 무제한 스택을 가질 수 있습니다. 그런 다음 "B_u + C_u + 1"대신 증명은 "1"만 사용할 수 있습니다. 기본적으로이 표현은 "바운드 스택의 상한의 합 + 1"을 나타내므로 바인딩 된 스택의 수는 중요하지 않습니다.
Dingfeng Quek

3

참고 : 이는 단일 링크 목록을 사용하여 실시간 (정시 최악의 경우) 큐의 기능 구현에 대한 주석입니다. 평판 때문에 의견을 추가 할 수는 없지만 누군가 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) 작업 시간과 공유 (불변) 구조로 불변 목록을 디자인하는 좋은 방법입니다.


1
antti의 답변으로 다른 알고리즘과 대화 할 수는 없지만 6 스택 상수 시간 솔루션 ( eecs.usma.edu/webs/people/okasaki/jfp95/queue.hm.sml ) 은이 목록의 속성을 사용하지 않습니다 , java.util.Stack객체를 사용하여 Java로 다시 구현 했으므로. 이 기능이 사용되는 유일한 곳은 불변 스택을 일정 시간에 "중복"할 수있는 최적화입니다. 기본 Java 스택은 변경 가능한 구조이기 때문에 수행 할 수 없습니다 (그러나 Java로 구현할 수 있음).
Welbog

계산 복잡성을 줄이지 않는 최적화라면 결론에 영향을 미치지 않아야합니다. 마침내 해결책을 찾아서 다행입니다. 이제 SML을 읽는 것을 좋아하지 않습니다. 자바 코드를 공유하고 싶으십니까? (:
Dingfeng Quek

불행히도 3 대신 6 스택을 사용하므로 최종 솔루션이 아닙니다. 그러나 6 개의 스택이 최소한의 솔루션임을 증명할 수 있습니다.
Welbog

@ 웰 보그! 6 스택 구현을 공유 할 수 있습니까? :) 그것을 보는 것은 시원 할 것이다 :)
Antti Huima

1

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

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

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

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

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


원래 질문에 관해서 : 스택을 분할하려면 실제로 추가 중간 스택이 필요합니다. 이것이 작업에 3 개의 스택이 포함 된 이유 일 수 있습니다.
Thomas Ahle
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.