하나의 스택, 두 개의 큐


59

배경

몇 년 전, 제가 학부생이었을 때, 상각 된 분석에 관한 숙제를 받았습니다. 문제 중 하나를 해결할 수 없었습니다. 나는 comp.theory 에서 그것을 요구 했지만 만족스러운 결과가 나오지 않았다. 나는 TA가 자신이 증명할 수 없었던 것을 고집하고 그 증거를 잊어 버렸다고 말한 것을 기억한다.

오늘 나는 그 문제를 회상했다. 나는 아직도 알고 싶어서 여기 있었는데 ...

질문

PUSHPOP 작업이 상각 된 시간 O (1) 에서 실행 되도록 두 개의 큐를 사용하여 스택 을 구현할 수 있습니까? 그렇다면 어떻게 말해 줄 수 있습니까?

참고 : 우리가 구현하려는 경우 상황은 매우 쉽습니다 와 함께 두 스택 (해당 작업에 ENQUEUEDEQUEUE ). 차이점을 관찰하십시오.

추신 : 위의 문제는 숙제 자체가 아닙니다. 숙제에는 하한이 필요하지 않았습니다. 구현 및 실행 시간 분석 만 가능합니다.


2
두 대기열 (O (1) 또는 O (log n)) 이외의 제한된 공간 만 사용할 수 있다고 생각합니다. 긴 입력 스트림의 순서를 바꿀 방법이 없기 때문에 불가능합니다. 그러나 이것은 엄격한 주장으로 만들어 질 수 없다면 증거는 아니다.…
Tsuyoshi Ito

@ 쓰요시 : 당신은 제한된 공간 가정에 대해 맞습니다. 그리고 그렇습니다. 제가 그 (고집
스러운

2
@ 츠요시 : 일반적으로 공간에 바운드 할 필요는 없다고 생각합니다. 스택에서 밀고 튀어 나온 객체를 두 개의 대기열 이외의 장소에 저장할 수 없다고 가정하면됩니다 (아마도 상수의 변수).
Kaveh

@SadeqDousti 제 생각에, 이것이 가능할 수있는 유일한 방법은 큐의 링크 된 목록 구현을 사용하고 항상 "스택"의 상단을 가리키는 포인터를 사용하는 경우입니다
Charles Addis

2
TA가 실제로 "O (1) 할부 상환 시간"에서 가능한 "두 스택을 사용하여 큐 구현"이라고 말하고 싶었던 것 같습니다.
Thomas Ahle

답변:


45

실제 답변이 없지만 문제가 열려 있다는 증거가 있습니다.

  • Ming Li, Luc Longpré 및 Paul MB Vitányi, "대기열의 힘", Structures 1986에는 언급되지 않은 여러 다른 밀접한 시뮬레이션이 있습니다.

  • Martin Hühne에서는 "여러 줄의 힘으로"언급되지 않았습니다. Comp. 공상 과학 1993 년, 후속 논문.

  • COCOON 2001의 Holger Petersen, "Stacks vs Deques"에는 언급되어 있지 않습니다.

  • Burton Rosenberg, "두 개의 대기열을 사용하여 문맥없는 언어를 신속하고 비결정론 적으로 인식", Inform. Proc. 레트 사람. 1998, 두 개의 큐를 사용하여 모든 CFL을 인식하기위한 O (n log n) 2- 큐 알고리즘을 제공합니다. 그러나 비 결정적 푸시 다운 오토 마톤은 선형 시간으로 CFL을 인식 할 수 있습니다. 따라서 작업 당 O (log n)보다 2 개의 대기열이 빠른 스택의 시뮬레이션이 있었다면 Rosenberg와 그의 심판원이 이에 대해 알고 있어야합니다.


4
훌륭한 참조를 위해 +1 그러나 일부 기술은 다음과 같습니다. 첫 번째 논문과 같은 일부 논문은 두 개의 대기열을 사용하여 하나의 스택을 시뮬레이트하는 문제를 고려하지 않습니다. 다른 사람들은 상각 비용이 아니라 최악의 경우를 고려합니다.
MS Dousti

13

아래의 대답은 '속임수'입니다. 작업 간 공간을 사용하지 않지만 작업 자체는 이상의 공간을 사용할 수 있습니다 . 이 스레드의 다른 곳에서이 문제가없는 답변을 참조하십시오.O(1)

정확한 질문에 대한 답변이 없지만 O 에서 작동하는 알고리즘을 찾았습니다 ( O(n)대신에 n )시간. 나는 증거가 없지만 이것이 빡빡하다고 생각합니다. 어쨌든, 알고리즘은O(n)의 하한을 증명하려고 시도하는것은 쓸데없는 일이므로 귀하의 질문에 대답하는 데 도움이 될 수 있습니다.O(n)O(n)O(n)

나는 두 개의 알고리즘을 제시하는데, 첫 번째는 Pop에 대한 실행 시간을 가진 간단한 알고리즘이고 두 번째 알고리즘 은 O ( O(n)Pop의 실행 시간 첫 번째는 주로 단순성 때문에 두 번째 것이 이해하기 쉽도록 설명합니다.O(n)

더 자세하게 설명하자면, 첫 번째는 추가 공간을 사용하지 않고 최악의 경우 (및 상각) 푸시와 O ( n ) 최악의 경우 (및 상각) 팝을 갖지만 최악의 경우 항상 트리거되는 것은 아닙니다. 두 대기열 이상의 추가 공간을 사용하지 않기 때문에 Ross Snider가 제공하는 솔루션보다 약간 더 낫습니다.O(1)O(n)

두 번째는 단일 정수 필드 (따라서 여분의 공간)를 사용하고 O ( 1 ) 최악의 경우 (및 상각) 푸시 및 O ( O(1)O(1)팝을 상각했다. 따라서 '간단한'접근 방식보다 실행 시간이 훨씬 뛰어나지 만 추가 공간을 사용합니다.O(n)

첫 번째 알고리즘

우리는 queue 와 queue s e c o n d 두 개의 대기열을 가지고 있습니다 . F r에 t는 동안, 우리 '푸시 큐'것 S E C O N에서 D가 이미 '스택 주문에 대기열 될 것이다.firstsecondfirstsecond

  • 단순히 상 매개 변수가 대기 상태에서 행해진 다 밀어 .first
  • 팝핑은 다음과 같이 수행됩니다. 경우 , 우리는 단순히 디큐 비어 E C O N에서 D , 그 결과를 반환한다. 그렇지 않으면, 우리는 역방향 f를 r에 S t , 모든 추가 C O N의 D를f를 r에 S를 t 와 스왑 f를 r에 S를 tS의 E C의 입출력 N 개의 D를 . 그런 다음 s e c ofirstsecondfirstsecondfirstfirstsecond 및 대기열에서 제외 된 결과를 반환합니다.second

첫 번째 알고리즘의 C # 코드

C #을 본 적이 없어도 읽을 수 있어야합니다. 제네릭이 무엇인지 모르는 경우 문자열 스택에 대해 'T'의 모든 인스턴스를 'string'으로 바꾸십시오.

public class Stack<T> {
    private Queue<T> first = new Queue<T>();
    private Queue<T> second = new Queue<T>();
    public void Push(T value) {
        first.Enqueue(value);
    }
    public T Pop() {
        if (first.Count == 0) {
            if (second.Count > 0)
                return second.Dequeue();
            else
                throw new InvalidOperationException("Empty stack.");
        } else {
            int nrOfItemsInFirst = first.Count;
            T[] reverser = new T[nrOfItemsInFirst];

            // Reverse first
            for (int i = 0; i < nrOfItemsInFirst; i++)
                reverser[i] = first.Dequeue();    
            for (int i = nrOfItemsInFirst - 1; i >= 0; i--)
                first.Enqueue(reverser[i]);

            // Append second to first
            while (second.Count > 0)
                first.Enqueue(second.Dequeue());

            // Swap first and second
            Queue<T> temp = first; first = second; second = temp;

            return second.Dequeue();
        }
    }
}

분석

분명히 Push는 시간 안에 작동합니다 . 팝 안에 모든 터치 수 f를 r에 S를 tS의 E C의 입출력 N 개의 D를 회 일정량 그래서 우리가 O ( N를 ) 최악의 경우. 알고리즘은 예를 들어 n 개의 요소를 스택에 푸시 한 다음 단일 푸시와 단일 팝 작업을 연속으로 반복 수행하는 경우이 동작을 나타냅니다 .O(1)firstsecondO(n)n

두 번째 알고리즘

우리는 queue 와 queue s e c o n d 두 개의 대기열을 가지고 있습니다 . F r에 t는 동안, 우리 '푸시 큐'것 S E C O N에서 D가 이미 '스택 주문에 대기열 될 것이다.firstsecondfirstsecond

이것은 우리가 바로 '셔플'의 내용에 있지 않은 제 알고리즘의 적응 버전 S를 E C의 입출력 N 개의 D를 . 경우 대신 f를 r에 t는 비교 소자 충분히 적은 포함 S E C O N에서 D (원소 개수, 즉 제곱근 들에 전자 C O의 n 개의 D를 ), 우리는 재구성 f를 r에 S t 스택 순서로 병합하지 마십시오.firstsecondfirstsecondsecondfirst .second

  • 푸시 아직도 단순히 상 매개 변수가 대기 상태에서 행해진 다 .first
  • 팝핑은 다음과 같이 수행됩니다. 경우 , 우리는 단순히 디큐 비어 E C O N에서 D , 그 결과를 반환한다. 그렇지 않으면, 우리의 내용을 재구성 f를 내가 r에 S의 t을 그들이 스택 순서에 있도록. 경우 | f i r s t | < first에스이자형영형에프나는아르 자형에스우리는 단순히first를 대기열에서 빼고결과를 반환합니다. 그렇지 않으면, 우리는 추가S의EC의입출력N의D를Fr에S의t를스왑을f를r에S를tS의EC의입출력N의D, 디큐SECON에서D를하고 그 결과를 리턴한다.|에프나는아르 자형에스|<|에스이자형영형|에프나는아르 자형에스에스이자형영형에프나는아르 자형에스에프나는아르 자형에스에스이자형영형에스이자형영형

첫 번째 알고리즘의 C # 코드

C #을 본 적이 없어도 읽을 수 있어야합니다. 제네릭이 무엇인지 모르는 경우 문자열 스택에 대해 'T'의 모든 인스턴스를 'string'으로 바꾸십시오.

public class Stack<T> {
    private Queue<T> first = new Queue<T>();
    private Queue<T> second = new Queue<T>();
    int unsortedPart = 0;
    public void Push(T value) {
        unsortedPart++;
        first.Enqueue(value);
    }
    public T Pop() {
        if (first.Count == 0) {
            if (second.Count > 0)
                return second.Dequeue();
            else
                throw new InvalidOperationException("Empty stack.");
        } else {
            int nrOfItemsInFirst = first.Count;
            T[] reverser = new T[nrOfItemsInFirst];

            for (int i = nrOfItemsInFirst - unsortedPart - 1; i >= 0; i--)
                reverser[i] = first.Dequeue();

            for (int i = nrOfItemsInFirst - unsortedPart; i < nrOfItemsInFirst; i++)
                reverser[i] = first.Dequeue();

            for (int i = nrOfItemsInFirst - 1; i >= 0; i--)
                first.Enqueue(reverser[i]);

            unsortedPart = 0;
            if (first.Count * first.Count < second.Count)
                return first.Dequeue();
            else {
                while (second.Count > 0)
                    first.Enqueue(second.Dequeue());

                Queue<T> temp = first; first = second; second = temp;

                return second.Dequeue();
            }
        }
    }
}

분석

분명히 Push는 시간 안에 작동합니다 .O(1)

팝은 O 에서 작동합니다 ( 상각 시간. 두 가지 경우가 있습니다 : if| first| <O(n)다음 셔플 우리가f를r에S를t를스택의 순서로O(|f를r에St|)=O(|first|<|second|first시간. 경우| first| O(|first|)=O(n), 우리는 적어도√를가지고 있어야합니다|first||second| 은 푸시를 요구합니다. 따라서 우리는 마다이 사건을 칠 수 있습니다n 누르고 팝업 호출. 이 경우 실제 실행 시간은O(n)이므로 상각 된 시간은O( n)입니다.nO(n).O(nn)=O(n)

최종 메모

Pop an O ( 를 만드는 비용으로 추가 변수를 제거하는 것이 가능합니다 ( 동작 팝 재구성 가짐으로써f를r에St대신 푸시 모든 작업을 가지는 모든 호출에.O(n)first


첫 번째 단락을 편집하여 내 답변이 질문에 대한 실제 답변으로 공식화되었습니다.
Alex ten Brink

6
반전을 위해 어레이 (리버)를 사용하고 있습니다! 나는 당신이 이것을 할 수 있다고 생각하지 않습니다.
Kaveh

사실, 메소드를 실행하는 동안 여분의 공간을 사용하지만 허용 될 것이라고 생각 했습니다. 직접적으로 두 스택 을 사용하여 를 구현 하려면 스택 중 하나를 한 지점에서 반대 방향으로 되돌려 야합니다 나는 당신이 그것을하기 위해 여분의 공간이 필요하다는 것을 알고 있습니다. 따라서이 질문은 비슷하기 때문에 메소드 호출 사이에 추가 공간을 사용하지 않는 한 메소드 실행 중에 여분의 공간을 사용하는 것이 허용 될 것이라고 생각했습니다.
Alex ten Brink

6
"간단한 방법으로 두 개의 스택을 사용하여 큐를 구현하려면 한 지점에서 스택 중 하나를 되돌려 야하며 그렇게하려면 추가 공간이 필요하다는 것을 알고 있습니다."--- 그렇지 않습니다. Enqueue의 상각 비용을 3으로, Dequeue의 상각 비용을 1 개의 메모리 셀과 2 개의 스택으로 1 (즉, O (1))로 만드는 방법이 있습니다. 어려운 부분은 알고리즘의 디자인이 아니라 실제로 증명입니다.
Aaron Sterling

그것에 대해 좀 더 생각한 후에, 나는 실제로 속임수이고 내 이전 의견이 실제로 잘못되었음을 깨닫습니다. 나는 그것을 수정할 수있는 방법을 찾았습니다 : 나는 여분의 공간을 전혀 사용하지 않고 위의 두 가지와 동일한 실행 시간을 가진 두 개의 알고리즘을 생각했습니다 (Push는 이제 오래 걸리고 Pop은 일정한 시간에 수행됩니다). 모든 답변을 작성하면 새로운 답변을 게시 할 것입니다.
Alex ten Brink

12

이전 답변에 대한 몇 가지 의견에 따르면, 내가 부정 행위를하고 있음이 분명해졌습니다. 나는 여분의 공간을 사용했습니다 ( 내 Pop 메소드 실행 중 n )두 번째 알고리즘의 추가 공간).O(n)

다음 알고리즘은 Push와 Pop을 실행하는 동안 메서드 사이에 추가 공간을 사용하지 않고 추가 공간 만 사용합니다 . 푸시에는 O ( O(1)상각 된 실행 시간 및 Pop은O(1)최악의 경우 (및 상각 된) 실행 시간을갖습니다.O(n)O(1)

중재자 참고 사항 :이 답변을 별도의 답변으로 내겠다는 결정이 올바른지 확실하지 않습니다. 원래 답변이 여전히 질문과 관련이 있기 때문에 원래 답변을 삭제해서는 안된다고 생각했습니다.

알고리즘

우리는 queue 와 queue s e c o n d 두 개의 대기열을 가지고 있습니다 . F r에 t는 동안, 우리 "캐시"될 것이다 S E C O N에서 D는 우리의 주요 '기억'할 것이다. 두 대기열 모두 항상 '스택 순서'입니다. F r에 t는 스택과 상단에있는 원소 포함 S의 E C의 입출력 N 개의 D을 스택의 하단의 요소를 포함한다. 크기 f를 r에firstsecondfirstsecondfirstsecond 는 항상 s e c o n d 제곱근입니다.firstsecond

  • 다음 푸시 '삽입'큐의 시작에서 파라미터에 의해 수행된다 : 우리의 파라미터 대기열 다음 디큐 및 모든 다른 요소 - 인큐 다시 F r에 S t를 . 이 방법은 파라미터의 시작에서 끝 f를 r에 S의 t를 .firstfirstfirst
  • 경우 제곱근보다 커진다 S의 E C의 입출력 N의 D , 우리의 모든 요소 대기열 들에 E C의 입출력 N의 D를F r에 S t 하나씩 후 교환 f를 r에 S t(S) e c o n d . 이 방법의 요소 F는 r에 S t (적층 체의 상단)의 머리 끝 (S)의 Efirstsecondsecondfirstfirstsecondfirst .second
  • 팝 dequeueing 의해 행해진 하는 경우, 그 결과를 반환 F r에 S t가 비어 있지, 그렇지 dequeueing 의해 S 전자 의 C 입출력 N의 D 와 결과를 반환.firstfirstsecond

첫 번째 알고리즘의 C # 코드

이 코드는 C #을 본 적이 없어도 읽을 수 있어야합니다. 제네릭이 무엇인지 모르는 경우 문자열 스택에 대해 'T'의 모든 인스턴스를 'string'으로 바꾸십시오.

public class Stack<T> {
    private Queue<T> first = new Queue<T>();
    private Queue<T> second = new Queue<T>();
    public void Push(T value) {
        // I'll explain what's happening in these comments. Assume we pushed
        // integers onto the stack in increasing order: ie, we pushed 1 first,
        // then 2, then 3 and so on.

        // Suppose our queues look like this:
        // first: in 5 6 out
        // second: in 1 2 3 4 out
        // Note they are both in stack order and first contains the top of
        // the stack.

        // Suppose value == 7:
        first.Enqueue(value);
        // first: in 7 5 6 out
        // second: in 1 2 3 4 out

        // We restore the stack order in first:
        for (int i = 0; i < first.Count - 1; i++)
            first.Enqueue(first.Dequeue());
        // first.Enqueue(first.Dequeue()); is executed twice for this example, the 
        // following happens:
        // first: in 6 7 5 out
        // second: in 1 2 3 4 out
        // first: in 5 6 7 out
        // second: in 1 2 3 4 out

        // first exeeded its capacity, so we merge first and second.
        if (first.Count * first.Count > second.Count) {
            while (second.Count > 0)
                first.Enqueue(second.Dequeue());
            // first: in 4 5 6 7 out
            // second: in 1 2 3 out
            // first: in 3 4 5 6 7 out
            // second: in 1 2 out
            // first: in 2 3 4 5 6 7 out
            // second: in 1 out
            // first: in 1 2 3 4 5 6 7 out
            // second: in out

            Queue<T> temp = first; first = second; second = temp;
            // first: in out
            // second: in 1 2 3 4 5 6 7 out
        }
    }
    public T Pop() {
        if (first.Count == 0) {
            if (second.Count > 0)
                return second.Dequeue();
            else
                throw new InvalidOperationException("Empty stack.");
        } else
            return first.Dequeue();
    }
}

분석

팝은 최악의 경우 시간 안에 작동합니다 .O(1)

O 에서 푸시 작동 ( 상각 시간. 두 가지 경우가 있습니다 : if| first| <O(n)그런 다음 푸시는O(|first|<|second|시간. 경우| first| O(n)후 밀어서 소요O(N)시간이 있지만,이 동작 후에f를r에t는비어있을 것이다. 그것은 걸릴 것입니다O를(|first||second|O(n)first이 사례를 다시 받기 전의 시간이므로 상각 된 시간은O( nO(n)시간.O(nn)=O(n)



나는 선을 이해할 수 없다 first.Enqueue(first.Dequeue()). 잘못 입력 한 적이 있습니까?
MS Dousti

링크 주셔서 감사합니다, 나는 그에 따라 원래의 답변을 업데이트했습니다. 둘째, 알고리즘을 실행하는 동안 무슨 일이 일어나고 있는지 설명하는 코드에 많은 주석을 추가했습니다. 혼란을 없애기를 바랍니다.
Alex ten Brink

나를 위해 알고리즘은 편집하기 전에 더 읽기 쉽고 이해하기 쉬웠습니다.
Kaveh

9

나는 우리가 작업 당 상각 비용. Alex의 알고리즘은 상한을 제공합니다. 하한을 증명하기 위해 최악의 PUSH 및 POP 이동 시퀀스를 제공합니다.Θ(N)

최악의 시퀀스는 PUSH 연산과 로 구성됩니다.N PUSH 연산 및N POP 조작, 다시N PUSH 연산 및N POP 조작 등 :N

PUSHN(PUSHNPOPN)N

초기 PUSH 조작 후 상황을 고려하십시오 . 알고리즘의 작동 방식에 관계없이 큐 중 하나 이상에 적어도 N / 2 항목이 있어야합니다.NN/2

이제 (첫 번째 세트) 를 다루는 작업을 고려하십시오. PUSH 및 POP 조작. 알고리즘 전술은 다음 두 경우 중 하나에 속해야합니다.N

첫 번째 경우 알고리즘은 두 큐를 모두 사용합니다. 이 대기열 중 더 큰 대기열에는 이상의 항목이 있으므로 결국 대기열에서 하나의 요소 만 검색하고 나중에이 큰 대기열에서 DEQUEUE해야하는 경우 N / 2 이상의 대기열 작업 비용이 발생해야합니다 .N/2N/2

두 번째 경우 알고리즘은 두 큐를 모두 사용하지 않습니다. 이렇게하면 단일 대기열로 스택을 시뮬레이션 할 때 발생하는 문제가 줄어 듭니다. 이 대기열이 처음에 비어 있어도 순차적 액세스 권한이있는 순환 목록으로 대기열을 사용하는 것보다 낫지 않으며 이상을 사용해야하는 것이 간단합니다.의 각각에 대한 평균 작업 큐2N/2 스택 작업.2N

두 경우 모두 2 를 처리하기 위해 최소 시간 (대기열 작업)이 필요했습니다.N/2 스택 작업. 이 과정을 반복 할 수 있기 때문에2N 번, 우리는N을 필요로한다N3 개의N스택 작업을 처리하는 데 N /2시간,Ω의 하한 제공(NN/23N작업 당 상각 시간.Ω(N)


Jack은 이것을 수정하여 라운드 수 (괄호에 지수)가 내가 가진 것처럼대신 N. 이것은 전체 시퀀스에서 상각 할 수 없었던 것을 "과잉"이라고 보여 주었기 때문입니다.N 반복. 고마워, 잭! N
Shaun Harker

이 두 경우의 혼합은 어떻습니까? 예를 들어 대안으로 항목Q1(적어도 하나와N / 2엔트리) 및Q2(다른 큐)? 이 패턴이 더 비싸다고 생각하지만 어떻게 논쟁해야합니까? 그리고 두 번째 경우에는2 개 √에 대한 평균 비용이nQ1N/2Q2 스택 작업은 이상입니다2n . n4:1+2++n+n2n
hengxin

분명히 베드로의 대답은이 하한과 모순됩니까?
Joe

@Joe 나는 Peter의 대답이 첫 번째 N 푸시 가이 시퀀스에서 터지지 않기 때문에이 하한과 모순되지 않는다고 생각합니다. 셔플 링 절차는 적어도 O (N) 시간이 필요하므로 각``단계 ''( 순서)가 발생해야하는 경우PUSHNPOPNO(N)

O(N)

6

O(lgn)pushpoppopO(lgn)pop 가 요청되고 출력 대기열이 비어 있으면 일련의 완벽한 셔플을 수행하여 입력 대기열을 뒤집고 출력 대기열에 저장합니다.

O(1)

내가 아는 한, 이것은 새로운 아이디어입니다 ...



아아! 업데이트 또는 관련 질문을 찾아야했습니다. 이전 답변에서 링크 한 논문은 k 스택과 k + 1 스택 사이의 관계를 제기했습니다. 이 트릭으로 인해 k와 k + 1 스택 사이에 k 큐의 성능이 제공됩니까? 그렇다면, 그것은 일종의 깔끔한 주석입니다. 어느 쪽이든, 당신의 답변에 나를 연결시켜 주셔서 감사합니다. 그래서 나는 다른 장소를 위해 이것을 쓰는 데 너무 많은 시간을 낭비하지 않았습니다.
피터 부스

1

여분의 공간을 사용하지 않고 우선 순위가 지정된 대기열을 사용하고 새로운 푸시를 강제로 수행하여 이전보다 더 큰 우선 순위를 부여합니까? 그래도 여전히 O (1)는 아닙니다.


0

상각 된 일정한 시간에 스택을 구현하기 위해 대기열을 얻을 수 없습니다. 그러나 최악의 선형 시간에 스택을 구현하기 위해 두 개의 대기열을 얻는 방법을 생각할 수 있습니다.

  • AB
  • 푸시 작업이있을 때마다 비트를 뒤집고 비트를 구분하는 대기열에 요소를 삽입합니다. 다른 큐에서 모든 것을 팝하여 현재 큐로 푸시하십시오.
  • 팝 조작은 현재 큐의 앞쪽을 벗어나 외부 상태 비트를 건드리지 않습니다.

물론 마지막 작업이 푸시인지 팝인지를 알려주는 다른 외부 상태를 추가 할 수 있습니다. 한 번에 두 번의 푸시 작업을받을 때까지 한 큐에서 다른 큐로 모든 것을 옮기는 것을 지연시킬 수 있습니다. 또한 팝 조작이 약간 더 복잡해집니다. 이것은 우리에게 팝 연산에 대한 O (1) 상각 복잡성을 제공합니다. 불행히도 푸시는 선형으로 유지됩니다.

푸시 작업이 완료 될 때마다 새 요소가 빈 큐의 헤드에 배치되고 전체 큐가 해당 요소의 꼬리 끝에 추가되어 요소를 효과적으로 되돌리기 때문에이 모든 것이 작동합니다.

상각 된 일정한 시간 작업을 원한다면 더 영리한 일을해야 할 것입니다.


4
분명히, 케이스 시간 복잡성이 동일하고 복잡하지 않은 단일 큐를 사용할 수 있습니다. 본질적으로 큐를 스택의 맨 위를 나타내는 추가 큐 요소가있는 순환 목록으로 처리합니다.
Dave Clarke

당신이 할 수있는 것 같습니다! 그러나 이런 방식으로 스택을 시뮬레이션하려면 둘 이상의 클래식 큐가 필요한 것 같습니다.
로스 스나이더

0

대기열에 프론트 로딩이 허용되는 경우 대기열이 하나만 필요한 (또는 더 구체적으로 deque) 필요한 간단한 해결책이 있습니다. 아마도 이것이 원래 질문의 TA 과정을 염두에 둔 대기열 유형입니까?

전면 로딩을 허용하지 않으면 다른 솔루션이 있습니다.

이 알고리즘에는 두 개의 대기열과 두 개의 포인터가 필요합니다. 각각 Q1, Q2, primary 및 secondary라고합니다. 초기화시 Q1 및 Q2가 비어 있으면 기본 포인트는 Q1을 가리키고 보조 포인트는 Q2를 가리 킵니다.

PUSH 작업은 간단하며 다음과 같이 구성됩니다.

*primary.enqueue(value);

POP 작업은 약간 더 복잡합니다. 기본 대기열의 마지막 항목을 제외한 모든 항목을 보조 대기열로 스풀링하고 포인터를 교체하고 원래 대기열에서 마지막 남은 항목을 리턴해야합니다.

while(*primary.size() > 1)
{
    *secondary.enqueue(*primary.dequeue());
}

swap(primary, secondary);
return(*secondary.dequeue());

경계 검사가 수행되지 않으며 O (1)이 아닙니다.

이것을 입력하면서 Alex가했던 것처럼 while 루프 대신 for 루프를 사용하여 단일 대기열 로이 작업을 수행 할 수 있음을 알 수 있습니다. 어느 쪽이든, PUSH 연산은 O (1)이고 POP 연산은 O (n)이됩니다.


다음은 각각 2 개의 큐와 1 개의 포인터 (Q1, Q2 및 queue_p)를 사용하는 다른 솔루션입니다.

초기화시 Q1 및 Q2는 비어 있으며 queue_p는 Q1을 가리 킵니다.

다시 말하지만, PUSH 조작은 간단하지만 다른 큐에서 queue_p를 가리키는 추가 단계가 필요합니다.

*queue_p.enqueue(value);
queue_p = (queue_p == &Q1) ? &Q2 : &Q1;

POP 작업 작업은 이전과 비슷하지만 대기열을 통해 회전해야하는 n / 2 개의 항목이 있습니다.

queue_p = (queue_p == &Q1) ? &Q2 : &Q1;
for(i=0, i<(*queue_p.size()-1, i++)
{
    *queue_p.enqueue(*queue_p.dequeue());
}
return(*queue_p.dequeue());

PUSH 작업은 여전히 ​​O (1)이지만 POP 작업은 O (n / 2)입니다.

개인적 으로이 문제의 경우 단일 이중 엔드 큐 (디케)를 구현하고 원하는 때 스택이라고 부르는 아이디어를 선호합니다.


두 번째 알고리즘은 Alex의 관련성이 높은 알고리즘을 이해하는 데 도움이됩니다.
hengxin

0

케이Θ(1/케이)

케이

영형(1)

나는Θ(나는/케이)Θ(나는/케이)영형(1)나는+1영형(1/케이)나는1Θ(1/케이)

Ω(1/케이)o(n1/k)Ω(n1/k)o(n2/k)ko(n)

Θ(logn)


-3

스택은 제 2 큐를 버퍼로 사용하여 두 개의 큐를 사용하여 구현 될 수있다. 항목을 스택으로 밀면 대기열 끝에 추가됩니다. 항목이 팝업 될 때마다 첫 번째 대기열의 n – 1 요소는 두 번째로 이동해야하며 나머지 항목은 반환됩니다. 공개 클래스 QueueStack 구현 IStack {private IQueue q1 = new Queue (); 개인 IQueue q2 = 새 대기열 (); public void push (E e) {q1.enqueue (e) // O (1)} public E pop (E e) {while (1 <q1.size ()) // O (n) {q2.enqueue ( q1.dequeue ()); } sw apQueues (); 반환 q2.dequeue (); } 개인 void swapQueues () {IQueue Q = q2; q2 = q1; q1 = Q; }}


2
상각 시간 O (1)에 대한 질문에서 부분을 놓치셨습니까?
David Eppstein
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.