Review of Stacks and Queues
Purpose
Stacks and queues are temporally ordered container types.
Elements are removed in an order based upon the order in which they were
added.
- Stacks remove the last element added first. They use a LIFO
(Last in First out) ordering.
- Queues remove the first element added first. They use a FIFO
(First in First out) ordering.
Implementaion
There are many ways to implement Stacks and Queues. We looked at
some of them last time in class.
- Stacks
- Using lists, using cons (:) to add elements to the front of the list.
- Using Arrays and a top pointer to store the next available slot.
- A method we did not discuss uses pointers.
- Queues
- Using lists, using append (++) to add elements to the back of the list.
- Using a pair of lists. One representing the front of the Q, and the other representing the reverse of the end of the Q.
- Using Arrays and front, and rear pointers to store the
next available slot, and the slot storing the next element to remove.
- A method we did not discuss uses pointers.
Performance
Ideally we would like all operations of adding and removing from
stacks and Queues to take constant time, independent of the
size of the stack or queue. What implementation method fails this test?
Pure v.s. Commands
Both stacks and queues can be given both pure and command based
implementations.
- Pure implementations are usually based upon lists. A pure
implementation returns both a value and a new stack or queue. We
can see this in the contracts of list based implementations.
- add:: a -> Stack a -> Stack a
- remove:: Queue a -> (a,Queue a)
Note there is no IO in the type of these operators.
- Command based implementations are usually based upon arrays or pointers, since they
can potentially change the state of the observable world.
There are basically two types. Completely command based and hybrid.
- Hybrid.The array based implementation of stacks
we saw in class, and the array based implementation of Queues in the homework
are hybrid, since the array was command based but the
top (in case of stack) and the front, rear and full (in the case of queues)
were pure.
-
data ArrStack a = ArrStack Int Int (Array a)
-
data ArrQueue a = ArrQueue Int -- index just to the left of the front element (if one exists)
Int -- index of the last element (if one exists)
Int -- low bound of the array
Int -- high bound of the array
Bool -- is the Queue full
(Array a) -- the array that stores the elements
This also shows in the type of operations. Like
pure based implementations, we need to return new Stacks and Queues
(in order to update the top, front, rear, etc).
- addStack :: a -> ArrStack a -> IO (ArrStack a)
- delStack :: ArrStack a -> IO (a,ArrStack a)
The advantages of this
kind of implementation are that we can test certain properties (i.e. where the next element goes, is the structure full or empty)
without using a command.
- isEmpty:: Stack a -> Bool
- Completely command based implementations store the
top, front, rear, etc in mutable variables just like the elements
of the arrays. This way operations don't need to return new
stacks and queues, since all the elements are updated by mutation.
- data ArrStack2 a = ArrStack2 (Ptr Int) (Ptr Int) (Array a)
- addStack :: a -> ArrStack a -> IO ()
Demos of several stack and queue implementations.
Back to the Daily Record.
Back to the class web-page.