Stacks and Queue

CSCI 1933 – Introduction to Algorithms and Data Structures
Adriana Picoral

Stacks

Stacks

│         │
│         │
│         │  
├─────────┤    
│    D    │    ← top
├─────────┤   
│    C    │  
├─────────┤  
│    B    │ 
├─────────┤  
│    A    │  
└─────────┘  

Stacks

│    ↓    │
├─────────┤   
│    E    │    ← top
├─────────┤    
│    D    │
├─────────┤   
│    C    │  
├─────────┤  
│    B    │ 
├─────────┤  
│    A    │  
└─────────┘       

push(E)

Stacks

│    F    │    ← top
├─────────┤   
│    E    │
├─────────┤    
│    D    │
├─────────┤   
│    C    │  
├─────────┤  
│    B    │ 
├─────────┤  
│    A    │  
└─────────┘       

push(F)

Stacks

│         │
├─────────┤   
│    E    │    ← top
├─────────┤    
│    D    │
├─────────┤   
│    C    │  
├─────────┤  
│    B    │ 
├─────────┤  
│    A    │  
└─────────┘   

pop() → returns F
returns top and updates it (removes top)

Stacks

│         │
│         │
│         │  
├─────────┤    
│    D    │    ← top
├─────────┤   
│    C    │  
├─────────┤  
│    B    │ 
├─────────┤  
│    A    │  
└─────────┘  

pop() → returns E returns top and updates it (removes top)

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

First In First Out                                                                                                                   

back  ┌───┬───┬───┬───┐ front
─────▶│ D │ C │ B │ A │──────▶
      └───┴───┴───┴───┘

Queues

  enqueue(E)                             
  adds to back  
       
       
back  ┌───┬───┬───┬───┬───┐  front
─────▶│ E │ D │ C │ B │ A │──────▶
      └───┴───┴───┴───┴───┘

Queues

dequeue()
removes from front


back  ┌───┬───┬───┬───┐  front    returns A
─────▶│ D │ C │ B │ A │──────▶
      └───┴───┴───┴───┘
      
            │
            ▼  after dequeue
back  ┌───┬───┬───┐  front
─────▶│ D │ C │ B │──────▶
      └───┴───┴───┘

Queues

peek()                       
returns front, no removal

 back  ┌───┬───┬───┬───┐  front   
 ─────▶│ D │ C │ B │ A │──────▶    
       └───┴───┴───┴───┘      
                  
              returns A      
              does NOT          
              remove it             

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
    • enqueue: back = (back + 1) % capacity
    • dequeue: front = (front + 1) % capacity
  • This allows for the utilization of the entire array
  • Array is ful if number of count = array.length

Resizing?

Circular Array

   0   1   2   3   4
 ┌───┬───┬───┬───┬───┐  
 │   │   │ A │ B │   │
 └───┴───┴───┴───┴───┘
 
 front index: 2
 back index: 3
 

Circular Array

   0   1   2   3   4
 ┌───┬───┬───┬───┬───┐  
 │   │   │ A │ B │ C │
 └───┴───┴───┴───┴───┘
 
 front index: 2
 back index: 4
 

Circular Array

   0   1   2   3   4
 ┌───┬───┬───┬───┬───┐  
 │ D │   │ A │ B │ C │
 └───┴───┴───┴───┴───┘
 
 front index: 2
 back index: 0

Circular Array

   0   1   2   3   4
 ┌───┬───┬───┬───┬───┐  
 │ D │   │   │ B │ C │
 └───┴───┴───┴───┴───┘
 
 front index: 3
 back index: 0
 

Circular Array

   0   1   2   3   4
 ┌───┬───┬───┬───┬───┐  
 │ D │ E │   │ B │ C │
 └───┴───┴───┴───┴───┘
 
 front index: 3
 back index: 1

Array interface and ArrayQueue implementation

Submit your Array.java and ArrayQueue.java files to gradescope

ArrayQueue

Problem: this is not dealing with what happens 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 = (front + 1) % size;
        count--;
        return out;
    }

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

    @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();
    }
}