Stacks and Queue

CSCI 1933 – Introduction to Algorithms and Data Structures
Adriana Picoral

Stacks

Stacks

  • Can be implemented as arrays or linked lists
  • Features – push() and pop(), others?
  • LIFO – Last In, First Out

Write up a generic stack interface

Solution

public interface Stack<T> {

    /**
     * Stack is LIFO – Last In, First Ou
     * add a new value (with T data) to the top of the stack
     * @param data of generic type T
     */
    void push(T data);

    /**
     * Removes and returns the value at the top of the stack
     * @return the value stored in the node at the top of the stack
     */
    T pop();

    /**
     * Looks at the value at the top of the stack without removing it
     * @return the value stored in the node at the top of the stack
     */
    T top();
  
   /**
     * @return true if Stack is empty, false otherwise
     */
    boolean isEmpty();
}

Implementation of the Stack Interface

  • Stack as a linked list vs. array implementation
  • Complexity – how does runtime compare?
  • Which is better (array or linked list)?

Submit your Stack.java, ArrayStack.java and LinkedStack.java to gradescope

Array Implementation (with issues)

public class ArrayStack<T> implements Stack<T>{
    private T[] stack;
    private int count;

    public ArrayStack(int size) {
        stack = (T[]) new Object[size];
        count = 0;
    }

    @Override
    public void push(T data) {
        stack[count] = data;
        count++; // what happens when count is equal or greater than size?
    }

    @Override
    public T pop() {
        if (count == 0) return null;
        count--;
        return stack[count];
    }

    @Override
    public T top() {
        if (count == 0) return null;
        return stack[count-1];
    }

    @Override
    public boolean isEmpty() {
        return count == 0;
    }

    @Override
    public String toString() {
        String out = "";
        for (int i=count-1; i >= 0; i--)
            out += "| " + stack[i] + "\t|\n";
        return out;
    }
}

Linked List Implementation

Download Node.java

public class LinkedStack<T> implements Stack<T>{
    private Node<T> top;

    public LinkedStack() {
        top = null;
    }

    @Override
    public void push(T data) {
        Node<T> newTop = new Node<>(data);
        newTop.setNext(top);
        if (!isEmpty()) top.setNext(top.getNext());
        top = newTop;
    }

    @Override
    public T pop() {
        if (!isEmpty()) {
            T out = top.getData();
            top = top.getNext();
            return out;
        }
        return null;
    }

    @Override
    public T top() {
        if (!isEmpty()) return top.getData();
        return null;
    }

    @Override
    public boolean isEmpty() {
        return top == null;
    }

    @Override
    public String toString() {
        String out = "";
        Node<T> currNode = top;
        while (currNode != null) {
            out += "| " + currNode.getData() + "\t|\n";
            currNode = currNode.getNext();
        }
        return out;
    }
}

Stack Applications

  • Traversing a maze, a graph, or a tree
  • Expression evaluation (infix <–> postfix)
  • Implementing a recursive process using iteration (and a stack)

Queues

Queues

  • FIFO – First In, First Out
  • Queue basics:
    • enqueue() (adds to the end of the queue)
    • dequeue() (removes from the beginning of the queue)

Write up a generic queue interface

Solution

public interface Queue<T> {
    /**
     * Queue is First In, First Out
     * @param data is the value to be added to the end of the queue
     */
    void enqueue(T data);

    /**
     * Removes the value from the beginning of the queue and returns it
     * @return the value of type T at the beginning of the queue
     */
    T dequeue();

    /**
     * @return true if Queue is empty, false otherwise
     */
    boolean isEmpty();
}

Implementation of the Queue Interface

Queue as a linked list vs. array implementation

  • Linked List implementation – keep head and tail like we did for our first Linked List implementation
  • Array queue implementation requires some creativity because the “filled” portion of the array will tend to “move” upwards in the array with potential for lots of wasted space

Circular Array

  • Keep track of two array indices: front, rear
  • Front index is not necessarily < rear index
  • Use % operator to “wrap” around the array (for toString for example)
  • This allows for the utilization of the entire array
  • Array is ful if number of count = array.length

Resizing?

ArrayQueue

Problem: this is not dealing with what happend when count = array.length

public class ArrayQueue<T> implements Queue<T>{
    private T[] queue;
    private int count;
    private int front;
    private int rear;
    private int size;

    public ArrayQueue(int size) {
        queue = (T[]) new Object[size];
        count = 0;
        front = 0;
        rear = 0;
        this.size = size;
    }

    @Override
    public T dequeue() {
        T out = queue[front];
        queue[front] = null;
        front++;
        count--;
        if (front == size) front = 0;
        return out;
    }

    @Override
    public void enqueue(T data) {
        queue[rear] = data;
        count++;
        rear++;
        if (rear == size) rear = 0;
    }

    @Override
    public boolean isEmpty() {
        return count == 0;
    }

    @Override
    public String toString() {
        String out = "";
        for (int i=front;i<front+count;i++) {
            if (queue[i%size] != null) out += i%size + " - " + queue[i%size] + " ";
        }

        return out.trim();
    }
}

Checked vs. un-checked exceptions

  • Checked: “Something outside your code went wrong” — file missing, network dropped, database unreachable. The compiler reminds you to have a plan.
  • Unchecked: “Your code has a bug” — you accessed a null object, went out of bounds, passed a bad argument. The fix is to write better code, not to catch the exception.

Checked Exception Handling

  • Checked exceptions must be checked!
  • Methods that throw checked exceptions must have a throws clause on signature
public void readFile(String path) throws IOException {
      FileReader file = new FileReader(path);  // throws checked IOException
}
  • But, not if the checked exception is caught
public void readFile(String path) {
      try {
          FileReader file = new FileReader(path);
      } catch (IOException e) {
          System.out.println("File not found: " + e.getMessage());
      }
  }

Unchecked Exceptions

The compiler does not require you to handle these. They typically represent bugs in your code — things that should not happen if the program is written correctly.

// No compiler warning -- blows up at runtime if array is length 3
  public void getElement(int[] arr) {
      System.out.println(arr[10]);  // ArrayIndexOutOfBoundsException
  }

Exceptions

  • Exception “hierarchy” based on inheritance
  • “Throwing” vs. instantiating an exception
  • Try/catch blocks and cascading catch blocks
  • The finally statement – executes ALWAYS

You can build your own Exception class

Throwing new vs. Throwing existing

What is the advantage to creating an entirely new Exception class (and throwing it) over just throwing an instance of an existing Exception with a custom “message?”