Appearance
题目
已知头指针 h 指向一个带头结点的非空单循环链表,结点结构为
------------
|data |next|
------------其中 next 是指向直接后继结点的指针,p 是尾指针,q 是临时指针。现要删除该链表的第一个元素,正确的语句序列是( )。
错因
A
操作顺序错位。先执行 h->next = h->next->next 把头结点的 next 跳到第二个数据结点,此后 q = h->next 拿到的是新链表的"第一个数据结点"(即原来的第二个),不再是要被删的那个。free(q) 把活着的第二个结点释放掉了——本该删的那个反而成了内存泄漏,链表也被破坏。
B
遗漏了尾指针 p 的维护。指针调整顺序是对的,能正确摘下并释放原首结点。但当链表只剩一个数据结点时,p 和 q 指的是同一个结点;free 完之后 p 仍然指向那块已释放的内存,变成野指针。后续任何一次"通过 p 找尾巴"的操作都可能崩。
C
尾指针更新条件写反了。当 p != q 时说明 q 不是尾结点,链表删除后还有剩余结点,p 本来不需要改;C 偏偏在这种情况下把 p 改成了 h(指向头结点),导致 p 不再指向真正的尾。这与题面对 p 的定义矛盾。条件应该是 p == q(删的就是尾),不是 p != q。
总解析
思路:带头结点的单循环链表 = "头结点 → 数据结点1 → ... → 数据结点k → 头结点"。删第一个数据结点要做三件事:① 用临时指针 q 抓住它,避免摘掉后找不回来;② 把头结点的 next 指针绕过 q;③ 如果 q 同时也是尾结点(即整张链表只有这一个数据结点),删完链表就空了,尾指针 p 必须跟着调整为指向头结点 h,循环结构才完整。
逐句拆解 D:
c
q = h->next; // ① 临时指针 q 抓住第一个数据结点
h->next = q->next; // ② 头结点 next 指针绕过 q,链表正确摘除
if (p == q) p = h; // ③ 如果 q 就是尾,链表删完为空,p 改指头结点
free(q); // ④ 释放被摘下的结点为什么 p == q 才需要调整:
- 多结点情况:删第一个时,尾结点(最后一个数据结点)一动没动,p 自然继续指着它,不用管。
- 单结点情况:第一个就是最后一个,p 和 q 指同一个结点。删完后链表只剩头结点 h,循环链表的形态要求"尾结点的 next 指回头结点"——只剩 h 时,h 自己要既当头又当尾,所以 p 改指 h。
与 B 的关键区别:B 少了 if (p == q) p = h;,单结点删除后 p 成野指针;后续操作(哪怕是简单的"在尾部插入"用 p 来定位尾巴)都会崩。
最终答案是 D。