Linked List Algorithms Using Object References
Insertion in Order
When inserting a new node into the sorted linked list, we need to consider three scenarios:
- A new node at the beginning of the list
- Set the new node's next to the current head.
- Update the head to the reference to the new node.
- A new node in the middle of the list
- Get the node that should be before the new node (let's denote that node A).
- Store temporarily a reference of the next node of A (let's denote that node B).
- Hence, the new node should be between node A and node B.
- Set the next attribute of the new node to the reference of node B.
- A new node at the end of the list.
- Find the last element.
- Change the element's next attribute to reference of the new node.
public class LinkedList{
public static class Node{ // ideally in seperate file
public int value;
public Node next;
public Node(int valueIn){ // constructor with passed value
this.value = valueIn;
this.next = null;
}
}
private Node head; // default null
public void add(int newValue){
Node newNode = new Node(newValue);
// insertion at the beginning
if (this.head == null){ // if list is empty
this.head = newNode; // move head pointer
return; // stop method execution
}
if (this.head.value > newValue){ // if new value is smaller then it is still insertion at the beginning, but newNode's next should be updated
newNode.next = this.head;
this.head = newNode;
return; // stop method execution
}
// insertion in the order in the middle
Node currentNode = this.head;
while (currentNode.next != null){ // iterate through until last
if (currentNode.next.value > newValue){ // (currentNode.next).value
// position found
Node temp = currentNode.next;
currentNode.next = newNode;
newNode.next = temp;
return; // stop method execuiton as action was completed
} else {
currentNode = currentNode.next; // move on to the next node
}
}
// if newNode should be added to the end, then code here is executed
// here currentNode is the last node (after iteration above)
currentNode.next = newNode;
}
public void print(){ // iterate through list and output values
String res = "";
if (this.head == null){
System.out.println("List is empty");
return; // stop method execution
}
Node currentNode = this.head;
while (currentNode != null){ // iterate through
res += currentNode.value + " ";
currentNode = currentNode.next; // move on to the next node
}
System.out.println(res);
}
public static void main(String[] args){ // test the code
LinkedList myList = new LinkedList();
int[] values = {5, 2, 6, 3, 1, 10, 7, 9, 8, 4};
for (int i : values){ // for i in values
myList.add(i);
}
myList.print(); // output list
}
}
In some cases, insertion at the beginning and the end can be handled by separate functions addHead() and addTail().
Delete Node
Removes a node with a specific value from the list.
Algorithm
- If the list is empty, return.
- If the head contains the target value, update the head to the next node.
- Otherwise, traverse the list to find the node and update the previous node's next reference (if we need to delete the node in the end, it will automatically update to null reference).
public void delete(int valueOut){
if (this.head == null){// if list is empty stop method execution
return;
}
if (this.head.value == valueOut){ // if value to remove is at the beginning
this.head = this.head.next; // move reference to next node
return; // stop method execution
}
Node currentNode = this.head; // already skip the element at the beginning as we compared it before
while (currentNode.next != null){ // iterate through whole list except last element
Node nextNode = currentNode.next;
if (nextNode.value == valueOut){ // if next node is node that we need to delete
currentNode.next = nextNode.next; // skip node
return; // stop method execution
}
currentNode = currentNode.next; // move to next node
}
System.out.println("Element not found");
}List Empty
Sometimes, it is more useful to move the check if the list is empty or not to a separate function.
public boolean isEmpty(){
return this.head == null; // returns true is head is empty (no elements)
}Additionally, if there is a limit on how many elements can be present in the list, we can implement an element count and the isFull() method that returns a boolean (whether the number of elements equals the limit).
Practical Considerations
- Efficiency: Operations like addHead are O(1), while addTail and delete can be O(n) due to the traversal.
- Memory management: Unlike arrays, linked lists do not require contiguous memory but incur overhead due to the references.
- Use cases: Linked lists are ideal for applications that require frequent insertions and deletions.
Linked lists are not suitable for random access operations, as they require traversal from the head.
Full Working Code
public class LinkedList{
public static class Node{ // ideally in seperate file
public int value;
public Node next;
public Node(int valueIn){ // constructor with passed value
this.value = valueIn;
this.next = null;
}
}
private Node head; // default null
public void print(){ // iterate through list and output values
String res = "";
if (this.head == null){
System.out.println("List is empty");
return; // stop method execution
}
Node currentNode = this.head;
while (currentNode != null){ // iterate through
res += currentNode.value + " ";
currentNode = currentNode.next; // move on to the next node
}
System.out.println(res);
}
public void add(int newValue){
Node newNode = new Node(newValue);
// insertion at the beginning
if (this.head == null){ // if list is empty
this.head = newNode; // move head pointer
return; // stop method execution
}
if (this.head.value > newValue){ // if new value is smaller then it is still insertion at the beginning, but newNode's next should be updated
newNode.next = this.head;
this.head = newNode;
return; // stop method execution
}
// insertion in the order in the middle
Node currentNode = this.head;
while (currentNode.next != null){ // iterate through until last
if (currentNode.next.value > newValue){ // (currentNode.next).value
// position found
Node temp = currentNode.next;
currentNode.next = newNode;
newNode.next = temp;
return; // stop method execuiton as action was completed
} else {
currentNode = currentNode.next; // move on to the next node
}
}
// if newNode should be added to the end, then code here is executed
// here currentNode is the last node (after iteration above)
currentNode.next = newNode;
}
public void delete(int valueOut){
if (this.head == null){// if list is empty stop method execution
return;
}
if (this.head.value == valueOut){ // if value to remove is at the beginning
this.head = this.head.next; // move reference to next node
return; // stop method execution
}
Node currentNode = this.head; // already skip the element at the beginning as we compared it before
while (currentNode.next != null){ // iterate through whole list except last element
Node nextNode = currentNode.next;
if (nextNode.value == valueOut){ // if next node is node that we need to delete
currentNode.next = nextNode.next; // skip node
return; // stop method execution
}
currentNode = currentNode.next; // move to next node
}
System.out.println("Element not found");
}
public boolean isEmpty(){
return this.head == null; // returns true is head is empty (no elements)
}
public static void main(String[] args){ // test the code
LinkedList myList = new LinkedList();
// test isEmpty() when empty
System.out.println(myList.isEmpty());
// test add()
int[] values = {5, 2, 6, 3, 1, 10, 7, 9, 8, 4};
for (int i : values){ // for i in values
myList.add(i);
}
myList.print(); // output list
// test isEmpty() when non-empty
System.out.println(myList.isEmpty());
// test delete()
myList.delete(1);
myList.print();
myList.delete(4);
myList.print();
myList.delete(10);
myList.print();
}
}- Can you implement the addTail and isFull methods in a linked list?
- What are the advantages of using linked lists over arrays?
- How does the add algorithm maintain the list's order?