Lists Stacks and Queues

Abstract Data Types (ADTs)

Lists stacks and queues are examples of collection abstract data types; basically, they hold a collection of objects. From a user’s perspective, the main difference is the API or Application Programming Interface that each collection provides. The API is the interface to collections. APIS provide a:

  1. standard interface that is separate from the implementation of the actual algorithms.
  2. reference for programmers to use the collection.

Important

The concept of an API is extremely important from a software engineering perspective! The API should be designed first! (why?)

The API should not change! (why?)

The concept of an ADT is another powerful software engineering concept. An ADT is purposefully ABSTRACT! In other words, it doesn’t matter what we are modeling, we simply model it in an abstract form keeping only the parts we need.

Example ADT

Lets create an ADT for a student in a student information system. We need to model a student, but only the parts we need for our application. So, a student might be a:

  • student ID number
  • name
  • address
  • emergency contact
  • classes taken
  • grades for classes
  • credit hours for classes

We might also need to perform some operations on a student:

  • register for a class
  • calculate a GPA
  • update an emergency contact

The data and operations together form an ADT. The operations form an API.

Note that the ADT and API have no mention of how any of the operations are implemented. The point is that users of the class don’t necessarily need to know, beyond some basic details. This allows us flexibility to update implementations without breaking existing code using the API (as long as the API doesn’t change).

So, lists, stacks and queues are 3 container ADTs with varying APIs. The question, and one of the goals of this class, is to determine which one should be used for a given situation.

Lists

Definition of List

A list is:

  • an item
  • an item followed by a list

What is in item? anything. Even an empty item.

Operations on a list:

  • empty the list
  • determine if the list is empty
  • determine size of the list
  • find an element
  • determine value of an element at a particular location
  • insert an element at a particular location
  • remove an item
  • print

There are 2 straightforward implementation of the list ADT with identical APIs. An array structure and a node structure (linked list).

Exercise

Implement both list structures.

Determine the time complexity of each list operation for each structure.

How can we improve/alter the time complexities.

Which version requires more/less memory?

C++ STL has 2 alternatives for list-like structures:

  • vectors (actually dynamic arrays)
  • lists (node structured linked lists)

Each have different performance characteristics. How does a list grow? How does a vector grow?

Stacks

A stack is like a stack of paper on a desk

  1. What operations are available on stacks?
  2. What are their time complexities? Can they be bad?

Stacks can be easily implemented as a list.

Exercise

Convert one of the list implementations to a stack.

Which implementation is better for a stack? why?

Stacks in Practice: Postfix notation

rules:

  • When you see a number, push it
  • When you see an operation,
  • Pop two times
  • Perform operation
  • Push the result

7 6 + 3 5 8 2 / + *

_images/postfix.png

Stacks in Practice: C++ run-time stack

Stores data and call information for nested scopes (often functions).

Grows downward from an origin

Stack pointer points to the top of stack

Every time you enter a new scope, all local variables and some scope info are pushed to the stack

If you know how the stack works, you can be root!

_images/cppCallStack.png

So, with this information, we can create a buffer overflow! This is exactly why most languages protect against array over-runs, but C++ is designed for SPEED, not safety!

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <iostream>

using namespace std;

void f1( int theArray[] ) {
        for( int i = 0; i < 10; i++ )
                theArray[i] = i+20;
}

int main() {


        int a=3, b=4, c=5;
        int array[] = {1,2};
        cout << "a: " << a << endl
                         << "b: " << b << endl
                         << "c: " << c << endl << endl;

        f1( array );

        cout << "a: " << a << endl
                         << "b: " << b << endl
                         << "c: " << c << endl;

}

The output of the above code...

marmcke@beet$ ./a.out
a: 3
b: 4
c: 5
a: 24
b: 23
c: 22
marmcke@beet$

Whats going on?

Queues

Not just an awesome scrabble word!

An ADT organized as a list in which insertions occur at one end and deletions occur at the other.

Think of standing in line.

Applications:

  • printer queue
  • (there is a queue application that most people use every single day, without realizing it. What is it?)

Implementation:

  • Array (usually dynamic). This can be trickier, why? Lets give it a try.
  • Node structure. How do we make constant time operations?

Note:

Queues and stacks are super useful as constructs to prove an algorithm runs in a certain amount of time (big-O). If you can prove that all items are put into a queue or stack exactly once, the running time of the algorithm is the running time of the push and pop operations! usually constant.