板子:建表(头插法、尾插法)与插入删除
链表的操作都能拆成两个基本动作:在
p后面插入s、删除p的后继。头插法、尾插法建表就是一直重复「插入」。
两个基本动作
// 速记:在 p 之后插入 s
s->next = p->next; // ① s 先接上 p 原来的后继
p->next = s; // ② p 再指向 s。①② 不能颠倒,否则 p 原来的后继就丢了
// 速记:删除 p 的后继 q
q = p->next;
p->next = q->next; // p 直接跳过 q
free(q);要删除一个结点,必须先拿到它的前驱,所以删除类的题都要带一个 pre 指针。
建表
LinkList headInsert(int A[], int n) { // 头插法:链表顺序和输入顺序相反
LinkList L = (LNode *)malloc(sizeof(LNode));
L->next = NULL; // 先建一个只有头结点的空表
for (int i = 0; i < n; i++) {
LNode *s = (LNode *)malloc(sizeof(LNode));
s->data = A[i];
s->next = L->next; // 每个新结点都插到头结点后面
L->next = s;
}
return L;
}
LinkList tailInsert(int A[], int n) { // 尾插法:链表顺序和输入顺序相同
LinkList L = (LNode *)malloc(sizeof(LNode));
LNode *r = L; // r 始终指向当前的表尾
for (int i = 0; i < n; i++) {
LNode *s = (LNode *)malloc(sizeof(LNode));
s->data = A[i];
r->next = s; // 接到表尾后面
r = s; // s 成为新的表尾
}
r->next = NULL; // 最后把表尾的 next 置空
return L;
}复杂度:两种都是时间
教材是一边 scanf 一边建表,读到 9999 结束。逻辑完全一样,这里改成从数组读,写起来更直观。
易错点
- 头插法建出来的顺序和输入相反,就地逆置用的正是这一点,见 链表就地逆置。
- 尾插法最后的
r->next = NULL不能漏。漏了的话表尾的next是野指针,遍历停不下来。 - 带头结点的好处:在第一个数据结点前插入、删除第一个数据结点,写法都和其他位置一样,不用特判。
malloc返回的是void *,C++ 里必须强转成(LNode *)。