A linked list is a linear colletion of elements in sequential order. Imagine a chain, with a start and a end, with the chain links connected to each other. A linked list can be visualized as below:
A linked list is composed of a smaller data structure usually called a Node
. The node object contains the value
of the element itself, and also a pointer to the next
node in the chain. By utilizing node.next
, you can traverse the list. The beginning of a linked list is denoted with head
and the last element is tail
.
In a singly-linked list, the link is established in one direction only, where the node only keeps track of the next node in the chain. This means that with a singly-linked list, you can only traverse in the direction of the tail. On the other hand, a node in a doubly-linked list keeps track of both next
and previous
which allows you to traverse in both directions (towards head
and tail
).
In this exercise, implement the following functions for the LinkedListNode
and LinkedList
classes:
LinkedListNode
constructor()
- Write a method that instantiates the node.
- The node takes
value
andnext
.
LinkedList
constructor()
- Write a method that instantiates the list.
- The instance variables
head
andtail
should be set tonull
.
prepend(value)
- Write a method that inserts the
value
at the beginning of the linked list.
- Write a method that inserts the
append(value)
- Write a method that inserts the
value
at the end of the linked list.
- Write a method that inserts the
delete(value)
- Write a method that deletes the
value
in the linked list.
- Write a method that deletes the
find(value)
- Write a method that returns the
node
that contains thevalue
.
- Write a method that returns the
insertAfter(value, targetValue)
- Write a method that inserts the
node
after thenode
that contains thetargetValue
.
- Write a method that inserts the
deleteHead()
- Write a method that deletes the first element in the linked list.
deleteTail()
- Write a method that deletes the last element in the linked list.
The most important operations are prepend/append
for adding data, delete
for removing data, and find
for retrieving data.
Let's start by opening up a terminal window and running npm run watch
. This will start our TDD framework that will automatically re-run the tests when you save any changes to your file.
- In your code editor, navigate to
/algorithms/src/data-structures/LinkedListNode.js
. - In the constructor method, write
this.value = value
and save the changes. - You will see the tests run automatically in your terminal:
Jest Tip: If you need to filter by the exercise name, press
p
. (pressw
for the list of available commands).
The output tells you that three tests are failing:
- ✕ should create list node with value (10ms)
- ✕ should create list node with object as a value (2ms)
- ✕ should link nodes together
- ✓ should convert node to string
- ✓ should convert node to string with custom stringifier (1ms)
The code for the last two tests (toString()
tests) have been provided for you in the skeleton. Your task is to make the three failing tests to pass.
- The test description says
› should create list node with value
- The output tells us where the test is failing:
expect(node.next).toBeNull();
. It also tells usexpect(received).toBeNull()
andReceived: undefined
. - In this case, the test is
expect
ingnode.next
to benull
. However, the test receivedundefined
. - Since
next
is undefined, let's update ourconstructor()
inLinkedListNode.js
:constructor(value, next = null) { this.value = value; this.next = next; }
- When you save the file, you should see all your tests for
LinkedListNode
pass! - Often, many tests can depend on the same part of the code. In this case, solving for one test case solved all others.
Now, let's open up LinkedList.js
in our editor.
- A linked list should always have a
head
and atail
. - The test tells us:
expect(linkedList.head).toBeNull();
. Let's create the instance variables:constructor() { this.head = null; this.tail = null; }
- The
prepend
method inserts the item at the beginning of the list. - Prepending an item is a simple operation:
- Create a new
Node
. - Set the new
node
's `next to be the current head node. - Update the
head
to point at the new node.
- Create a new
- We also have to consider a case where this is the first item stored in the list. In that case, we also set the tail to the same node.
- The
append
method inserts the item at the end of the list. - Operations:
- Create a new
Node
. - Set the tail's
next
to be the new node. - Update
tail
to point at the new node.
- Create a new
- Again, take into consideration when this is the first item stored in the list.
-
The
find
method returns the node with the target value. -
A node can be visited by utilizing a loop and
node.next
. -
The following snippet traverses the entire list:
let currentNode = node; while (currentNode.next) { currentNode = node.next console.log(currentNode.value) }
-
Think about how the above concept can be applied to find the target node.
- The
insertAfter
method stores theinsertValue
right after the node with thevalue
. - Operation:
- Create a new
Node
. - Find the node with the target value.
- Set the found node's
next
to the new node. - Set new node's
next
to the found node's `next.
- Create a new
- Utilize the previously written
find
method to find the node.
- The
delete
method removes the first node with the specified value. - Operations:
- Traverse the list and locate the node before the target node.
- Remove the target node that is placed after the found node.
- In a linked list, you don't have to delete the actual node.
- Deleting means removing the reference to that node from the list.
- The above diagram shows that all we have to do is set the target node's previous node's
next
to point at the target node'snext
. - This means that we must locate the node right before the target node. Think about how you would be able to tell when the next node will be the target node.
- Also, take into consideration if the
head
ortail
node is the node to be deleted.
- This is the most complex operation in a linked list. Drawing the operation on paper can help you visualize the problem. Don't hesitate to reach out to #basic-algorithms channel on Slack if you are stuck.
- The
deleteHead/Tail
methods are utility methods for common operations.
This section will be released in the future when the Big O notation tutorials are written. Please join #basic-algorithms channel for the update notifications.