1-3 递增的整数序列链表的插入
分数 4
作者 DS课程组
单位 浙江大学
本题要求实现一个函数,在递增的整数序列链表(带头结点)中插入一个新整数,并保持该序列的有序性。
函数接口定义:
1
| List Insert( List L, ElementType X );
|
其中List结构定义如下:
1 2 3 4 5 6
| typedef struct Node *PtrToNode; struct Node { ElementType Data; PtrToNode Next; }; typedef PtrToNode List;
|
L是给定的带头结点的单链表,其结点存储的数据是递增有序的;函数Insert要将X插入L,并保持该序列的有序性,返回插入后的链表头指针。
裁判测试程序样例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| #include <stdio.h> #include <stdlib.h>
typedef int ElementType; typedef struct Node *PtrToNode; struct Node { ElementType Data; PtrToNode Next; }; typedef PtrToNode List;
List Read(); void Print( List L );
List Insert( List L, ElementType X );
int main() { List L; ElementType X; L = Read(); scanf("%d", &X); L = Insert(L, X); Print(L); return 0; }
|
输入样例:
输出样例:
我的段错误答案
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| List Insert( List L, ElementType X ) { List p = L->Next; if (X < p->Data) { List newNode = (List) malloc(sizeof (struct Node)); newNode->Data = X; newNode->Next = L->Next; L->Next = newNode; return L; } while (p->Next != NULL) { if (p->Data <= X && X <= p->Next->Data) { List newNode = (List) malloc(sizeof (struct Node)); newNode->Data = X; newNode->Next = p->Next; p->Next = newNode; return L; } p = p->Next; } if (p->Next == NULL && p->Data < X) { List newNode = (List) malloc(sizeof (struct Node)); newNode->Data = X; newNode->Next = NULL; p->Next = newNode; return L; } return L; }
|
段错误是啥
程序非法访问了不属于它的内存区域
本质是程序违反了内存访问规则,常见场景包括:
- 解引用空指针:访问
NULL 指针或未初始化的野指针的成员(比如 NULL->Data)。
- 越界访问数组 / 链表:比如数组下标超出范围、链表遍历到
NULL 后仍继续访问下一个节点。
- 访问已释放的内存:内存被
free 后再次访问,或使用已销毁的栈变量指针。
- 栈溢出:递归过深、局部数组过大等导致栈内存超出限制。
- 修改只读内存:比如试图修改字符串常量(如
char *s = "abc"; s[0] = '1')。
排查思路
- 检查指针初始化:确保所有指针使用前已正确赋值,避免
NULL 指针解引用。
- 验证内存访问边界:链表遍历前判断指针是否为
NULL,数组访问确认下标在合法范围。
- 规范内存管理:避免重复释放内存、访问已释放的内存。
- 使用调试工具:通过
gdb(Linux)、IDE 调试功能(如 CLion 的断点调试)跟踪指针值变化,定位非法访问的代码行。
我段错误原因
1 2 3
| List p = L->Next; // 定义指针p,用于遍历链表L,初始指向第一个结点 // 特殊情况:比第一个还小,插在第一个的前面 if (X < p->Data) {...
|
如果是空链表,头结点后面是NULL,p->Data相当于NULL->Data,解引用空指针
正确答案(复杂)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| List Insert( List L, ElementType X ) { if (L->Next == NULL) { List newNode = (List)malloc(sizeof(struct Node)); newNode->Data = X; newNode->Next = NULL; L->Next = newNode; return L; } List p = L->Next; if (X < p->Data) { List newNode = (List) malloc(sizeof (struct Node)); newNode->Data = X; newNode->Next = L->Next; L->Next = newNode; return L; } while (p->Next != NULL) { if (p->Data <= X && X <= p->Next->Data) { List newNode = (List) malloc(sizeof (struct Node)); newNode->Data = X; newNode->Next = p->Next; p->Next = newNode; return L; } p = p->Next; } if (p->Next == NULL && p->Data < X) { List newNode = (List) malloc(sizeof (struct Node)); newNode->Data = X; newNode->Next = NULL; p->Next = newNode; return L; } return L; }
|
这段代码可以简化,核心是利用一个指针遍历找到插入位置,无需拆分多个特殊情况判断。简化后的版本保留了所有功能
正确答案(简化)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| struct Node { ElementType Data; struct Node *Next; }; typedef struct Node *List;
List Insert(List L, ElementType X) { List p = L; while (p->Next != NULL && p->Next->Data < X) { p = p->Next; } List newNode = (List)malloc(sizeof(struct Node)); newNode->Data = X; newNode->Next = p->Next; p->Next = newNode; return L; }
|
豆包比我聪明多了
简化思路说明:
- 统一插入逻辑:
- 用
p 从表头开始遍历(而非第一个数据节点),找到 “下一个节点值大于 X” 的位置
- 无论插入在头部、中间还是尾部,都可以用相同的代码完成插入(利用
p->Next 连接)
- 减少条件判断:
- 原代码中的 “空链表”“头部插入”“尾部插入” 等特殊情况,被while循环的条件自然覆盖:
- 空链表时,
p->Next 为 NULL,直接插入
- 插入头部时,
p 停留在表头(L)
- 插入尾部时,
p 停留在最后一个节点
- 代码更健壮:
- 无需单独处理各种边界情况,逻辑更清晰
- 所有路径都能正确返回,避免编译错误