单链表 - 有序插入并输出学习笔记

一、题目背景与要求

1. 题目描述

  • 时间限制:1000 ms

  • 内存限制:256 mb

  • 节点定义:

    c 复制代码
    struct Node {
        int Element; // 节点存储整数元素
        struct Node * Next; // 指向下一个节点的指针
    };
  • 功能要求:从键盘输入5个整数,将整数按从小到大的顺序插入单链表,最后遍历输出链表中的整数。

2. 输入输出格式

类型 描述
输入描述 输入5个整数(空格分隔)
输出描述 输出按升序排列的5个整数(空格分隔)

3. 输入输出样例

  • 输入:5 3 4 2 1
  • 输出:1 2 3 4 5

二、核心知识点梳理

1. 单链表基础定义

单链表由若干节点组成,每个节点包含"数据域"(存储元素)和"指针域"(指向下一节点),通过指针串联成链式结构。

  • 简化类型定义(常用写法):

    c 复制代码
    typedef 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
  • 释放链表时,需先移动头指针再释放节点,不能先释放再移动(否则指针失效)。

六、核心要点总结

  1. 有序插入核心:分3种情况(空链表、插头部、插中间/尾部),重点是"先连后断"避免链表断裂;
  2. 边界处理:空链表、插入头部、遍历越界是单链表编程的高频易错点,必须显式判断;
  3. 内存管理mallocfree成对出现,避免内存泄漏和野指针;
  4. 格式规范:输出避免末尾空格、补充换行,命名符合规范,提升代码鲁棒性和可读性。
相关推荐
智者知已应修善业2 小时前
【求等差数列个数/无序获取最大最小次大次小】2024-3-8
c语言·c++·经验分享·笔记·算法
坚持学习前端日记3 小时前
软件开发完整流程详解
学习·程序人生·职场和发展·创业创新
Wokoo73 小时前
开发者AI大模型学习与接入指南
java·人工智能·学习·架构
小猪佩奇TONY4 小时前
OpenCL 学习(3)---- OpenCL 第一个程序
学习
Huanzhi_Lin4 小时前
图形渲染管线流程笔记
笔记·图形渲染·shader·glsl
守护安静星空4 小时前
live555学习笔记
笔记·学习
航Hang*4 小时前
第1章:初识Linux系统——第13节:总复习②
linux·笔记·学习·centos
日更嵌入式的打工仔5 小时前
Ehercat代码解析中文摘录<2>
笔记·ethercat
YJlio5 小时前
PsPing 学习笔记(14.1):ICMP Ping 进阶——替代系统 ping 的正确姿势
windows·笔记·学习