Definition · Stack and queueA stack supports push and pop at one end: the element removed is the one most recently added. A queue supports enqueue at one end and dequeue at the other: the element removed is the one least recently added. Neither supports access by position, and that restriction is what makes every operation on both of them cost 1 on an array or on a list.
Definition · The two-stack queueKeep two stacks, IN and OUT. To enqueue, push onto IN. To dequeue: if OUT is empty, pop every element of IN and push it onto OUT, which reverses the order; then pop OUT. Because a stack reverses and two reversals restore, the element that comes off OUT is the oldest one still in the queue.
Charge one for each push, each pop and each move. Then an enqueue costs 1 always. A dequeue costs 1 when OUT is not empty, and k + 1 when OUT is empty and IN holds k elements — k moves and then the pop. So the worst single dequeue costs as much as the number of elements in the queue, and that is a genuine Θ(n) operation that a reader is entitled to be unhappy about.
Example · Three in, three outThe sequence E E E D D D costs 1, 1, 1, 4, 1, 1, adding to 9. The 4 is the first dequeue: three moves to pour IN into OUT, then one pop. The two dequeues after it cost 1 each, because OUT already holds their elements. One operation out of six cost four times what the others did, and the sequence total is 9 for three arrivals — exactly 3 each.
The accounting argument
The technique is the accounting method of Discrete Mathematics’ “Recursion Trees and Amortised Analysis”, applied to a structure rather than to a loop. Give every enqueue a budget of 3: one for the push that happens now, one to pay for the move this element will make later, and one to pay for the pop that will finally remove it. Nothing else in the structure ever touches an element.
Theorem · The two-stack queue costs at most 3 per enqueueOver any sequence of operations containing m enqueues, the total cost of the two-stack queue is at most 3m.
Every element enters exactly once, is moved from IN to OUT at most once, and is popped from OUT at most once — once it has been moved it is never pushed back, because elements only ever travel from IN to OUT. So the three units charged at its enqueue cover every operation performed on it, for ever. Summing over the m elements gives 3m, and the real total cannot exceed the charged total.
Formally, let the credit after an operation be the charged total so far minus the real total so far. An enqueue is charged 3 and costs 1, so the credit rises by 2. A dequeue is charged 0 and costs k + 1 or 1, so the credit falls by that much.
The credit is never negative. Each element of IN carries 2 unspent units and each element of OUT carries 1, so before a dequeue that must pour k elements across, the bank holds at least 2k — and the dequeue costs k + 1 ≤ 2k for k ≥ 1. A dequeue on a non-empty OUT costs 1 and that element’s own unspent unit pays for it.
Since the credit is the charged total minus the real total and it is never negative, the real total is at most the charged total, which is 3m. The lab prints the credit balance after every step for exactly this reason: if it ever went negative the argument would be wrong, and watching it stay non-negative is watching the proof run.
□
What the measurement does and does not show
On E E E D D D the lab reports a total of 9 and a bound of 9: the two agree, because every element that arrived was also served, so each one really did pay all three of its charges. On E D E D E D E D the total is 12 and the bound is 12, for the same reason. On the twelve-operation sequence E E D E E D E E D D D D the total is 18 with one dequeue costing 5, and the bound is again 18.
That the two keep coinciding is informative and it is not the theorem. A sequence that enqueues and never dequeues costs m against a bound of 3m, so the bound is loose there; the coincidence above happens when the queue is drained. What no sequence can do is establish the bound, because the bound quantifies over sequences and a run is one sequence. Type in a pattern that makes the expensive dequeue as expensive as you can — the cumulative line will approach the 3m line and, by the argument above, never cross it.