Linked List Data Structure
A linear data structure where elements are stored in nodes, and each node points to the next node in the sequence.
Definition
A linked list is a linear data structure consisting of nodes where each node contains data and a reference (pointer) to the next node in the sequence. Unlike arrays, linked lists don't store elements in contiguous memory locations. The first node is called the head, and the last node points to null.
Key Properties
Dynamic Size
Size can grow or shrink during runtime
Non-contiguous Memory
Nodes can be stored anywhere in memory
Sequential Access
Must traverse from head to access elements
Easy Insertion/Deletion
O(1) insertion/deletion at known position
Memory Efficient
Only uses memory for actual data
No Wasted Space
No unused memory allocation
Time Complexity
Operation | Time Complexity | Description |
---|---|---|
Access | O(n) | Must traverse from head |
Search | O(n) | Linear search through nodes |
Insertion (at head) | O(1) | Direct insertion at beginning |
Insertion (at tail) | O(n) | Must traverse to end |
Deletion (at head) | O(1) | Direct deletion from beginning |
Deletion (at tail) | O(n) | Must traverse to end |
Python Code Examples
Python Implementation
# Node class
class ListNode:
def __init__(self, data):
self.data = data
self.next = None
# Linked List class
class LinkedList:
def __init__(self):
self.head = None
self.size = 0
def prepend(self, data):
"""Add element at the beginning"""
new_node = ListNode(data)
new_node.next = self.head
self.head = new_node
self.size += 1
def append(self, data):
"""Add element at the end"""
new_node = ListNode(data)
if not self.head:
self.head = new_node
else:
current = self.head
while current.next:
current = current.next
current.next = new_node
self.size += 1
def insert_at(self, data, index):
"""Insert at specific position"""
if index < 0 or index > self.size:
return False
if index == 0:
self.prepend(data)
return True
new_node = ListNode(data)
current = self.head
for i in range(index - 1):
current = current.next
new_node.next = current.next
current.next = new_node
self.size += 1
return True
def remove(self, data):
"""Remove element"""
if not self.head:
return False
if self.head.data == data:
self.head = self.head.next
self.size -= 1
return True
current = self.head
while current.next and current.next.data != data:
current = current.next
if current.next:
current.next = current.next.next
self.size -= 1
return True
return False
def find(self, data):
"""Search element"""
current = self.head
index = 0
while current:
if current.data == data:
return index
current = current.next
index += 1
return -1
def display(self):
"""Display list"""
current = self.head
result = []
while current:
result.append(str(current.data))
current = current.next
return " -> ".join(result)
def reverse(self):
"""Reverse the linked list"""
prev = None
current = self.head
while current:
next_node = current.next
current.next = prev
prev = current
current = next_node
self.head = prev
# Usage
ll = LinkedList()
ll.append(1)
ll.append(2)
ll.append(3)
ll.prepend(0)
print(ll.display()) # "0 -> 1 -> 2 -> 3"
# Find element
print(f"Index of 2: {ll.find(2)}") # 2
# Remove element
ll.remove(1)
print(ll.display()) # "0 -> 2 -> 3"
# Reverse list
ll.reverse()
print(ll.display()) # "3 -> 2 -> 0"
Common Use Cases
Dynamic Memory Allocation
When size is unknown at compile time
Implementation of Stacks/Queues
Base structure for other data structures
Music Playlists
Sequential access to songs
Undo Functionality
Storing command history
Browser History
Forward/backward navigation
Polynomial Representation
Mathematical expressions