Types of Linked List - Singly linked, doubly linked and circular (Data Structures & Algorithms)
Learn Types of Linked List - Singly linked, doubly linked and circular (Data Structures & Algorithms) step by step with clear examples and exercises.
Why This Matters
Linked lists are fundamental data structures in computer science that provide dynamic memory allocation and allow for efficient manipulation of data elements. In this tutorial, we will delve into three types of linked lists: singly linked, doubly linked, and circular linked lists, each with its unique advantages and applications. We'll explore these concepts using Python examples and learn how to implement and use them effectively.
Why This Matters
Understanding linked lists is crucial for grasping dynamic memory allocation, data structures, and algorithms. They are essential in various real-world scenarios such as implementing efficient data structures for stacks, queues, and dynamic arrays. Furthermore, they help in debugging common programming errors related to memory management.
Prerequisites
Before diving into the types of linked lists, it is essential to have a good understanding of the following concepts:
- Basic Python syntax and control structures (if-else, for, while)
- Data structures like arrays and lists
- Memory management in Python (dynamic memory allocation)
- Familiarity with recursion can be helpful but is not strictly required.
Core Concept
Singly Linked List
A singly linked list is a linear data structure where each node contains a data element and a reference to the next node. The last node in the list has a None or null pointer indicating the end of the list. Here's an example of a singly linked list:
class Node:
def __init__(self, data):
self.data = data
self.next = None
class SinglyLinkedList:
def __init__(self):
self.head = None
def insert(self, data):
new_node = Node(data)
if not self.head:
self.head = new_node
else:
current = self.head
while current.next:
current = current.next
current.next = new_node
def print_list(self):
current = self.head
while current:
print(current.data, end=" -> ")
current = current.next
print("None")
Doubly Linked List
A doubly linked list is an extension of a singly linked list, where each node contains two pointers—one pointing to the next node and another pointing to the previous node. This allows for efficient traversal in both directions. Here's an example of a doubly linked list:
class Node:
def __init__(self, data):
self.data = data
self.next = None
self.prev = None
class DoublyLinkedList:
def __init__(self):
self.head = None
def insert(self, data):
new_node = Node(data)
if not self.head:
self.head = new_node
else:
current = self.head
while current.next:
current = current.next
current.next = new_node
new_node.prev = current
def append(self, data):
new_node = Node(data)
if not self.head:
self.head = new_node
else:
current = self.head
while current.next:
current = current.next
current.next = new_node
new_node.prev = current
new_node.next = None
def print_list(self, reverse=False):
if reverse:
current = self.head
while current and current.prev:
print(current.data, end=" <- ")
current = current.prev
print(current.data, end=" <- None")
else:
current = self.head
while current:
print(current.data, end=" -> ")
current = current.next
print("None")
Circular Linked List
A circular linked list is a type of linked list where the last node's next pointer points back to the first node, forming a cycle. This structure allows for efficient traversal from any node to any other node in the list. Here's an example of a circular linked list:
class Node:
def __init__(self, data):
self.data = data
self.next = None
class CircularLinkedList:
def __init__(self):
self.head = None
self.tail = None
def insert(self, data):
new_node = Node(data)
if not self.head:
self.head = new_node
self.tail = new_node
else:
current = self.head
while current.next:
current = current.next
current.next = new_node
new_node.next = self.head
def print_list(self):
if not self.head:
print("Empty list")
else:
current = self.head
while True:
print(current.data, end=" -> ")
current = current.next
if current == self.head:
break
Worked Example
Let's create a singly linked list, doubly linked list, and circular linked list using the provided classes and insert some data:
Singly Linked List
sll = SinglyLinkedList()
sll.insert(1)
sll.insert(2)
sll.insert(3)
sll.print_list() # Output: 1 -> 2 -> 3 -> None
Doubly Linked List
dll = DoublyLinkedList()
dll.insert(4)
dll.append(5)
dll.insert(6)
dll.print_list() # Output: 6 <- 5 <- 4 <- None
dll.print_list(reverse=True) # Output: None <- 4 <- 5 <- 6
Circular Linked List
cll = CircularLinkedList()
cll.insert(7)
cll.insert(8)
cll.print_list() # Output: 7 -> 8 -> 7
Common Mistakes
- Forgetting to initialize the head or tail node in a linked list
- Not setting the next or previous pointers correctly when inserting nodes
- Creating an infinite loop while traversing the linked list due to incorrect pointer handling
- Failing to handle edge cases such as empty lists or duplicate data entries
- Using inappropriate data structures for specific problems (e.g., using a singly linked list where a doubly linked list would be more efficient)
- Neglecting to free memory when working with circular linked lists to avoid memory leaks
Practice Questions
- Implement a function to find the length of a singly linked list.
- Write a function to reverse a singly linked list recursively and iteratively.
- Create a doubly linked list and implement functions for inserting, deleting, and searching nodes.
- Implement a circular queue using a circular linked list with a fixed size.
- Solve the following problem: Given a singly linked list, determine if it contains a cycle.
- Implement a function to merge two sorted singly linked lists into one sorted singly linked list.
- Write a function to find the middle node of a singly linked list.
- Implement a function to remove duplicates from a singly linked list.
- Solve the following problem: Given a circular linked list, return the node that precedes the given node in the list. If the given node is the first node, return None.
- Write a function to reverse every k-th node in a singly linked list (e.g., reversing every third node for a list with nodes 1->2->3->4->5->6 results in 1->3->2->6->5->4).
FAQ
- What is the time complexity of inserting an element in a singly linked list? - O(1) for average and worst-case scenarios since we are adding elements at the end or in the middle of the list.
- Why use doubly linked lists over singly linked lists? - Doubly linked lists allow efficient traversal in both directions, which can be useful in certain applications such as implementing a queue data structure. They also make deleting nodes easier since we have access to the previous node.
- What are some advantages and disadvantages of using circular linked lists? - Advantages include efficient traversal from any node to any other node in the list, but they can also lead to memory leaks if not implemented correctly. Disadvantages include the complexity of operations like insertion and deletion compared to singly or doubly linked lists.
- Can we implement a stack or queue using a circular linked list? - Yes, both stacks and queues can be efficiently implemented using circular linked lists.
- What is the time complexity of searching for an element in a singly linked list? - The average time complexity is O(n), where n is the number of nodes in the list. However, if the element is not present in the list, the worst-case scenario has a time complexity of O(n).
- What is the space complexity of a circular linked list compared to a singly or doubly linked list? - The space complexity of a circular linked list is slightly higher due to the need for an extra pointer (next) in each node to form the cycle. However, this difference is usually negligible in practice.