Doubly Linked Lists

Overview - Assume we have a , and now we want to append a node to the end of the list. What we can do to make it happen? [We need to have a temporary, use it to go through the list, and make it points at the last node of the list. Then update the next pointer of the last node and set it points to the new node.] - This means if we have a long list, it will take a while to find the last node and do the operation. Same thing happens to removing the last node. The time complexity depends on the size of the list.

- How can we modify the linked list, so that it will be easier to access the tail of the list? • Add a tail pointer. - Or we can make it even more convenient in a way that we can move forward and backward in the list. To achieve this, we can add a prev pointer to node in addition to the next pointer. - And this is what we called doubly linked lists.

- Pros: more flexible - Cons: more memory and maintenance of references needed

- Note: Header and trailer are sentinel nodes. A sentinel node is a specifically designed node used with linked lists and trees as a traversal path terminator. This type of node does not hold or reference any data managed by the data structure.

- Each node in a doubly linked list contains three fields: the data, and two pointers: prev and next.

Private inner class Node private class Node { private Node prev; private Node next; private T data;

public Node (T item) { data = item; next = null; prev = null; } //getters and setters … }

Diagram Example - Original doubly linked list

- Delete head node

- Delete middle node

- Delete last node

- Please node: when adding/removing a node, it is necessary to update both prev and next pointers of the node and the two pointers of the node before it if there is one.

Implementation Example /* Returns the element at the specified position in the list */ public T get (int index) { if (index < 0 || index >= size) return null;

Node n = head; for (int i = 0; i < index; i++) { n = n.getNext(); }

return n.getData(); }

/* Removes and returns the first element from the list */ public T removeFirst () { if (head == null) return null;

T t = head.getData(); head = head.getNext(); if (head != null) { // there are more than one nodes in the list head.getPrev().setNext(null); head.setPrev(null); } else { tail = head; } size--;

return t; } Queues

Overview - A queue is a FIFO sequence. Addition tasks place only at the tail, and removal takes place only at the head. - Like a stack, queue can contain any object (Generics). - The basic operations for queues are identical to those for stacks, except the standard name for adding elements into a queue is “enqueue” (instead of push) and the name for removing elements from a queue is “dequeue”.

- The basic operations are : • enqueue(x): add an item at the tail • dequeue(): remove the item at the head (poll) • peek(): return the item at the head (without removing it) • size(): return the number of items in the queue • isEmpty(): return whether the queue has no items

Queue Interface - Since all queues provide those basic operations, we can actually implement an interface for queue, which contains all the method signatures for the basic operations. So, we can make sure those queue classes that implement the interface will provide the basic operations at least. - Java 8 also provides a Queue interface. This interface inherits size and isEmpty from another interface called Collection. It also provide two set of methods to enqueue, dequeue, and peek. One set returns a special value if the operation fails. The other set throws exception. - In the Lab 5, you will implement your queue, using the idea of the set that returns a special value if the operation fails. But, you do not need to implement the Queue interface in this lab, as we only implement one set of methods.

Applications of Queues - Line of cars at a light - Line of people at cafe - Printer buffer

Implementation of Queues - We can use many ways to implement queues: • Array: enqueue is easy to dequeue requires shifting the rest in the array • ArrayList: enqueue is easy, ArrayList takes care of the shifting cased by dequeue, resize memory is necessary • SinglyLinkedList: dequeue is easy, but enqueue need to traverse to the end of the list • DoublyLinedList: enqueue (addLast) and dequeue (removeFirst/remove(0)) are easy.

Disclaimer: Notes adapted from previous CS 231 lecture materials at Colby College.