09-stackqueue
09-stackqueue
1 Introduction
In this lecture we introduce queues and stacks as data structures, e.g., for
managing tasks. They follow similar principles of organizing the data.
Each provides simple functions for adding and removing elements. But
they differ in terms of the order in which the elements are removed. They
can be implemented easily as an abstract data type in C0, like the abstract
arr type of arrays that we discussed in the previous lectures). Today we
will not talk about the implementation of arrays; we will implement them
in the next lecture.
Relating this to our learning goals, we have
Algorithms and Data Structures: Queues and stacks are two important data
structure to understand.
L ECTURE N OTES
Stacks & Queues L9.2
L ECTURE N OTES
Stacks & Queues L9.3
where x1 is the bottom of the stack and xn is the top of the stack. We push
elements on the top and also pop them from the top. If we’re feeling artis-
tic, we can draw stacks with arrows to emphasize that we’re pushing and
popping from the top:
4 Abstraction
An important point about formulating a precise interface to a data structure
like a stack is to achieve abstraction. This means that as a client of the data
structure we can only use the functions in the interface. In particular, we
are not permitted to use or even know about details of the implementation
of stacks.
Let’s consider an example of a client-side program. We would like to
examine the element at the top of the stack without removing it from the
stack. Such a function would have the declaration
string peek(stack_t S)
//@requires S != NULL && !stack_empty(S);
;
L ECTURE N OTES
Stacks & Queues L9.4
string peek(stack_t S)
//@requires is_stack(S) && !stack_empty(S);
{
return S->data[S->top];
}
string peek(stack_t S)
//@requires S != NULL && !stack_empty(S);
{
string x = pop(S);
push(S, x);
return x;
}
We encourage you to consider this problem and program it before you read
on.
L ECTURE N OTES
Stacks & Queues L9.5
int stack_size(stack_t S)
//@requires S != NULL;
{
int count = 0;
while (!stack_empty(S)) {
pop(S);
count++;
}
return count;
}
However, this function has a big problem: in order to compute the size
we have to destroy the stack! Clearly, there may be situations where we
would like to know the number of elements in a stack without deleting all
of its elements. Fortunately, we can use the idea from the peek function in
amplified form: we maintain a new temporary stack T to hold the elements
we pop from S. Once we are done counting, we push them back onto S to
repair the damage.
L ECTURE N OTES
Stacks & Queues L9.6
int stack_size(stack_t S)
//@requires S != NULL;
{
stack_t T = stack_new();
int count = 0;
while (!stack_empty(S)) {
push(T, pop(S));
count++;
}
while (!stack_empty(T)) {
push(S, pop(T));
}
return count;
}
L ECTURE N OTES
Stacks & Queues L9.7
L ECTURE N OTES
Stacks & Queues L9.8
L ECTURE N OTES
Stacks & Queues L9.9
queue_t C = Q;
will not have the effect of copying the queue Q into a new queue C. Just
as for the case of stacks, this assignment makes C and Q aliases, so if we
change one of the two, for example enqueue an element into C, then the
other queue will have changed as well. Just as for the case of stacks, we
need to implement a function for copying the data.
The queue interface provides functions that allow us to dequeue data
from the queue, which we can do as long as the queue is not empty. So we
create a new queue C. Then we read all data from queue Q and put it into
the new queue C.
queue_t C = queue_new();
while (!queue_empty(Q)) {
enq(C, deq(Q));
}
//@assert queue_empty(Q);
Now the new queue C will contain all data that was previously in Q, so C
is a copy of what used to be in Q. But there is a problem with this approach.
Before you read on, can you find out which problem?
L ECTURE N OTES
Stacks & Queues L9.10
L ECTURE N OTES
Stacks & Queues L9.11
We could try to enqueue all data that we have read from Q back into Q
before putting it into C.
queue_t C = queue_new();
while (!queue_empty(Q)) {
string s = deq(Q);
enq(Q, s);
enq(C, s);
}
//@assert queue_empty(Q);
But there is something very fundamentally wrong with this idea. Can you
figure it out?
L ECTURE N OTES
Stacks & Queues L9.12
The problem with the above attempt is that the loop will never termi-
nate unless Q is empty to begin with. For every element that the loop body
dequeues from Q, it enqueues one element back into Q. That way, Q will
always have the same number of elements and will never become empty.
Therefore, we must go back to our original strategy and first read all ele-
ments from Q. But instead of putting them into C, we will put them into a
third queue T for temporary storage. Then we will read all elements from
the temporary storage T and enqueue them into both the copy C and back
into the original queue Q. At the end of this process, the temporary queue
T will be empty, which is fine, because we will not need it any longer. But
both the copy C and the original queue Q will be replenished with all the
elements that Q had originally. And C will be a copy of Q.
queue_t queue_copy(queue Q) {
queue_t T = queue_new();
while (!queue_empty(Q)) {
enq(T, deq(Q));
}
//@assert queue_empty(Q);
queue_t C = queue_new();
while (!queue_empty(T)) {
string s = deq(T);
enq(Q, s);
enq(C, s);
}
//@assert queue_empty(T);
return C;
}
L ECTURE N OTES