Linked List
A linked list is a linear data structure where elements are not stored in contiguous memory locations, but are linked together through pointers. Each element (called a node) contains data and a pointer to the next node.
Basic Concepts
Node Structure
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ data | next │───▶│ data | next │───▶│ data | null │
└─────────────┘ └─────────────┘ └─────────────┘
Node 1 Node 2 Node 3
Each node contains:
- Data field (data): Stores the actual data
- Pointer field (next): Points to the next node
Linked List vs Array
Feature | Array | Linked List |
---|---|---|
Memory Layout | Contiguous | Scattered |
Access Method | Random access O(1) | Sequential access O(n) |
Insert/Delete | O(n) | O(1) |
Memory Overhead | Low | High (extra pointers) |
Cache Friendliness | Good | Poor |
Linked List Types
Singly Linked List
- Singly Linked List Details
- Each node has only one pointer to the next node
- Can only traverse from head to tail in one direction
Doubly Linked List
- Doubly Linked List Details
- Each node has two pointers: prev and next
- Can traverse in both directions
Circular Linked List
- The last node points to the first node
- Forms a circular structure
Basic Operations
Create Node
class ListNode {
constructor(data) {
this.data = data;
this.next = null;
}
}
Traverse Linked List
function traverse(head) {
let current = head;
while (current !== null) {
console.log(current.data);
current = current.next;
}
}
Search Element
function search(head, target) {
let current = head;
let index = 0;
while (current !== null) {
if (current.data === target) {
return index;
}
current = current.next;
index++;
}
return -1; // Not found
}
Time Complexity Analysis
Operation | Time Complexity | Description |
---|---|---|
Access | O(n) | Need to traverse from head |
Search | O(n) | Need to traverse and search |
Insert (head) | O(1) | Directly modify head pointer |
Insert (tail) | O(n) | Need to find tail node |
Insert (known position) | O(1) | Directly modify pointers |
Delete (head) | O(1) | Directly modify head pointer |
Delete (known node) | O(1) | Directly modify pointers |
Delete (by value) | O(n) | Need to search first |
Advantages and Disadvantages
Advantages
- Dynamic Size: Can dynamically grow and shrink at runtime
- Efficient Insert/Delete: Insert/delete at known position is O(1)
- Memory Efficiency: Only allocate memory when needed
- Flexibility: Can implement complex data structures
Disadvantages
- Extra Memory Overhead: Each node needs to store pointers
- No Random Access: Cannot directly access the i-th element
- Poor Cache Performance: Nodes are not contiguous in memory
- Pointer Management: Prone to memory leaks or dangling pointers
Application Scenarios
Suitable Cases
- Frequent Insert/Delete: Especially in the middle of the list
- Unknown Size: Data volume changes greatly at runtime
- Implement Other Structures: Such as stacks, queues, adjacency lists for graphs
- Undo Operations: Editor's undo functionality
Unsuitable Cases
- Need Random Access: Frequently access elements by index
- Memory Sensitive: Strict memory usage requirements
- Cache Sensitive: Need high-performance sequential access
Practical Application Examples
1. Music Playlist
class Song {
constructor(title, artist) {
this.title = title;
this.artist = artist;
this.next = null;
}
}
class Playlist {
constructor() {
this.head = null;
this.current = null;
}
addSong(title, artist) {
const newSong = new Song(title, artist);
if (!this.head) {
this.head = newSong;
this.current = newSong;
} else {
let last = this.head;
while (last.next) {
last = last.next;
}
last.next = newSong;
}
}
nextSong() {
if (this.current && this.current.next) {
this.current = this.current.next;
return this.current;
}
return null;
}
}
2. Browser History
class HistoryEntry {
constructor(url, title) {
this.url = url;
this.title = title;
this.next = null;
}
}
class BrowserHistory {
constructor() {
this.head = null;
this.maxSize = 100;
this.size = 0;
}
visit(url, title) {
const entry = new HistoryEntry(url, title);
entry.next = this.head;
this.head = entry;
this.size++;
// Limit history size
if (this.size > this.maxSize) {
this.removeLast();
}
}
getHistory() {
const history = [];
let current = this.head;
while (current) {
history.push({
url: current.url,
title: current.title,
});
current = current.next;
}
return history;
}
}
Learning Suggestions
- Start from Basics: First master the basic operations of singly linked lists
- Visualize with Diagrams: Use diagrams to understand pointer changes
- Watch Edge Cases: Special handling for empty lists, single nodes, head/tail nodes
- Practice Pointer Operations: Master pointer CRUD operations
- Comparative Learning: Compare with arrays to understand each other's use cases
Next Steps
We recommend learning in the following order:
- Singly Linked List - Master basic concepts and operations
- Doubly Linked List - Understand the advantages of bidirectional pointers
- Circular Linked List - Learn about special linked list variants
- Advanced Linked List Applications - Such as LRU cache, skip lists, etc.
Mastering linked lists is essential for understanding more complex data structures (like trees and graphs)!