Project 2: Linked Lists
(Return to homepage)
In Project 2, you'll implement a Doubly Linked List, a list-like data structure that enables iteration in
both forward and reverse directions.
The Doubly Linked List is just like the (Singly) Linked List, with a slight addition. The internal nodes
inside the Doubly Linked List not only point forward to the next node like a Linked List, but also point
backward to the previous node in the list.
This enables clients of the Doubly Linked List to iterate in both directions. Furthermore, a node can be
inserted and removed from the list-like
with just a reference to the node itself, rather than the complete list, as a node can re-link both of its
neighbors.
Your task is to implement a complete Doubly Linked List that provides an interface supporting operations
like
insertion, deletion, and iteration in both directions. This project not only involves extensive use of
references
to track and organize internal data nodes, but also offers several practical examples on how a data
structure's performance (Big O)
can vary based on a client's needs (i.e. insertion vs. iteration). For those two reasons, Linked Lists are
commonly featured
in technical interviews to evaluate a candidate's understanding of references, runtime, and the trade-offs
between ways data can be organized.
Ready? It's interview day. Get the Project 2 starter code
here! (jUnit dependencies included)
The project code should work out-of-the-box for IntelliJ (running and debugging), but if you're having
trouble getting it working, try these IntelliJ setup instructions.
Classes
linkedlist.LinkedList<T>: A generic, concrete Doubly Linked List class.
Implements Iterable.
-
ListNode<T>: An internal concrete class that contains the state of a single
node in the Linked List. Contains a generic payload value, and references
to the next and previous nodes in the list.
- LinkedList(): Default Linked List constructor. Constructs an empty list
with no values.
- LinkedList(Iterable<? extends T>): Iterates over elements of the
Iterable argument to construct a list containing the same elements supplied by the Iterable, in the
same order as iteration.
- T peekFront(): Returns the front element value of the list and null if no such element exists.
- T peekBack(): Returns the back element value of the list and null if no such element exists.
- T popFront(): Removes and returns the front element value of the list and
throws NoSuchElementException() if no such element exists.
- T popBack(): Removes and returns the back element value of the list and
throws NoSuchElementException() if no such element exists.
- void pushBack(T): Appends a new element to the back of the list containing
the argument value.
- void pushFront(T): Appends a new element to the front of the list
containing the argument value.
- void add(T): Same as pushFront(T).
- void add(int, T): Inserts an element with the argument value at the
specified index, shifting the existing element and all successive elements at the given index
forwards. If the index is less than zero or exceeds the size of the list, throws IndexOutOfBoundsException().
- T remove(): Same as popFront().
- T remove(int): Removes and returns an element at the specified index, shifting all successive elements backwards (in effect, the opposite of add(index, T)). If the index is less than zero or exceeds the size of the list, throws IndexOutOfBoundsException().
- int size(): Returns the size of the list.
- iterator<T>: Returns a new LinkedListIterator.
- LinkedListIterator: An Iterator that enables iteration over the Linked List instance. Implements hasNext(), next(), and remove(). See official Java Iterator documentation for details.
- reverseIterator<T>: Returns a new LinkedListReverseIterator.
- LinkedListReverseIterator: An Iterator that enables iteration over the Linked List instance, but in reverse order. Implements hasNext(), next(), and remove(). See official Java Iterator documentation for details.
Bonus Classes
This project features two additional classes that you may optionally implement for up to +15 bonus points. You are eligible to earn bonus points even if your Linked List implementation is not fully complete. These classes are the Stack and Queue, which are natural clients of the Linked List that can take advantage of O(1) append and remove. By implementing both, you will explore the differences between containment and extension, two different strategies of re-using a base class's behavior.
-
Stack: A generic, concrete class describing a stack of items. A stack is a last-in-first-out (LIFO) queue. Elements are "pushed" onto the top and "popped" from the top in reverse order or their addition, much like a stack of dinner plates in real life.
Stack should contain a LinkedList as a private variable, leveraging its public interface to support all Stack operations and store elements. Stack will provide the following public methods:
- void push(T): Pushes an element onto the top of the stack.
- T pop(): Removes and returns the element from the top of the stack.
- T peek(T): Returns (but does not remove) the element from the top of the stack.
- int size(): Returns the size of the stack.
-
Queue: A generic, concrete class describing a queue of items. A stack is a first-in-first-out (FIFO) queue. Elements are "pushed" onto the queue and "popped" in the same order of addition, much like a queue of people in real life.
Queue should extend LinkedList. as a private variable, leveraging its inherited methods to support all Queue operations and store elements. Queue will provide the following public methods:
- void add(T): Pushes an element to the back of the queue.
- T remove(): Removes and returns the element from the front of the queue.
- T peek(T): Returns (but does not remove) the element from the front of the queue.
- int size(): Returns the size of the queue.
Unlike Stack, Queue may not reveal methods that modify its internal state and disrupt the expected behavior of the queue. Any methods, inherited or otherwise, that allow the queue to become inconsistent, should throw a RuntimeException().
Running Your Code
As with the previous projects, this project contains a full test suite (see the test package) that will evaluate your code.
A run script for the entire project test suite is included as run.bat
(Windows) or
run.sh (Mac/Linux). This will compile your entire project and produce a run
report
with your estimated grade, including bonus points. Your entire project must compile for the script to work, and your entire project must compile to receive a grade after submission.
You are welcome to modify the tests for debugging purposes, though keep in mind your project will be graded using an unmodified copy of the test suite.
Submission Instructions
See the submissions page for details. Have questions or need help? Post on Piazza!
Critical Analysis Questions
These questions aren't graded, but are similar to those you may see on an exam. You are welcome to
discuss
these with classmates offline on on Piazza.
- How would you reverse a singly linked list?
- How would you reverse a doubly linked list?
- Why are both Linked List append and remove considered O(1), even though indexing is O(n)?
- What use cases would Linked List be good for over ArrayList?
- Java's garbage collector automatically "frees" the memory of taken by Object instances which no
longer have references pointing to them. However, it is still possible to have a memory leak: an
build-up of memory usage, composed of unused Object instances never garbage collected. How might
this happen in Java because of a poorly implemented Linked List method?
- What are the implementation and usage differences between containment (Stack) and extension (Queue)?
- Which of the SOLID principles we have covered thus far does the Queue violate?
- How might you implement Queue with only access to the Stack class?
(Return to homepage)