一、题目背景与要求
1. 题目描述
-
时间限制:1000 ms
-
内存限制:256 mb
-
节点定义:
cstruct Node { int Element; // 节点存储整数元素 struct Node * Next; // 指向下一个节点的指针 }; -
功能要求:从键盘输入5个整数,将整数按从小到大的顺序插入单链表,最后遍历输出链表中的整数。
2. 输入输出格式
| 类型 | 描述 |
|---|---|
| 输入描述 | 输入5个整数(空格分隔) |
| 输出描述 | 输出按升序排列的5个整数(空格分隔) |
3. 输入输出样例
- 输入:
5 3 4 2 1 - 输出:
1 2 3 4 5
二、核心知识点梳理
1. 单链表基础定义
单链表由若干节点组成,每个节点包含"数据域"(存储元素)和"指针域"(指向下一节点),通过指针串联成链式结构。
-
简化类型定义(常用写法):
ctypedef struct Node { int Element; struct Node * Next; } Node, *Linklist; // Linklist等价于struct Node*,简化指针声明
2. 单链表核心操作
实现题目需求需完成4个核心操作:
- 创建节点:为新节点分配内存并初始化;
- 有序插入:保证插入后链表始终升序;
- 遍历输出:从表头开始逐个打印节点元素;
- 内存释放:避免内存泄漏(编程好习惯)。
三、完整实现代码(优化版)
c
#include <stdio.h>
#include <stdlib.h>
// 简化节点类型定义:Node为结构体类型,Linklist为结构体指针类型
typedef struct Node {
int Element; // 数据域:存储整数
struct Node *Next; // 指针域:指向下一节点
} Node, *Linklist;
/**
* @brief 创建新节点并初始化
* @param val 节点要存储的整数值
* @return 新节点的指针(内存分配失败则退出程序)
*/
Linklist createNode(int val) {
// 分配节点内存,sizeof(Node)计算节点所占字节数
Linklist newNode = (Linklist)malloc(sizeof(Node));
// 内存分配失败的异常处理(新手易忽略)
if (newNode == NULL) {
printf("内存分配失败!\n");
exit(1); // 终止程序
}
newNode->Element = val; // 初始化数据域
newNode->Next = NULL; // 初始化指针域:新节点默认指向空
return newNode;
}
/**
* @brief 有序插入节点(升序)
* @param head 链表头节点指针
* @param val 要插入的整数值
* @return 插入后的链表头节点(可能因插入头部改变)
*/
Linklist insertSorted(Linklist head, int val) {
Linklist newNode = createNode(val); // 创建待插入节点
// 情况1:链表为空,新节点直接作为头节点
if (head == NULL) {
return newNode;
}
// 情况2:新节点值小于头节点,插入到链表头部
if (newNode->Element < head->Element) {
newNode->Next = head; // 新节点指向原头节点
return newNode; // 新节点成为新头节点
}
// 情况3:插入到链表中间或尾部
Linklist current = head; // 遍历指针,从头部开始
// 找到"最后一个值小于val"的节点(避免越界需先判断current->Next!=NULL)
while (current->Next != NULL && current->Next->Element < val) {
current = current->Next; // 指针后移
}
// 插入逻辑:先连后断,避免链表断裂
newNode->Next = current->Next; // 新节点指向current的下一节点
current->Next = newNode; // current指向新节点
return head; // 头节点未变,返回原头节点
}
/**
* @brief 遍历输出链表元素
* @param head 链表头节点指针
* 优化点:避免末尾多余空格,补充换行符
*/
void printList(Linklist head) {
Linklist current = head;
if (current == NULL) return; // 空链表直接返回
// 先打印第一个元素,避免末尾空格
printf("%d", current->Element);
current = current->Next;
// 后续元素前加空格
while (current != NULL) {
printf(" %d", current->Element);
current = current->Next;
}
printf("\n"); // 补充换行,符合输出规范
}
/**
* @brief 释放链表所有节点内存(避免内存泄漏)
* @param head 链表头节点指针
*/
void freeList(Linklist head) {
Linklist temp; // 临时指针,保存待释放节点
while (head != NULL) {
temp = head; // 记录当前节点
head = head->Next; // 头指针后移
free(temp); // 释放当前节点内存
}
}
int main() {
Linklist head = NULL; // 初始化链表头节点为空
int num, i; // 兼容C89标准,循环变量定义在外部
// 输入5个整数并有序插入链表
for (i = 0; i < 5; i++) {
scanf("%d", &num); // 读取单个整数
head = insertSorted(head, num); // 有序插入
}
printList(head); // 输出有序链表
freeList(head); // 释放链表内存
return 0;
}
四、关键逻辑拆解
1. 创建节点(createNode)
- 核心:用
malloc分配内存,必须检查内存是否分配成功(避免空指针); - 初始化:数据域赋值为输入值,指针域初始化为
NULL(避免野指针)。
2. 有序插入(insertSorted)
有序插入是核心,需处理3种边界情况:
| 情况 | 处理逻辑 |
|---|---|
| 链表为空 | 新节点直接作为头节点返回 |
| 插入到头部 | 新节点指针指向原头节点,新节点成为新头节点 |
| 插入中间/尾部 | 遍历找到"最后一个值小于val"的节点,先让新节点指向该节点的下一节点,再让该节点指向新节点 |
3. 遍历输出(printList)
- 优化点:先打印第一个元素,后续元素前加空格,避免输出末尾多余空格;
- 边界:空链表直接返回,避免空指针访问。
4. 内存释放(freeList)
- 逻辑:用临时指针保存当前节点,头指针后移后再释放当前节点(避免释放后指针失效);
- 必要性:C语言不会自动释放堆内存,必须手动
free,否则会导致内存泄漏。
五、常见优化点与注意事项
1. 命名规范
- 推荐使用驼峰命名(如
createNode而非createnode),增强代码可读性; - 函数名体现功能(如
insertSorted明确是"有序插入")。
2. 边界处理(新手易错点)
- 必须判断链表是否为空(
head == NULL); - 遍历链表时,先判断
current->Next != NULL再访问current->Next->Element(避免越界)。
3. 兼容性与格式
- 循环变量:C89标准要求循环变量定义在循环外,笔试/上机环境需注意;
- 输出格式:补充换行符,避免末尾空格,严格匹配题目输出样例。
4. 内存管理
- 只要用
malloc分配内存,必须对应free; - 释放链表时,需先移动头指针再释放节点,不能先释放再移动(否则指针失效)。
六、核心要点总结
- 有序插入核心:分3种情况(空链表、插头部、插中间/尾部),重点是"先连后断"避免链表断裂;
- 边界处理:空链表、插入头部、遍历越界是单链表编程的高频易错点,必须显式判断;
- 内存管理 :
malloc和free成对出现,避免内存泄漏和野指针; - 格式规范:输出避免末尾空格、补充换行,命名符合规范,提升代码鲁棒性和可读性。