Nontraversable Data Structures

CSC 385 - Data Structures and Algorithms

Brian-Thomas Rogers

University of Illinois Springfield

College of Health, Science, and Technology

Objectives

Objectives

  • Know what a nontraversable data structure is
  • Understand the fundamentals of the stack, queue, and deque
  • Understand how stacks and queues can be implemented using an array or using a set of linked nodes
  • Understand how circular arrays work and why they are useful

Nontraversable

Nontraversable

  • A nontraversable data structure limits the data which a user can access from it
  • Consider an array which you can only access the very first item until it is removed then you can access the next, and so on
  • The goal of nontraversable data structures is
  • The three datastructures in this lecture are nontraversable
    • Stacks
    • Queues
    • Deques

Utility Methods

  • These methods are nice to have and are often available in most common data structures
  • void clear(): Clears the data structure
  • int getSize(): Returns the value of the size attribute if one exists else needs to calculate the size some other way
  • boolean isEmpty(): Returns whether the structure is empty (true) or not (false)
  • String toString(): Overriding this method is nice since it will allow debugging

Note

These utility methods will be seen over and over and over again! They are great starter methods to begin with as they get used a lot!

Stack

Stack Basics

  • A stack is a last in first out (LIFO) data structure
  • The last element added will be the first one removed
  • Classic example is a tray dispenser at a cafeteria
    • A new tray is always added at the top
    • You can only take the tray that is on top which was the most recently added tray
  • In order to access any item you must first remove all items on top of it in the stack

Stack Applications

  • Simulating recursion
    • Allows solving recursive problems without needing to write recursive methods
  • Algebraic expression evaluation and syntax parsing in compilers
  • Certain pathfinding algorithms

Main Stack Operations

  • This operations are given their canonical names but is not necessary to do so
    • All that matters is that a stack acts like a stack not that the names are the same
  • push(): This adds a new item to the stack
  • pop(): This both removes and returns the top item of the stack
  • peek(): This returns the item on the top of the stack

Stack Interface

/**
 * The canonical stack methods are provided
 * peek == get
 * pop == remove
 * push == add 
 * 
 * Written by Roger West, editted by Elham S. Buxton and
 * Brian-Thomas Rogers
 * University of Illinois at Springfield
 * Adapted from code written by Mark Weiss in 
 * Data Structures and Problem Solving using Java 2nd edition 2002
 */ 

public interface Stack<T> {
    
    //return the item at the top of the stack; the item is not removed
    public T peek();

    //remove the item at the top of the stack and return it
    public T pop();

    //add obj to the top of the stack
    public boolean push(T obj);
}

Array-Based Stack

Array Stack

  • Array stack is called so because the underlying collection used is an array
  • This presents at least two challenges (and is the same for every array based data structure)
    • The length of the array is not the size of the stack
    • Making the stack dynamically sized even though the array is statically sized

Array Stack Attributes

  • T stack[]: The underlying array that represents the stack
  • int topOfStack: Keeps track of the top of the stack
  • int size: Keeps track of the size of the stack which is just the number of elements in the stack
  • final int DEFAULT_CAPACITY: A constant that is the initial capacity of the stack array

Required Method

  • For array based data structures, there is at least 1 required method
  • That method is used to increase the size of the array
    • Arrays in Java are statically sized so what happens is an array is created that is larger than the original array and the elements copied to this array
  • This lecture will call that method resizeArray and will be private

Array Stack Constructor

  • The constructor of ArrayStack calls the clear method which
    • creates a new empty array
    • Resets size to zero
    • Sets topOfStack to an invalid array index

ArrayStack Peek

  • Retrieves the item at the top of the stack
  • First checks to see whether the stack is empty and if so it throws an UnderflowException or EmptyStackException
  • Has \(O(1)\) running time
  • This is achieved by return stack[topOfStack]

ArrayStack Push

  • Adds an item to the top of the stack
  • First checks to see whether stack[] has reached capacity (size == stack.length)
    • If so, call resizeArray method
  • Inserts the new item at index topOfStack + 1 in stack[]
  • Increments topOfStack
  • Increments size
  • Running time is \(O(1)\) if resizing the array happens infrequently

ArrayStack Pop

  • Removes an item from the top of the stack and returns it
  • Checks whether the stack is empty
    • If it is throw an UnderflowException or EmptyStackException
  • Store item currently at top in another variable
  • Decrement size
  • Decrement topOfStack
  • Set item at topOfStack + 1 to null
  • Return variable that is holding the removed element
  • Running time is \(O(1)\) since no elements need to be shifted

LinkedStack Attributes

  • LinkedStack requires another method of data storage using linked nodes
  • A node is simply a class which typically holds a piece of data and references to other nodes
  • The first required attribute is an inner class called StackNode
  • StackNode has the following attributes
    • T data: Stores a piece of data
    • StackNode previous: A reference to the previously added item
  • Once the StackNode class is created you can have the following attributes for the LinkedStack class
    • StackNode topOfStack: Reference to the top node of the stack
    • size: Current number of elements in the stack

LinkedStack Constructor

  • Constructor, like the ArrayStack, calls the clear method.
  • Clear method
    • set the topOfStack node to null
    • set size to zero

LinkedStack Peek

  • Returns the item in topOfStack
  • Has \(O(1)\) runtime
  • Throws an UnderflowException or EmptyStackException if the stack is empty
  • Achieved by return topOfStack.data

LinkedStack Push

  • Adds a new element to top of stack
  • Creates a new StackNode with obj data
  • Point the new node’s previous to topOfStack
  • Point topOfStack to the newly created node
  • Increments size
  • returns true
  • Has \(O(1)\) runtime

LinkedStack Pop

  • Removes and returns element from top of stack
  • Throws UnderflowException or EmptyStackException if stack is empty
  • Create temporary node to point to topOfStack
  • Move topOfStack to point to previous node
  • Set temporary node’s previous to null
  • Return temporary node’s data
  • Has \(O(1)\) runtime

Runtime Summary

Method ArrayStack LinkedStack
peek \(O(1)\) \(O(1)\)
push \(O(1)\) (array not full) or \(O(n)\) (array full) \(O(1)\)
pop \(O(1)\) \(O(1)\)
clear \(O(1)\) \(O(1)\)
isEmpty \(O(1)\) \(O(1)\)
getSize \(O(1)\) \(O(1)\)
resizeArray \(O(n)\) N/A

Queues

Queue Basics

  • Queues are a first in first out (FIFO) data structure
    • Items can only be added to the end and removed from the front
    • Just like a queue in real life
      • e.g. people waiting in line to get on a rollercoaster
  • To get an item you have to remove all the items in the front first

Main Queue Applications

  • Task scheduling
  • Multiprocessing operations
  • Pathfinding algorithms

Queue Interface

/**
 * The canonical queue methods are provided
 * getFront == get
 * dequeue == remove
 * enqueue == add
 * 
 * Written by Roger West, editted by Elham S. Buxton and
 * Brian-Thomas Rogers
 * University of Illinois at Springfield
 * Adapted from code written by Mark Weiss in 
 * Data Structures and Problem Solving using Java 2nd edition 2002
 */ 

public interface Qeueue<T> {
    
    //return the item at the front of the queue; not removed
    public T getFront();

    //remove and return the item at the front of the queue
    public T dequeue();

    //add obj to the end of the queue
    public boolean enqueue(T obj);
}

ArrayQeueue

ArrayQueue Attributes

  • T queue[]: Array to store the elements
  • int size: Number of elements in the queue
  • final int DEFAULT_CAPACITY: integer constant of the initial capacity of the queue[] array
  • int front: Index of the front of the queue
  • int back: Index of the back of the queue

Adding and Removing

0 1 2 3 4 5 6 7
A B C
front back

Add “D”

0 1 2 3 4 5 6 7
A B C D
front back

Remove “A”

0 1 2 3 4 5 6 7
B C D
front back

ArrayQueue as Circular Array

  • When the back index reaches the end of the array, if there are empty cells at the beginning of the array then the back index is wrapped to 0
  • If the array is full then we need to resize the array

Circular Array

ArrayQueue GetFront

  • Retrieves the item at the front of the queue
  • If queue is empty, throws an EmptyQueueException
  • Has \(O(1)\) runtime

ArrayQueue Enqueue

  • Add an element to the end of the queue
  • If the array is full then the array should be resized
  • Increment the back index (wrapping to 0 if necessary)
  • Store the new item at the new back index
  • Increment the size
  • Return true
  • Has \(O(1)\) runtime if no resize is necessary

ArrayQueue Dequeue

  • Removes and return the front item
  • Throw an EmptyQueueException if the queue is empty
  • Store the current front element in a temporary variable
  • Set front to null
  • Increment front (wrapping to 0 if necessary)
  • Decrement size
  • Return data in temporary variable
  • Has \(O(1)\) runtime

Linked Based Queue

LinkedQueue Attributes

  • Much like LinkedStack, LinkedQueue requires a node structure
  • QueueNode has two attributes
    • T data: stores the data element
    • QueueNode next: references the next node in the queue
  • LinkedQueue Attributes
    • QueueNode front: Reference to the front of the queue
    • QueueNode back: Reference the back of the queue
    • int size: Stores the number of elements in the queue

LinkedQueue Constructor

  • Calls clear method
  • Clear method
    • Sets front and back to null
    • Sets size to 0

LinkedQueue GetFront

  • Retrieves the element from the front node
  • Throws an EmptyQueueException if the queue is empty
  • Has a \(O(1)\) runtime

LinkedQueue Enqueue

  • Adds a new element to the queue
  • Create a new node with the passed in object
  • If the queue is empty then set front and back nodes to the new node
  • Else set back node’s next to the new node
  • Then set the back node to the new node
  • Increment size
  • Has a \(O(1)\) runtime

LinkedQueue Dequeue

  • Removes and returns the front element of the queue
  • If queue is empty, throw an EmptyQueueException
  • Create temp node that points to the front
  • Move front node to next node
  • Set temp node’s next to null
  • Decrement size
  • Return temp node’s data element
  • Has \(O(1)\) runtime

Queue Runtime Summary

Method ArrayQueue LinkedQueue
getFront \(O(1)\) \(O(1)\)
enqueue \(O(1)\) (array not full) or \(O(n)\) (array full) \(O(1)\)
dequeue \(O(1)\) \(O(1)\)
clear \(O(1)\) \(O(1)\)
isEmpty \(O(1)\) \(O(1)\)
getSize \(O(1)\) \(O(1)\)
resizeArray \(O(1)\) N/A

Deque

Deque

  • Pronounced “deck”
    • Stands for double-ended queue
  • Allows for adding and removing from both ends of the collection

Deque Applications

  • The undo operation
    • Undoing an action corresponds to removing the most recent action
    • A limit needs to be imposed on how many actions are remembered
    • Need to remove the oldest action (front of deque) to make room for new actions (back of deque)
    • We also need to be able to remove the most recent action
    • In other words, we want to remove elements from both the front and back
    • Stack is not good for this because to the the oldest item would take \(O(n)\) time
    • Queue is not good for this because it would take \(O(n)\) time to reach the most recent item

Deque As Undo

Deque as Undo

Deque Interface

public interface Deque<T> {

    //add obj to front of deque
    public void addFront(T obj);

    //add obj to back of deque
    public void addBack(T obj);

    //remove and return from front of deque
    public T removeFront();

    //remove and return from back of deque
    public T removeBack();

    //return element at front of deque
    public T getFront();

    //return element at back of deque
    public T getBack();

}

Array Based Deque

ArrayDeque

  • ArrayDeque can be derived from ArrayQueue
public class ArrayDeque<T> extends ArrayQueue<T> { }
  • No new attributes are needed because ArrayQueue has everything needed
    • This means certain attributes and methods may need to have the protected access modifier

ArrayDeque Constructor

  • Calls clear
  • This method is inherited from the ArrayQueue class

ArrayDeque Inheritance

  • These methods do not need to be changed as they are inherited from ArrayQueue
    • getFront
    • isEmpty
    • getSize
    • toString
    • clear

ArrayDeque GetBack

  • Returns the item in the back of the deque
  • Throws an exception if the deque is empty
  • Has \(O(1)\) running time

ArrayDeque AddFront

  • Check if array is full
    • Resize array if it is
  • If deque is empty, add element to front
  • Otherwise, decrement front index (wrapping to end of array if necessary)
    • Then add element to front
  • increment size
  • Has \(O(1)\) running time

ArrayDeque AddBack

  • Calls enqueue of the parent class
  • Has same runtime as the enqueue method

ArrayDeque RemoveFront

  • Calls the dequeue method of the parent class
  • has same runtime as the dequeue method

ArrayDeque RemoveBack

  • Throw exception if deque is empty
  • Store element in temp variable from back index
  • Decrement back index (wrapping to end of array if necessary)
  • Set previous back index position to null
  • Decrement size
  • Return temp variable
  • Has \(O(1)\) runtime

Array vs Linked Based Implementations

Array vs Linked

  • For either implementation all the methods for stack, queue, and deque run in O(1) time. However, When the array must be doubled the running time is increased to O(n). Hopefully this happens infrequently.

  • For the most part, the array based implementations must run slightly faster than linked based implementation, because of the random access nature of the array.

  • One disadvantage to the array-based implementations is the fact that they require contiguous blocks of memory, which may not always be available.

DNE

pop(title)
pop(title)
pop(title)