💡 打个比方
单链表是单行道 :每个路口只写着一个"下一站地址",只能往前走,想回头只能从起点重走。
双链表是双向街道 :每个节点多存一个"上一站地址"(prev),既能往前也能往后,想去哪边都行。
循环链表是地铁环线 :最后一站的"下一站"又指回第一站,永远走不到尽头,一圈一圈转。
三种链表 :单链表(val + next,只能前进);双链表(val + prev + next,双向通行);循环链表(尾节点的 next 指回头部,像地铁环线)。删除节点永远只改邻居的"地址"。
🖼️ 大图看明白
单链表
双链表
循环链表
① 双链表:每个节点存两个地址,上一站 prev、下一站 next
10
prev | next
20
prev | next
30
prev | next
head
tail
② 循环链表:尾节点的 next 指回头部,像地铁环线
A
B
C
C 的 next 指回 A(tail->next = head)
③ 删除对比:单链表改 1 个地址,双链表要改邻居的 2 个地址
单链表:删 20
10
20
30
只改 10 的 next,让它直连 30
双链表:删 20
10
20
30
改 10 的 next 和 30 的 prev,都绕开 20
📏 建双链表,只要 3 步
第 1 步:造节点。 新建一个节点,把值存进 val,prev 和 next 先都设为 NULL(还没有邻居)。
第 2 步:尾插接上。 把新节点的 prev 指向旧 tail,旧 tail 的 next 指向新节点;然后让 tail 变成新节点。如果这是第一个节点,head 也指向它。
第 3 步:双向遍历。 从 head 顺着 next 往前走是正序;从 tail 顺着 prev 往后走是倒序。两个方向都能走到所有节点。
为什么双链表删除更"麻烦"? 单链表删节点只需改它前一个的 next;双链表要改两个邻居 :前一个的 next 和后一个的 prev,都绕过被删节点。但好处是:拿着任意一个节点,不用从 head 重走,就能直接找它的上一个。
💻 C++ 怎么写
// 双链表节点:值 + 上一站 + 下一站
struct Node {
int val;
Node *prev;
Node *next;
};
Node *head = nullptr, *tail = nullptr;
void tailInsert(int x) { // 尾插
Node *p = new Node{x, tail, nullptr};
if (tail) tail->next = p; // 旧尾指向新节点
tail = p; // 新节点成为新尾
if (!head) head = p; // 第一个节点也是头
}
int main() {
tailInsert(10); tailInsert(20); tailInsert(30);
Node *p = head;
while (p) { // 正序遍历
cout << p->val << " "; p = p->next;
}
p = tail;
while (p) { // 倒序遍历
cout << p->val << " "; p = p->prev;
}
return 0;
}
考点提醒 :循环链表判断"走完了"不能靠 next == NULL,要靠"又走回 head"(next == head)。双链表节点比单链表多一个指针,多占空间,换来的是双向通行。
✋ 你来试试
题 1:双链表 10↔20↔30↔40,删掉 20,需要改哪两个地址?
改两处:① 20 的前邻居 10:10->next = 20->next (让 10 直连 30);② 20 的后邻居 30:30->prev = 20->prev (让 30 回连 10)。最后释放 20 这个节点。单链表只需要做第 ① 步。
题 2:为什么循环链表不能像普通链表那样"走到 NULL 就停"?
因为循环链表的尾节点 next 指回 head,顺着走永远走不到 NULL ,会无限循环!判断"走完整圈"的办法是:记录起点 head,当再次遇到 head(p->next == head)时说明一圈结束。这也是循环链表求尾节点的技巧:从 head 一直走到 next == head 的那个节点,它就是 tail。
🎯 一句话记住:单链表=单行道(只有 next);双链表=双向街道(prev+next,删除要改两个邻居);循环链表=地铁环线(尾接回头部,判断终止靠"走回 head")。