Stacks, lists and queues are primitive data structures fundamental to implementing any program requiring data storage and retrieval. The following tables offer specific information on each type of data structure. The rest of the web page offers information about implementing and applying these data structures.
|
ADT |
Mathematical Model |
Operations |
|||||||||||
|
Stack |
|
Push(S,Data) Pop(S) Makenull(S) Empty(S) Top(S) |
|||||||||||
|
|
|||||||||||||
|
ADT |
Mathematical Model |
Operations |
|||||||||||
|
Queue |
|
Enqueue(Q) |
|||||||||||
|
The standard operation on a queue are as follows:
|
|||||||||||||
|
ADT |
Mathematical Model |
Operations |
|||||||||||||||
|
Deque |
|
Inject(D,Data) Eject(D) Dequeue(D) Enqueue(D,Data) Front(D) Rear(D) MakeNull(D) |
|||||||||||||||
|
|
|||||||||||||||||
|
ADT |
Mathematical model |
Operations |
|||||||
|
List |
[x1 ,x2 ,...,xn] |
Concatenate(L1, L2) Access(L, i) Sublist(L, [i...j]) |
|||||||
|
The standard operations on s list are as follows:
|
|||||||||
CONCATENATE, ACCESS, and SUBLIST are called
"ATOMIC" operations. Using these three operations we can make
other operations.Examples:
The atomic operations may be used to describe the standard stack, queue and deque operations. |
|||||||||
The ADTs can be implemented by various data structures.
A static array can be used to implement the data structures. Consider, for example, a queue implemented as an array.

As data is added to the rear of the queue, the cell labelled 'rear' (the cell containing the last enqueued data) is incremented to the right. If data is dequeued, the cell labelled 'front' (the cell containing the first enqueued data) is also incremented to the right.
Subsequently, the data could end up flanked by empty cells.

If the cell labelled 'rear' is the last cell in the array, AND there are empty cells at the beginning of the array (due to previous dequeues) then 'rear' will wrap to the beginning of the array on the next enqueue. This will result in data at the beginning and end of the array, with empty cells in the middle.

An ANCHORED LIST prevents this movement of data in the array. Data is always left skewed. In our queue example, if data is dequeued then the whole array must be shifted one cell to the left.
Anchored lists are more appropiate for stacks, where the non-anchored end of the list represents the top of the stack.
Two stacks can be anchored at opposite ends of an array. As the stacks fill with data, they will "grow" towards each other. The above figure illustrates this concept. By filling the array with the two stacks anchored at opposite ends, the user can have the utility of two stacks while using the storage of one array.
A series of structures connected with pointers can be used to implement the data structures. Each structure contains data with one or more pointers to neighbouring structures.

There are several variants of the linked list structure:
Endogenous / Exogenous lists:
Here are example declarations of endogenous and exogenous structures:

Circular / Non-circular lists:
- a circular list has the last cell in the array pointing to the first cell in the array. Specifically, the last cell's 'next' pointer references the first cell.
Representation in C : last_cell.next = &first_cell
- the last cell in a non-circular list points to nothing.
Representation in C : last_cell.next = NULL
Circular lists are useful for representing polygons (for example) because one can trace a path continously back to where one started. This is useful for representing a polygon because there is essentially no starting or ending point. Thus, we would like an implementation to illustrate this.

With/Without a Header/Trailer:
- a header node is a dummy first node in the list. It is not part of the data, but rather contains some information about the list (eg. size).
- a trailer node is at the end of a list (its contents marks the end).

Doubly Linked List:
-each node in a doubly linked list is a structure with two pointers to link with neighbouring nodes. One pointer points to the next node in the list, and the other pointer points to the previous node in the array. This implementation is useful for deleting nodes. The algorithm can be performed in O(1) time. Deleting nodes in a singly linked list can be done in OMEGA(n) time. Therefore, doubly linked lists can be very useful in applications requiring a lot of deletions. The pseudocode for the delete algorithm is as follows:
p -> prev -> next = p -> next
p-> next -> prev = p -> prev

-Stacks can easily be implemented as a linked list. One needs a pointer to the top of the stack (which is the front of the list).

-Queues can easily be implemented as a linked list as well. One needs a pointer to the front of the queue (which is the front of the list); as well, one needs a pointer to the rear of the queue (which is the end of the list).

It is better, however, to implement a queue as a circular list. This circular list has a pointer to the list pointing at the rear rather than the front of the list.
-Concatenating sublists can easily be done with a linked list. Conversely, concatenating sublists with an array implementation is expensive.
-Referencing sublists with a linked list implementation is expensive. Conversely, sublists can be referenced easily with an array implementation.
A polynomial can be represented with primitive data structures. For example, a polynomial represented as akxk ak-1xk-1 + ... + a0 can be represented as a linked list. Each node is a structure with two values: ai and i. Thus, the length of the list will be k. The first node will have (ak, k), the second node will have (ak-1, k-1) etc. The last node will be (a0, 0).
The polynomial 3x9 + 7x3 + 5 can be represented in a list as follows: (3,9) --> (7,3) --> (5,0) where each pair of integers represent a node, and the arrow represents a link to its neighbouring node.
Derivatives of polynomials can be easily computed by proceeding node by node. In our previous example the list after computing the derivative would represented as follows: (27,8) --> (21,2). The specific polynomial ADT will define various operations, such as multiplication, addition, subtraction, derivative, integration etc. A polynomial ADT can be useful for symbolic computation as well.
Large integers can also be implemented with primitive data structures.
To conform to our previous example, consider a large integer represented
as a linked list. If we represent the integer as successive powers of 10,
where the power of 10 increments by 3 and the coefficent is a three digit
number, we can make computations on such numbers easier. For example, we
can represent a very large number as follows:
513(106) + 899(103) + 722(100).
Using this notation, the number can be represented as follows:
(513) --> (899) --> (722).
The first number represents the coefficient of the 106 term,
the next number represents the coefficient of the 103 term and
so on. The arrows represent links to adjacent nodes.
The specific ADT will define operations on this representation, such as
addition, subtraction, multiplication, division, comparison, copy etc.
A window interface can be represented by lists. Consider an environment
with many windows. The fist node in the list could represent the current
active window. Subsequent windows are further along the list. In other
words, the nth window corresponds to the nth node
in the list.
The ADT can define several functions, such as Find_first_window which would
bring a window clicked upon to the front of the list (make it active).
Other functions could perform window deletion or creation.

When memory is requested, a list of available blocks of memory might
be useful. Again, a list could represent blocks in memory available to
the user, with nodes containing pointers to these available blocks. The
list can be used like a stack (LIFO). The last freed memory becomes the
next available to the user. Such lists are called 'free space lists' or
'available space lists'. Since addition and deletion of nodes is at one
end, these lists behave like stacks. All operations on free space lists
can be done in O(1) time.

Expressions can be evaluated using a stack. Given an expression in a
high-level language (for example, (a + b) * c) the compiler will transform
this expression to postfix form. The postfix form of the above example
is ab + c *, where a and b are operands, + and * are operators, and the
expression is scanned left to right. The expression is pushed on the stack
and evaluated as it is popped. The following algorithm illustrates the
process:
In the end the stack will hold one element (the result).
A text editor can be implemented with a stack. Characters are pushed
on a stack as the user enters text. Commands to delete one character or
a command to delete a series of characters (for example, a sentence or
a word) would also push a character on a stack. However, the character
would be a unique identifier to know how many characters to delete. For
example, an identifier to delete one character would pop the stack once.
An identifier to delete a sentence would pop all characters until the stack
is empty or a period is encountered.
Postscript is a full-fledged interpreted computer language in which
all operations are done by accessing a stack. It is the language of choice
for laser printers. For example, the Postscript section of code
1 2 3 4 5 6 ADD MUL SUB 7 ADD MUL ADD
represents:
1 2 3 4 5 6 + * - 7 + * +
which in turn represents:
1 + ( 2 * ( ( 3 - ( 4 * ( 5 + 6 ) ) ) + ( 7 ) ) )
Very much as in the stack-based language example, the expression can be
evaluated from left to right. Expressions written in the form given above
are called postfix expressions. Their easy evaluation with the help of
a stack makes them natural candidates for the organization of expressions
by compilers.
Stacks are used to write down instructions that you can not act on immediately.
For example, future work to be done by the program, information that may
be useful later, and so forth (just as with a scratch pad). An example
of this is the rat-in-maze problem (see below). A stack can be used to
solve the problem of traversing a maze. One must keep track of previously
explored routes, or else an infinite loop could occur. For example, with
no previous knowledge of exploring a specific route unsuccessfully, one
can enter a path, find no solution to the maze, exit the path through the
same route as entrance, then enter the same unsuccessful path all over
again. This problem can be solved with the help of a stack.
If we consider each step through a maze a cell, the following algorithm
will traverse a maze successfully with the help of a stack 'S':
(For all cells)
- VISITED(q) <- true
- if q = 'TARGET CELL' then stop
- PUSH(q, S) /*S has your path*/
else POP(S)
Stacks are used in recursions. Every recursive program can be rewritten
iteratively using a stack. One related problem is the knapsack problem:
Consider a knapsack with volume represented as a fixed integer. One is
given a series of items of varying size (the size of the objects is represented
as an integer). The knapsack problem is to find a combination of items
that will fit exactly into the knapsack (i.e. no unused space). The function
call is written as 'knapsack(target: , candidate: )' where 'target' is
the amount of space left in the sack, and 'candidate' is the reference
to the item being considered to be added. The function returns a boolean
result; 'true' if target can be filled exactly using a subest of the items
numbered "candidate, ..., n". Here 'n' is the total number of
items. Define size[.] as an array of sizes of the items. The following
is a recursive solution to the problem:
knapsack(target,candidate)
if target = 0 then return "true"
if candidate > n or target < 0 then return "false"
if knapsack(target - size(candidate) , candidate + 1) then
A knapsack of size 26 can be filled with items of size 15 and 11.

This applet demonstrates an excellent application of the Stack ADT. In order to find its way out of the maze, the algorithm pushes its moves onto a stack until it either reaches the end, or becomes blocked. If it becomes blocked, the algorithm will backtrack its moves by popping them from the stack until it reaches a square with at least one free neighbor. It will then follow this new path until it again becomes blocked or it finishes. This process is repeated until the end of the maze is reached.
To see this algorithm in action, press the "Next ->" button and watch the green line advance one move. Press "<- Back" to go back one iteration. When the green line has to backtrack, it leaves behind a grey line to indicate the squares it has already visited.
Here are the source files:
For More Information on Data Structures, consider these books:
Additional information can be found at the following locations:
ttp://www.csi.uottawa.ca/~holte/T26/ordered-lists.html
-- Lecture notes on lists.
http://www.csi.uottawa.ca/~holte/T26/queue.html
-- Lecture notes on queues.
http://www.csi.uottawa.ca/~holte/T26/stack.html
-- Lecture notes on stacks.
The above three links are from the University of Ottawa.
http://www.cs.ntu..edu.au/staff/rsvp/sit211/axioms.html
-- Abstract Data Types, Stacks
http://www-theory.dcs.st-and.ac.uk/~
mda/cs2001/queues/general.html -- An introduction to Queues.
http://165.91.2.23/pascal/ -- How to program in Pascal.
http://www.ics.Hawaii.Edu/~sugihara/course/ics311s97/note/2-04n07
-- Lecture notes from the University of Hawaii on lists.
The text is based on class notes taken by Daniel Taranovsky. Web Page text was authored by Damian Orfamoudakis and Daniel Taranovsky. Daniel Levin created the Java applet and accompanying text. Graphics designed and drawn by Danny Wong.
Last Updated 4:25 am Feb 9, 1997