A deque is type of queue which enables adding and removing items from both ends. Deque ends have such names as left/right, front/rear or, as we will use here, front and back. The add/remove operations on a deque are typically called enqueue and dequeue. For ease of explanation but without loss of generality, we limit discussion to a deque of integers.
This gives us the following;
type Deque struct {
// Has unexported fields.
}
func (d *Deque) DequeueB() (int, bool)
func (d *Deque) DequeueF() (int, bool)
func (d *Deque) EnqueueB(x int)
func (d *Deque) EnqueueF(x int)
One method of implementing a deque is to use two stacks, one representing the front of the deque and the other representing the back of the deque. Enqueueing and dequeueing then become simply a matter of pushing or popping to, or from, the appropriate stack. . We will use a simple stack, supporting the operations push, pop, and length. Previous blog post on implementing stacks in Golang.
type stack []int
// push a new integer onto the stack
func (s *stack) push(x int) {
*s = append(*s, x)
}
// pop: remove and return top element of stack, return false if stack is empty
func (s *stack) pop() (int, bool) {
if s.len() == 0 {
return 0, false
}
i := len(*s) - 1
x := (*s)[i]
*s = (*s)[:i]
return x, true
}
func (s *stack) len() int {
return len(*s)
}
By now, the astute reader will be asking what happens when one stack becomes empty and a further request comes to deque from that end. The solution to this dilemma presents the only complexity in this implementation of a deque. Each time an item is added to, or removed from, the deque we balance the two stacks to maintain the invariant that while there are 2 or more items, each stack holds at least one third as many items as the other. In code, that is;
// balance stacks if needed
func (d *Deque) balance() {
small, big := order(&d.front, &d.back)
if small.len() == 0 && big.len() == 1 {
return
}
if 3*d.front.len() < d.back.len() ||
3*d.back.len() < d.front.len() {
d.rebalance()
}
}
order()
simply gives us the stacks back in order of size. Ignoring the
implementation of rebalance()
and time complexity for now, we have a deque that
maintains two ‘balanced’ stacks, one holding items for the front of the deque
and the other holding items for the back of the deque. We can enqueue and
dequeue from both ends.
balancing the deque
Based on Open Data Structures [Mor], we
maintain the invariant stated above (neither stack falls below 3x the size of
the other). Before giving the implementation let us discuss the running
time. Clearly if we are going to carry out some sequence of operations on
stacks, adding and/or removing some multiple of N, then we are going to be left
with running time of O(N). This would be disastrous since we call balance()
on
each enqueue/dequeue operation. It turns out though, that while the worst case running
time is O(N), the amortized running time is O(1). Stated another way, for M
enqueue/dequeue operations we will have running time in the order of O(M).
For those familiar with the running time complexity analysis of dynamic arrays, the above statement will not come as a surprise. For a more complete analysis see [Mor].
One method of balancing the stacks is by way of a couple of additional temporary stacks and a sequence of push and pop operations.
// rebalance stacks
func (d *Deque) rebalance() {
small, big := order(&d.front, &d.back)
half := (small.len() + big.len()) / 2
tmpB := &stack{}
tmpS := &stack{}
mvN(tmpB, big, half) // store half of big
mvAll(tmpS, small) // store small
mvAll(small, big) // put bottom half of big onto small
mvAll(small, tmpS) // restore small
mvAll(big, tmpB) // restore big
}
As mentioned previously, order()
simply gives us the stacks back in order of size, the other helper
functions are;
// mvN: move n items from dst and push to src
func mvN(dst, src *stack, n int) {
for i := 0; i < n; i++ {
x, _ := src.pop()
dst.push(x)
}
}
// mvAll: move all items from src to dst
func mvAll(dst, src *stack) {
mvN(dst, src, src.len())
}
See Github for complete source code and tests.
Bibliography:
[Mor] - Open Data Structures, Pat Morin, Edition 0.1