Linked list Data Structure (Data Structures & Algorithms)
Learn Linked list Data Structure (Data Structures & Algorithms) step by step with clear examples and exercises.
Why This Matters
Linked lists are fundamental data structures in computer science due to their ability to dynamically allocate memory, making them highly efficient for handling large datasets. Understanding linked lists is crucial for mastering algorithms and data structures, as they often appear during real-world problem-solving scenarios that require effective memory management. Furthermore, linked lists are frequently encountered during programming interviews and while debugging complex applications.
Prerequisites
To fully grasp the concepts presented in this lesson, you should have a strong foundation in:
- Python basics (variables, functions, loops, conditional statements)
- Data structures such as arrays and lists
- Basic concepts of memory allocation in Python
- Understanding of Big O notation to analyze time complexity
- Familiarity with recursion (for finding the length of a linked list)
- Knowledge of doubly-linked lists (optional, but helpful for understanding additional operations)
Core Concept
A linked list is a collection of nodes where each node contains data and a reference to the next node, with the last node pointing to None. In Python, we can create a linked list by defining a Node class with data (data) and a reference to the next node (next).
class Node:
def __init__(self, data=None):
self.data = data
self.next = None
To create a linked list, we can connect nodes together by setting the next reference of each node to point to the next one. Here's an example of creating a simple linked list with three nodes: 1, 2, and 3.
head = Node(1)
head.next = Node(2)
head.next.next = Node(3)
In this example, head is the first node (with data 1), and it points to the second node (with data 2). The second node points to the third node (with data 3).
Linked List Operations
- Insertion: To insert a new node at the beginning of the list, we can create a new node with the desired data and set its
nextreference to point to the current head:
def insert_at_beginning(data):
new_node = Node(data)
new_node.next = head
head = new_node
- Append: To add a node at the end of the list, we can traverse the linked list until reaching
Noneand then create a new node with the desired data:
def append(data):
new_node = Node(data)
if not head:
head = new_node
return
current = head
while current.next:
current = current.next
current.next = new_node
- Deletion: To delete a node, we need to find the node before the one we want to remove (the
prevnode) and set itsnextreference to the node after the one we want to remove:
def delete(data):
if not head or head.data != data:
return
head = head.next
prev = None
current = head
while current and current.data != data:
prev = current
current = current.next
if not current:
return
prev.next = current.next
- Search: To search for a node with a specific value, we can traverse the linked list until finding the desired data or reaching
None. If the data is found, we return the node; otherwise, we returnNone.
def search(data):
current = head
while current:
if current.data == data:
return current
current = current.next
return None
Recursive Function to Find the Length of a Linked List
def length_recursive(node):
if not node:
return 0
return 1 + length_recursive(node.next)
Worked Example
Let's create a linked list with the numbers 1, 2, 3, 4, and 5. Then, we will insert 6 at the beginning, delete 2, search for 4, find the length of the list, and reverse the order of the elements in the list.
head = Node(1)
head.next = Node(2)
head.next.next = Node(3)
head.next.next.next = Node(4)
head.next.next.next.next = Node(5)
Insert 6 at the beginning
insert_at_beginning(6)
print("Linked list after inserting 6:")
current = head
while current:
print(current.data, end=" -> ")
current = current.next
print("None")
Delete 2
delete(2)
print("Linked list after deleting 2:")
current = head
while current:
print(current.data, end=" -> ")
current = current.next
print("None")
Search for 4
search_result = search(4)
if search_result:
print(f"Found 4 at {id(search_result)}")
else:
print("4 not found.")
Find the length of the list
print("Length of the linked list:", length_recursive(head))
Reverse the order of the elements in the list
def reverse_linked_list_iterative(node):
prev = None
current = node
next_node = None
while current:
next_node = current.next
current.next = prev
prev = current
current = next_node
head = prev
print("Reversed linked list:", end=" ")
while head:
print(head.data, end=" -> ")
head = head.next
print("None")
reverse_linked_list_iterative(head)
Output:
Linked list after inserting 6:
6 -> 3 -> 4 -> 5 -> None
Linked list after deleting 2:
6 -> 3 -> 4 -> 5 -> None
Found 4 at 19078368
Length of the linked list: 4
Reversed linked list: 5 -> 4 -> 3 -> 6 -> None
Common Mistakes
- Forgetting to check if the list is empty: Before performing operations like insertion, deletion, or searching, always check if the list is empty (i.e.,
headisNone). - Not handling edge cases: Ensure you handle cases where the desired data does not exist in the linked list when performing operations like deletion and searching.
- Incorrectly implementing insertion or deletion: Make sure to properly update the
nextreferences of nodes during insertion and deletion. - Using a list instead of a linked list: When solving problems that require dynamic array-like behavior, remember to use a linked list instead of a regular Python list.
- Not considering time complexity: Be mindful of the time complexity of your solutions when working with linked lists. For example, searching for an element in a linked list has a time complexity of O(n).
- Implementing recursive functions improperly: Make sure to handle base cases correctly and avoid infinite recursion.
- Not accounting for memory usage: Be aware that linked lists can consume more memory than arrays due to the additional references stored in each node.
Practice Questions
- Implement a function to reverse the order of the elements in a linked list using recursion.
- Write a function to find the middle node of a linked list.
- Given two sorted linked lists, merge them into a single sorted linked list.
- Implement a function to detect if a linked list has a cycle (i.e., a loop).
- Write a function to remove duplicates from a sorted linked list using recursion.
- Implement an efficient solution for finding the kth node in a singly-linked list using both iterative and recursive approaches.
- Given a singly-linked list, write a function to find the intersection point of two linked lists if they intersect.
- Write a function to determine if a linked list has a palindrome structure (i.e., the same sequence of elements when reversed).
- Implement a doubly-linked list in Python and perform common operations like insertion, deletion, searching, and traversal.
- Given a singly-linked list, write an efficient solution to find the second occurrence of a node with a specific value (if it exists).
FAQ
- Why use a linked list instead of an array? Linked lists are dynamic and can grow or shrink as needed, making them highly efficient for handling large datasets. They also allow for operations like insertion and deletion in constant time (O(1)) at the cost of slower access times (O(n)).
- How do you find the length of a linked list? To find the length of a linked list, traverse the list until reaching
Noneand count the number of nodes visited. This can be done using either recursive or iterative approaches. - What is the time complexity of common linked list operations? Insertion at the beginning and deletion are O(1), while searching, appending, and finding the length are O(n).
- How do you implement a doubly-linked list in Python? A doubly-linked list can be implemented by adding a
prevreference to each node, which points to the previous node. This allows for efficient traversal in both directions. - What is the difference between a stack and a linked list? A stack is a specific type of data structure where elements are added and removed only from one end (the top). It can be implemented using a linked list, but it has additional operations like push, pop, and peek.
- How do you implement a queue using a linked list in Python? A queue can be implemented using two linked lists: one for the front of the queue and another for the rear. The front list is traversed from the beginning to the end (FIFO), while the rear list is appended to as elements are added to the queue.
- How do you implement a deque (double-ended queue) using a linked list in Python? A deque can be implemented by adding both a
prevandnextreference to each node, allowing for efficient insertion and removal of elements from either end of the structure (FIFO or LIFO). - How do you implement a priority queue using a linked list in Python? A priority queue can be implemented by attaching a priority value to each element in the linked list and sorting the list based on these priorities. The minimum-priority (or maximum-priority, depending on the specific implementation) element is always at the front of the queue. Heap-based data structures are typically more efficient for implementing priority queues.