目录
[为什么插入函数要写成 struct Node** head?](#为什么插入函数要写成 struct Node** head?)
首先,为什么要在链表中插入?
-
在数组中,插入元素需要移动后面的所有元素
-
而链表中,节点之间是通过指针连接的,不需要整体移动
-
只需要修改两个指针即可完成插入操作
问题本质:什么是"插入一个节点"?
我们要把一个新节点 插入到链表中的某个位置。本质上是在链表中"夹进去"一个节点。
这意味着:
改变原有两个节点之间的指针,使它们包住新的节点
假设你已经有一个链表:
head → [10] → [20] → [30] → NULL
现在你要插入一个值为 15
的新节点,插入在 10
和 20
之间。
插入后变成:
head → [10] → [15] → [20] → [30] → NULL
✅ 所以插入的本质是:改变两个指针
cpp
struct Node* prev; // 插入位置的前一个节点(如10)
struct Node* newNode; // 新节点
newNode->next = prev->next;
prev->next = newNode;
意思是:
-
先让新节点指向原来的"下一个"
-
再让"前一个"节点指向新节点
链表插入分为三种情况
我们按位置分类,分别推导指针操作:
情况一:插入在表头
假设你要把新节点插入到链表最前面。
原链表:
cpp
head → [10] → [20] → ...
操作后:
cpp
head → [newNode] → [10] → [20] → ...
cpp
newNode->next = head;
head = newNode;
-
newNode
的next
指向原来的头 -
head
指向新节点
情况二:插入在中间位置
假设你要把 15
插入在 10
和 20
之间:
cpp
head → [10] → [20] → [30]
↑
插入在这里
你需要:
-
一个指针
prev
指向10
-
一个新节点
newNode
值为15
操作步骤:
cpp
newNode->next = prev->next; // 指向 20
prev->next = newNode; // 10 指向 15
情况三:插入在末尾
你想把新节点加在最后:
cpp
head → [10] → [20] → [30] → NULL
↑
插入在这里
需要:
-
遍历到尾部,找到最后一个节点
tail
,即tail->next == NULL
-
执行:
cpp
tail->next = newNode;
newNode->next = NULL;
| 插入位置 | 操作顺序 |
| 头部插入 | newNode->next = head; head = newNode;
|
| 中间插入 | 找到 prev
,newNode->next = prev->next; prev->next = newNode;
|
尾部插入 | 遍历到尾部,tail->next = newNode; newNode->next = NULL; |
---|
实现插入函数
第一步:函数设计
我们假设链表下标从 0 开始:
cpp
insertAtPosition(struct Node** head, int pos, int value);
-
pos == 0
:插入到头部 -
pos == 1
:插入到第二个节点之前 -
...
-
pos == n
:插入到尾部
参数解释:
-
head
: 指向链表头指针的指针(因为头可能会改变) -
pos
: 插入的位置(下标从 0 开始) -
value
: 插入的整数值
为什么插入函数要写成 struct Node** head?
链表的"头节点"是通过一个指针 struct Node* head
表示的。如果你把 head
传进函数,函数里能改变它吗?
在 C 语言中,所有函数参数都是按值传递的,包括指针。
也就是说:
cpp
void f(struct Node* head) {
head = someNewNode;
}
↑ 这个 head
是函数 f
的局部副本 ,你在里面给 head
赋新值,不会改变原来的 head
。
因为我们想让函数内部能修改传入的 head 指针本身。
也就是说:
-
*head
是真正的头指针变量 -
head
是指向那个头指针变量的地址
你要改变的内容 | 参数应该传什么 |
---|---|
节点的内容(data) | 节点指针(struct Node* ) |
指针本身(head) | 指针的地址(struct Node** ) |
类比指针和变量:
cpp
int x = 10;
void change(int a) { a = 20; } // 无效
void change2(int* p) { *p = 20; } // 有效
int main() {
change(x); // 不会改变 x
change2(&x); // 才能真的改变 x 的值
}
第二步:先创建一个新节点
在插入之前,你必须先创建一个节点,用来存储 value
:
cpp
struct Node* newNode = (struct Node*) malloc(sizeof(struct Node));
newNode->data = value;
newNode->next = NULL;
为什么 next = NULL
?
- 初始状态下你还没连接它,为了避免野指针,最好先清空。
第三步:处理特殊情况 ------ 插入在头部
当 pos == 0
,说明新节点要放在链表开头:
cpp
newNode->next = *head; // 新节点指向原来的头
*head = newNode; // 新节点变成新的头
return;
第四步:插入在中间或尾部
我们需要:
-
遍历链表,找到第
pos-1
个节点(也就是新节点的"前一个") -
然后按顺序修改指针
cpp
struct Node* temp = *head;
for (int i = 0; i < pos - 1; i++) {
if (temp == NULL) {
printf("Position out of bounds.\n");
free(newNode);
return;
}
temp = temp->next;
}
第五步:指针修改连接链
此时 temp
是插入点的前驱节点:
cpp
newNode->next = temp->next;
temp->next = newNode;
完整代码
cpp
#include <stdio.h>
#include <stdlib.h>
struct Node {
int data;
struct Node* next;
};
void insertAtPosition(struct Node** head, int pos, int value) {
struct Node* newNode = (struct Node*) malloc(sizeof(struct Node));
newNode->data = value;
newNode->next = NULL;
if (pos == 0) {
// 插入到头部
newNode->next = *head;
*head = newNode;
return;
}
// 找到前一个节点
struct Node* temp = *head;
for (int i = 0; i < pos - 1 && temp != NULL; i++) {
temp = temp->next;
}
if (temp == NULL) {
printf("Position out of bounds.\n");
free(newNode);
return;
}
// 插入
newNode->next = temp->next;
temp->next = newNode;
}
void printList(struct Node* head) {
while (head != NULL) {
printf("%d → ", head->data);
head = head->next;
}
printf("NULL\n");
}
int main() {
struct Node* head = NULL;
// 插入几个节点
insertAtPosition(&head, 0, 30); // 30
insertAtPosition(&head, 0, 20); // 20 30
insertAtPosition(&head, 0, 10); // 10 20 30
insertAtPosition(&head, 2, 15); // 10 20 15 30
insertAtPosition(&head, 4, 40); // 10 20 15 30 40
printList(head);
return 0;
}