βοΈ Singly Linked List
π What is a Singly Linked List?
A singly linked list is the most basic type of linked list. It is a collection of nodes that are linked together in a sequence, allowing for traversal in only one direction (forward). Each node contains data and a single pointer that references the next node in the list.
π Node Structure
Every node in a singly linked list consists of two parts:
- Data: The value stored in the node (e.g., a number, string, or any other object).
- Next Pointer: A reference to the very next node in the list. For the last node, this pointer points to
None(ornull), signifying the end of the list.
class Node:
def __init__(self, data=None, next_node=None):
self.data = data
self.next = next_nodeβ Operations and Traversal
π Traversal
Traversal means visiting each node in the list exactly once, starting from the head. This is the foundation for most other operations. The process is strictly sequential and forward-only.
- Process:
- Create a temporary reference, letβs call it
current, and point it to the listβshead. - While
currentis notNone, you are at a valid node. - Process the data in the
currentnode (e.g., read or print it). - Move to the next node by updating
currentto point tocurrent.next.
- Create a temporary reference, letβs call it
- Complexity:
O(n), because in the worst case, you must visit allnnodes in the list.
π Insertion
Inserting a node involves creating a new node and carefully rearranging pointers to place it correctly in the list.
πΉ At the Head (Prepending)
This is the most efficient insertion operation.
- Process:
- Create a new node with the desired data.
- Set the new nodeβs
nextpointer to the currentheadof the list. - Update the listβs
headto point to this new node.
- Complexity:
O(1), as it involves a fixed number of steps regardless of list size.
πΉ At the End (Appending)
- Process:
- Create a new node with the desired data.
- Traverse the list until you find the last node (the one whose
nextisNone). - Set that last nodeβs
nextpointer to the new node.
- Complexity:
O(n), because you must traverse the entire list to find the end.
πΉ In the Middle
- Process:
- Create a new node with the desired data.
- Traverse the list to find the node before your desired insertion point. Letβs call this
previous_node. - Set the new nodeβs
nextpointer to whateverprevious_node.nextwas pointing to. - Update
previous_node.nextto point to the new node.
- Complexity:
O(n), as you need to traverse to the correct position first.
π Deletion
Deletion involves βbypassingβ a node by redirecting the pointers around it.
πΉ From the Head
- Process:
- Simply update the listβs
headto point to the second node (head.next). The old head is now disconnected from the list.
- Simply update the listβs
- Complexity:
O(1).
πΉ From the End or Middle
- Process:
- Traverse the list to find the node before the one you want to delete (
previous_node). - Let the node to be deleted be
target_node(previous_node.next). - Update
previous_node.nextto point totarget_node.next. This skips over thetarget_node.
- Traverse the list to find the node before the one you want to delete (
- Complexity:
O(n), because finding theprevious_noderequires traversal.
π Searching
Searching for a value involves traversing the list and checking each nodeβs data.
- Process: Follow the same steps as traversal. In each step, compare the
currentnodeβs data with the value you are searching for. If they match, youβve found the node. If you reach the end of the list (None), the value is not present. - Complexity:
O(n).
class SinglyLinkedList:
"""
Manages the overall Linked List.
"""
def __init__(self):
self.head = None
def display(self):
"""
Traverses and prints the entire list.
Complexity: `O(n)`
"""
nodes = []
current_node = self.head
while current_node:
nodes.append(str(current_node.data))
current_node = current_node.next
print(" -> ".join(nodes))
def prepend(self, data):
"""
Inserts a new node at the beginning of the list.
Complexity: `O(1)`
"""
new_node = Node(data)
new_node.next = self.head
self.head = new_node
def append(self, data):
"""
Inserts a new node at the end of the list.
Complexity: `O(n)`
"""
new_node = Node(data)
# If the list is empty, the new node is the head
if not self.head:
self.head = new_node
return
# Traverse to the last node
last_node = self.head
while last_node.next:
last_node = last_node.next
# Set the last node's next pointer to the new node
last_node.next = new_node
def delete_by_value(self, data):
"""
Deletes a node by its data value.
Complexity: `O(n)`
"""
if not self.head:
print("Error: List is empty.")
return
# Case 1: The node to delete is the head
if self.head.data == data:
self.head = self.head.next
return
# Case 2: The node is in the middle or at the end
current_node = self.head
# Find the node *before* the one to be deleted
while current_node.next and current_node.next.data != data:
current_node = current_node.next
if current_node.next:
# Bypass the node to be deleted
current_node.next = current_node.next.next
else:
print(f"Error: Node with data '{data}' not found.")
def search(self, data):
"""
Searches for a node with the given data.
Returns the node if found, otherwise None.
Complexity: `O(n)`
"""
current_node = self.head
while current_node:
if current_node.data == data:
return current_node
current_node = current_node.next
return Noneif __name__ == "__main__":
sll = SinglyLinkedList()
print("πΉ Appending 10, 20, 30...")
sll.append(10)
sll.append(20)
sll.append(30)
sll.display() # Output: 10 -> 20 -> 30
print("\nπΉ Prepending 5...")
sll.prepend(5)
sll.display() # Output: 5 -> 10 -> 20 -> 30
print("\nπΉ Deleting node with value 20...")
sll.delete_by_value(20)
sll.display() # Output: 5 -> 10 -> 30
print("\nπΉ Deleting head node (value 5)...")
sll.delete_by_value(5)
sll.display() # Output: 10 -> 30
print("\nπΉ Searching for value 30...")
found_node = sll.search(30)
if found_node:
print(f"Node with data '{found_node.data}' found.") # This will print
else:
print("Node not found.")
print("\nπΉ Searching for value 99...")
found_node = sll.search(99)
if found_node:
print(f"Node with data '{found_node.data}' found.")
else:
print("Node with data '99' not found.") # This will printπ Advantages
- Simplicity: Itβs simpler to implement than other types of linked lists.
- Memory: Uses slightly less memory per node compared to a doubly linked list because it only stores one pointer.
π Disadvantages
- Unidirectional Traversal: The biggest limitation is that you cannot traverse the list in reverse. Finding the previous node for a given node is an
O(n)operation. - Complex Deletions: To delete a specific node, you need a reference to the node before it, which complicates the operation if you only have a reference to the node you want to delete.
β οΈ Important Note: Choose a singly linked list when you need a simple, memory-efficient, dynamic data structure and your primary use case involves forward traversal or frequent insertions/deletions at the head of the list.