返回上一级
1652 字
8 分钟
链表(三)任意位置插入与删除

p7(15 分钟)、p8(12 分钟)

先记住两条约定#

位置编号从 1 开始#

位置编号是 1-based。这和前面数组、动态列表的 0-based 下标是两套约定,别混。

合法位置的范围#

以 3 个节点的链表为例:

位置 1 → 在开头插入
位置 2、3 → 中间插入
位置 4 → 在末尾插入
位置 5 → 无效位置

长度为 n 的链表,最大合法插入位置是 n+1。

为了简化实现,假定调用者总是给出有效位置,不处理无效或错误情况。

任意位置插入#

核心思路#

想在「第 n 个位置」插入
→ 先去到「第 (n-1) 个节点」
→ ① 先把新节点的链接字段设为第 (n-1) 个节点的链接字段(建立新链路)
→ ② 再把第 (n-1) 个节点的链接设为新节点的地址(断开旧链路)

顺序不能反:先建新链路,再断旧链路。

实现#

struct node {
int data;
struct node *next;
};
struct node *head; /* 全局变量 */
void insert(int data, int n) {
struct node *temp1 = new node; /* C++ 写法 */
temp1->data = data;
temp1->next = NULL;
/* 特殊情况:插到开头 */
if (n == 1) {
temp1->next = head;
head = temp1;
return; /* 必须 return */
}
/* 其他情况 */
struct node *temp2 = head;
for (int i = 0; i < n - 2; i++) { /* 循环 n-2 次 */
temp2 = temp2->next;
}
temp1->next = temp2->next; /* ① 先建新链路 */
temp2->next = temp1; /* ② 再断旧链路 */
}

循环次数是 n−2#

我们要走到「第 (n-1) 个节点」
但 temp2 一开始就指向表头(第 1 个节点)
所以只需要再走 (n-1) - 1 = n-2 次
n = 1 → 走特殊分支,不进循环 → 插到开头
n = 2 → 循环 0 次 → 第 (n-1) 个节点就是第 1 个(head)
n = 3 → 循环 1 次 → 走到第 2 个节点
n = 4 → 循环 2 次 → 走到第 3 个节点

写成 n−1 次会多走一步,越过目标节点。

n == 1 这一支天然覆盖空表#

空表时 head == NULL,temp1->next = head 写入的就是 NULL,然后 return。所以链表为空不用单独处理。

内存分区#

应用程序内存通常分四个部分:

代码 / 指令段 所有需要执行的指令 编译时固定
全局变量段 整个程序运行期间都存在的全局变量 编译时固定
栈 函数调用执行的所有信息 + 所有局部变量 编译时固定
堆 / 自由存储空间 运行时请求的内存(malloc / new) 不固定

前三个部分大小固定,在编译时确定。

head 是全局变量,位于全局变量段,而且任何人都能访问这个变量。

栈帧#

main 开始执行 → 栈中分配一块内存用于 main 的执行,所有局部变量和执行状态保存在此。这块内存叫函数的栈帧。

调用 insert → main 暂停,insert 进栈;insert 有 2 个参数 + 若干局部变量,栈帧会稍大一点。

堆和指针#

new 在堆中创建内存块。用 new 或 malloc 在堆上要内存时,拿不到变量名,访问它的唯一方式是通过一个指针变量——这个指针变量就像遥控器。

函数返回时#

每次函数执行完毕,所有局部变量都会从内存中消失,栈帧的分配与特定的调用相对应。

但堆里的节点不会被清除,除非显式 free / delete。

任意位置删除#

删除要做两件事#

① 修正链接,使该节点不再是列表的一部分
② 释放该节点所占的空间

只修链接不够。修链接只是把节点从链表里断开,让它没法再被访问,但它仍占着内存。

节点是从动态内存、也就是堆区分配的,在 C/C++ 里用完这块内存后必须显式释放,因为它不会自动被回收。

内存是关键资源,不再需要时不该白白占着。只有释放了,这个节点才真正从内存里消失。

逻辑(一般情况)#

要去第 n 个节点,得先到「第 (n-1) 个节点」
把第 (n-1) 个节点的链接设为「第 n 个节点的链接」(即第 n+1 个节点)
→ 切断这条链接

实现#

这里一共写了三个函数:

  • insert(value):总是把值插到列表末尾
  • print():打印所有元素
  • delete(n):接受要删除节点的位置 n
void deleteNode(int n) {
struct node *temp1 = head;
/* 特殊情况:删除头节点 */
if (n == 1) {
head = temp1->next; /* 头节点移到第二个 */
free(temp1); /* C++ 用 delete temp1; */
return; /* 必须 return 或加 else */
}
/* 一般情况 */
for (int i = 0; i < n - 2; i++) { /* 循环 n-2 次,走到第 n-1 个 */
temp1 = temp1->next;
}
struct node *temp2 = temp1->next; /* temp2 指向第 n 个节点 */
temp1->next = temp2->next; /* 修正链接:指向第 n+1 个 */
free(temp2); /* 释放 */
}

释放方式要对应分配方式:malloc 配 free,new 配 delete,别配错。

那个 return 的由来#

对于 n != 1 的情况,不应执行删除头节点这段代码,所以要在此之后加一个 else,或者在针对该条件执行完语句后加上 return。少了它,删完头节点还会继续往下跑。

运行验证#

初始列表 2 4 6 5:

删除位置 1 → 4 6 5 ✓
删除位置 4(数字 5) → 2 4 6 ✓
删除位置 2 → 2 6 ✓

全部正确。

内存模拟(删除位置 3)#

temp1 = head(地址 100)
n = 3 → 循环恰好执行 1 次 → temp1 移到地址 200(第 2 个节点)
temp2 = temp1->next = 150(第 3 个节点)
temp1->next = temp2->next → 150 变成 250
free(temp2) → 地址 150 的内存块被删除
函数结束,temp1、temp2 都被清除
最终地址 250 的节点成为第 3 个节点

head 是全局变量,所以不会被清除。

插入 vs 删除#

插入删除
走到哪第 (n-1) 个节点第 (n-1) 个节点
循环次数n−2n−2
关键顺序先建新链路,再断旧链路先跨过目标,再释放
必须做的事创建节点修正链接 + 显式释放
n==1 特殊处理temp1->next = head; head = temp1;head = temp1->next; free(temp1);

坑#

  • 位置是 1-based,长度为 n 的链表最大合法插入位置是 n+1。
  • 循环次数是 n−2 次,不是 n−1。这是最容易写错的地方。
  • 插入要「先建新链路,再断旧链路」,顺序反了会丢节点。
  • 删除只修链接不够,必须 free / delete,否则内存泄漏。
  • 删头节点之后必须 return 或加 else。
链表(三)任意位置插入与删除
https://me.622168.xyz/posts/data-structure/04-linked-list-insert-delete-position/
作者
Shaw 的小屋
发布于
2026-09-18
许可协议
CC BY-NC-SA 4.0