How to Implement a Linked List in Python from Scratch
To implement a linked list in Python from scratch, you must define two classes: a Node class to store the data and a reference to the next node, and a LinkedList class to manage these nodes. By linking these objects together via pointers, you create a dynamic linear data structure that allows for efficient insertions and deletions.
How to Implement a Linked List in Python from Scratch
A linked list is a fundamental data structure consisting of a sequence of elements, where each element (node) points to the next. Unlike Python lists, which are dynamic arrays, linked lists do not store elements in contiguous memory locations. This makes them highly efficient for frequent insertions and deletions.
Understanding the Core Components
To build a linked list, you must first understand the relationship between the node and the list manager.
The Node Class
The Node is the basic building block. It contains two primary attributes:
1. Data: The actual value being stored (integer, string, object, etc.).
2. Next: A reference (pointer) to the next node in the sequence. By default, the last node's next attribute is set to None.
The LinkedList Class
The LinkedList class acts as the wrapper. Its primary responsibility is to keep track of the head, which is the first node in the list. If the head is None, the list is empty.
Step-by-Step Implementation
Below is the professional implementation of a singly linked list.
class Node:
def __init__(self, data):
self.data = data
self.next = None
class LinkedList:
def __init__(self):
self.head = None
def append(self, data):
"""Adds a new node to the end of the list."""
new_node = Node(data)
if not self.head:
self.head = new_node
return
last_node = self.head
while last_node.next:
last_node = last_node.next
last_node.next = new_node
def prepend(self, data):
"""Adds a new node to the beginning of the list."""
new_node = Node(data)
new_node.next = self.head
self.head = new_node
def delete(self, key):
"""Removes the first occurrence of a node with the specified data."""
current = self.head
# If the head node itself holds the key
if current and current.data == key:
self.head = current.next
current = None
return
prev = None
while current and current.data != key:
prev = current
current = current.next
if current is None:
return # Key not found
prev.next = current.next
current = None
def display(self):
"""Prints the list elements in sequence."""
elements = []
current = self.head
while current:
elements.append(str(current.data))
current = current.next
print(" -> ".join(elements) + " -> None")
Time and Space Complexity Analysis
Understanding the efficiency of a linked list is critical for passing technical interviews and optimizing software performance.
Time Complexity
- Access/Search: $O(n)$. To find a specific element, you must traverse the list from the head node.
- Insertion at Head: $O(1)$. Adding an element to the front requires only updating the head pointer.
- Insertion at Tail: $O(n)$. Without a tail pointer, you must traverse the entire list to find the end.
- Deletion: $O(n)$. While the act of deleting is $O(1)$, finding the node to delete requires $O(n)$ time.
Space Complexity
- Overall Space: $O(n)$. Each element requires a node object containing the data and one pointer.
When to Use a Linked List vs. a Python List
Python's built-in list is actually a dynamic array. Choosing between a custom linked list and a standard array depends on your primary operation:
| Operation | Python List (Array) | Linked List |
|---|---|---|
| Indexing | $O(1)$ | $O(n)$ |
| Insert/Delete at Start | $O(n)$ | $O(1)$ |
| Append at End | $O(1)$ amortized | $O(n)$ (without tail pointer) |
If your application requires frequent additions and removals from the beginning of a dataset, a linked list is the superior choice. For random access and fast indexing, stick with Python's native lists.
Best Practices for Implementation
When building data structures, adhering to 5 Essential Best Practices for Clean Code ensures your logic is maintainable and readable.
- Encapsulation: Keep the
Nodeclass simple. All logic for manipulating the structure should reside within theLinkedListclass. - Edge Case Handling: Always account for empty lists (where
self.headisNone) and lists with a single element to avoidAttributeError. - Type Hinting: In professional environments, use Python's
typingmodule to specify thatnextis either aNodeorOptional[Node].
Expanding Your Knowledge
Mastering the singly linked list is the first step toward understanding more complex structures. Once you are comfortable with this implementation, CodeAmber recommends exploring: * Doubly Linked Lists: Where each node has a pointer to both the next and previous nodes, allowing bidirectional traversal. * Circular Linked Lists: Where the last node points back to the head, useful for round-robin scheduling.
For those deciding on their educational path, checking Which Programming Language Should I Learn First in 2024? can help you determine if Python's flexibility is the right fit for your software engineering goals.
Key Takeaways
- Structure: A linked list consists of
Nodeobjects containing data and anextpointer. - Head Pointer: The
LinkedListclass manages the entry point (head) of the sequence. - Efficiency: Linked lists offer $O(1)$ insertion at the head, making them faster than arrays for this specific operation.
- Trade-off: They sacrifice $O(1)$ random access (indexing) for dynamic memory allocation.
- Complexity: Search and deletion operations generally run in linear time, $O(n)$.