
🌟《双链表王国大冒险》🌟
课程目标:
✅ 完全理解双链表结构
✅ 熟练写插入 / 删除
✅ 明白它比单链表强在哪里
✅ 能应对考试和竞赛题
🏰 第一章:为什么需要双链表?
🎯 故事:单行道 vs 双向马路
🚂 单链表(单行道)
1 → 2 → 3 → 4
👉 只能往前走
👉 想回头?不行!
🚗 双链表(双向马路)
1 ⇄ 2 ⇄ 3 ⇄ 4
👉 可以前进
👉 也可以后退!
🧠 第二章:双链表结构(看不同)
🎯 每个节点长这样:
struct Node
{
int data;
Node* prev; // 指向前一个
Node* next; // 指向后一个
};
🧩 图解
NULL ← 1 ⇄ 2 ⇄ 3 → NULL
👉 每个节点有两个"指路牌"
🏗 第三章:创建双链表
🎯 手动创建
Node* a = new Node{1, NULL, NULL};
Node* b = new Node{2, NULL, NULL};
Node* c = new Node{3, NULL, NULL};
// 连接
a->next = b;
b->prev = a;
b->next = c;
c->prev = b;
Node* head = a;
🔍 第四章:遍历(双向优势!)
🎯 正向遍历
Node* p = head;
while(p != NULL)
{
cout << p->data << " ";
p = p->next;
}
🎯 反向遍历(双链表独有🔥)
Node* p = tail;
while(p != NULL)
{
cout << p->data << " ";
p = p->prev;
}
👉 单链表做不到这一点!
➕ 第五章:插入操作
🎯 1️⃣ 头插法
Node* insertHead(Node* head, int x)
{
Node* node = new Node{x, NULL, head};
if(head != NULL)
head->prev = node;
return node;
}
🎯 2️⃣ 在某节点后插入
👉 在 p 后插入 x
void insertAfter(Node* p, int x)
{
Node* node = new Node{x, p, p->next};
if(p->next != NULL)
p->next->prev = node;
p->next = node;
}
🧠 插入口诀
先连新节点 → 再改周围节点
❌ 第六章:删除操作(超级重点🔥🔥🔥)
🎯 删除某个节点 p
void deleteNode(Node*& head, Node* p)
{
if(p == NULL) return;
// 如果是头节点
if(p == head)
head = p->next;
if(p->prev != NULL)
p->prev->next = p->next;
if(p->next != NULL)
p->next->prev = p->prev;
delete p;
}
🧠 删除口诀(必背)
断前 → 断后 → 释放
🔄 第七章:双链表反转(比单链表更简单!)
🎯 思路
👉 把 prev 和 next 交换!
💻
Node* reverse(Node* head)
{
Node* p = head;
Node* newHead = NULL;
while(p)
{
// 交换指针
Node* tmp = p->next;
p->next = p->prev;
p->prev = tmp;
newHead = p;
p = tmp;
}
return newHead;
}
🧱 第八章:带头结点(哨兵节点)技巧🔥
🎯 为什么用?
👉 避免特殊情况(空链表 / 头节点)
🎯 结构
HEAD ⇄ 1 ⇄ 2 ⇄ 3
💡 插入更简单:
void insert(Node* head, int x)
{
Node* node = new Node{x, head, head->next};
if(head->next)
head->next->prev = node;
head->next = node;
}
⚔️ 第九章:双链表 vs 单链表
| 对比 | 单链表 | 双链表 |
|---|---|---|
| 指针数 | 1个 | 2个 |
| 能否回头 | ❌ | ✅ |
| 删除操作 | 麻烦 | 简单🔥 |
| 内存占用 | 少 | 多 |
| 使用场景 | 普通 | LRU缓存🔥 |
🎯 第十章:经典应用------LRU缓存(了解)
👉 浏览器"最近访问记录"
👉 最近用的放前面
👉 很久没用的删掉
👉 双链表 + 哈希表 = 高级结构🔥
| 结构 | 查找效率 | 插入/删除效率 | 顺序维护 |
|---|---|---|---|
| 纯哈希表 | O(1) | O(1) | ❌ 无序 |
| 纯双链表 | O(n) | O(1)(已知位置) | ✅ 有序 |
| 哈希表 + 双链表 | O(1) | O(1) | ✅ 有序 |
🧠 核心总结(非常重要)
🎯 双链表三大操作本质
插入 = 改4条指针
删除 = 改2~4条指针
反转 = 交换prev和next
🎁 最强口诀(考试必背🔥)
双链表有两指针,前驱后继都能寻
插入节点四步走,新前后要连好
删除节点要小心,前后断开再释放
反转链表很简单,prev next互交换
🏁 学完我们应该能做到:
✅ 手写双链表结构
✅ 正向 + 反向遍历
✅ 任意位置插入
✅ 删除任意节点
✅ 反转链表
✅ 理解为什么它比单链表强
🌟《双链表5道经典训练题(含LRU简化版)》🌟
👉 目标:从"会写"到"会用"再到"会做竞赛题"
🧩 第1题:正向 + 反向遍历(基础)
1、🎯 故事
你是列车管理员,不仅要从前往后检查,还要倒着检查!
2、🚂 图解
NULL ← 1 ⇄ 2 ⇄ 3 → NULL
3、💡 思路
✔ 正向:next
✔ 反向:prev
4、💻 代码
void printForward(Node* head)
{
Node* p = head;
while(p)
{
cout << p->data << " ";
p = p->next;
}
}
void printBackward(Node* tail)
{
Node* p = tail;
while(p)
{
cout << p->data << " ";
p = p->prev;
}
}
🧩 第2题:在指定位置插入(进阶)
1、🎯 故事
新乘客要插到第 k 个位置 🚃
2、💡 思路
👉 找第 k 个节点 p
👉 插入在 p 前面
3、💻代码
Node* insertAt(Node* head, int k, int x)
{
Node* node = new Node{x, NULL, NULL};
if(k == 1)
{
node->next = head;
if(head) head->prev = node;
return node;
}
Node* p = head;
for(int i = 1; i < k-1 && p; i++)
p = p->next;
if(p == NULL) return head;
node->next = p->next;
node->prev = p;
if(p->next)
p->next->prev = node;
p->next = node;
return head;
}
🧩 第3题:删除第k个节点
1、🎯 故事
第k个乘客要下车 🚫
2、💡 思路
👉 找到第k个节点
👉 修改前后指针
3、💻代码
Node* deleteK(Node* head, int k)
{
if(head == NULL) return head;
Node* p = head;
for(int i = 1; i < k && p; i++)
p = p->next;
if(p == NULL) return head;
if(p == head)
head = p->next;
if(p->prev)
p->prev->next = p->next;
if(p->next)
p->next->prev = p->prev;
delete p;
return head;
}
🧩 第4题:判断是否回文链表(经典🔥)
1、🎯 故事
火车从前看和从后看是不是一样?
1 2 3 2 1 ✅
1 2 3 ❌
2、💡 思路
👉 双指针:
-
左指针:head
-
右指针:tail
3、💻代码
bool isPalindrome(Node* head)
{
if(head == NULL) return true;
Node* tail = head;
while(tail->next)
tail = tail->next;
Node* l = head;
Node* r = tail;
while(l != r && l->prev != r)
{
if(l->data != r->data)
return false;
l = l->next;
r = r->prev;
}
return true;
}
🧩 第5题:LRU缓存(进阶了解🔥🔥🔥)
1、🎯 故事
👉 浏览器最近访问记录:
访问:1 → 2 → 3 → 4
👉 再访问 2:
变成:2 → 4 → 3 → 1
👉 最常用在前面!
2、🧠 核心思想
👉 双链表负责"顺序"
👉 最新的放前面
👉 删除尾巴(最久没用)
3💡 简化规则(不使用map)
✔ 访问:
-
如果存在 → 移到头
-
如果不存在 → 插入头
-
超容量 → 删除尾
4、💻 核心代码
struct Node
{
int data;
Node* prev;
Node* next;
};
class LRU
{
public:
Node* head;
Node* tail;
int cap;
int size;
LRU(int c)
{
head = tail = NULL;
cap = c;
size = 0;
}
// 删除节点
void remove(Node* p)
{
if(p == head) head = p->next;
if(p == tail) tail = p->prev;
if(p->prev) p->prev->next = p->next;
if(p->next) p->next->prev = p->prev;
}
// 插到头
void insertHead(Node* p)
{
p->prev = NULL;
p->next = head;
if(head) head->prev = p;
head = p;
if(tail == NULL) tail = p;
}
// 访问
void access(int x)
{
Node* p = head;
// 查找
while(p && p->data != x)
p = p->next;
if(p) // 存在
{
remove(p);
insertHead(p);
}
else
{
Node* node = new Node{x, NULL, NULL};
insertHead(node);
size++;
if(size > cap)
{
Node* t = tail;
remove(t);
delete t;
size--;
}
}
}
};
🧠 总结:双链表考题特点
🎯 答题技巧
1️⃣ 插入 = 改4条指针
2️⃣ 删除 = 改前后指针
3️⃣ 多用 head / tail
4️⃣ 回文 = 双指针
5️⃣ LRU = 双链表核心应用🔥(了解)
🎁 终极口诀(背这个就稳了🔥)
双链表,双方向,前驱后继都要连
插入节点四步走,前后关系别搞乱
删除节点要小心,前后断开再释放