深 入 剖 析 单 链 表:从 原 理 到 实 战 应 用

article/2025/6/23 12:07:59

深 入 剖 析 单 链 表:从 原 理 到 实 战 应 用

  • 顺 序 表 的 问 题
  • 单 链 表
    • 单 链 表 与 顺 序 表 区 别
    • 相 关 概 念
    • 链 表 定 义
    • 单 链 表 定 义
    • 存 储 结 构
  • 单 链 表 的 操 作 实 现
    • 代 码 全 貌 与 功 能 介 绍
    • 单 链 表 的 功 能 说 明
    • 代 码 效 果 展 示
    • 代 码 详 解
      • SList.h
      • SList.c
      • test.c
  • 总 结

💻作 者 简 介:曾 与 你 一 样 迷 茫,现 以 经 验 助 你 入 门 数据 结 构。
💡个 人 主 页:@笑口常开xpr 的 个 人 主 页
📚系 列 专 栏:硬 核 数 据 结 构 与 算 法
✨代 码 趣 语:巧 妙 的 数 据 结 构 搭 配 简 单 的 代 码,远 比 反 过 来 效 果 好 得 多。
💪代 码 千 行,始 于 坚 持,每 日 敲 码,进 阶 编 程 之 路。
📦gitee 链 接:gitee

在这里插入图片描述

         线 性 表 的 顺 序 存 储 结 构 的 特 点 是 逻 辑 关 系 上 相 邻 的 两 个 元 素 在 物 理 位 置 上 也 相 邻,因 此 可 以 随 机 存 取 表 中 任 一 元 素。然 而,从 另 一 方 面 来 看,这 个 特 点 也 铸 成 了 这 种 存 储 结 构 的 弱 点:在 作 插 入 或 删 除 操 作 时,需 移 动 大 量 元 素。本 篇 博 客 将 讨 论 线 性 表 的 另 一 种 表 示 方 法 - - - 链 式 存 储 结 构,由 于 它 不 要 求 逻 辑 上 相 邻 的 元 素 在 物 理 位 置 上 也 相 邻,因 此 它 没 有 顺 序 存 储 结 构 所 具 有 的 弱 点,但 同 时 也 失 去 了 顺 序 表 可 随 机 存 取 的 优 点。

顺 序 表 的 问 题

  1. 中 间 / 头 部 的 插 入 删 除,时 间 复 杂 度 为 O(N)。
  2. 增 容 需 要 申 请 新 空 间,拷 贝 数 据,释 放 旧 空 间。会 有 不 小 的 消 耗。
  3. 增 容 一 般 是 呈 2 倍 的 增 长,势 必 会 有 一 定 的 空 间 浪 费。例 如 当 前 容 量 为 100,满 了 以 后 增 容 到 200,我 们 再 继 续 插 入 了 5 个 数 据,后 面 没 有 数 据 插 入 了,那 么 就 浪 费 了 95 个 数 据 空 间。

单 链 表


单 链 表 与 顺 序 表 区 别

顺 序 表:使 用 一 组 地 址 连 续 的 存 储 单 元 来 依 次 存 放 线 性 表 的 结 点,因 此 结 点 的 逻 辑 顺 序 和 物 理 顺 序 是 一 样 的。

链 表:使 用 一 组 任 意 的 存 储 单 元 来 存 放 线 性 表 的 结 点,这 组 存 储 单 元 可 以 是 连 续 的,也 可 以 是 非 连 续 的,甚 至 是 零 散 分 布 在 内 存 的 任 何 位 置 上。因 此,链 表 的 逻 辑 顺 序 和 物 理 顺 序 不 一 样 相 同。


相 关 概 念

结 点:在 存 储 线 性 表 的 每 个 数 据 元 素 值 的 同 时 存 储 指 示 其 后 继 元 素 结 点 的 地 址(位 置)信 息,这 两 部 分 信 息 组 成 的 存 储 映 像 称 为 链 表 的 结 点。结 点 是 链 表 中 存 储 数 据 的 最 小 单 元,每 个 结 点 通 过 指 针 与 其 他 结 点 建 立 逻 辑 关 系,形 成 链 式 结 构。结 点 在 内 存 中 无 需 连 续 存 储,其 顺 序 由 指 针 指 向 决 定。

数 据 域:存 储 数 据 元 素 信 息 的 域,即 存 储 每个 结 点 的 值。

指 针 域:存 储 数 据 元 素 的 直 接 后 继 的 地 址(位 置)的域。

在这里插入图片描述

:指 针 域 中 存 储 的 信 息 称 作 指 针 或 链。

链 式 存 储 结 构:n 个 结 点 链 接 成 一 个 链 表,即 为 线 性 表 的 链 式 存 储 结 构。

线 性 链 表:链 表 中 的 每 个 结 点 中 只 包 含 一 个 指 针 域, 故 又 称 作 线 性 链 表 或 单 链 表。


链 表 定 义

         链 表 是 一 种 物 理 存 储 结 构 上 非 连 续非 顺 序 的 存 储 结 构,数 据 元 素 的 逻 辑 顺 序 是 通 过 链 表 中 的 指 针 链 接 次 序 实 现 的。

在这里插入图片描述


单 链 表 定 义

         单 链 表 是 一 种 通 过 指 针 串 联 节 点 的 动 态 数 据 结 构,每 个 节 点 包 含 数 据 域指 针 域(指 向 下 一 个 节 点 的 地 址)。与 顺 序 表 相 比,它 无 需 预 先 分 配 连 续 内 存 空 间,插 入 和 删 除 操 作 仅 需 修 改 指 针 指 向,具 有 高 效 的 动态 性。但 其 访 问 节 点 必 须 从 头 遍 历,时 间 复 杂 度 为 O(n)。


存 储 结 构

物 理 结 构

         物 理 结 构 是 指 数 据 元 素 及 其 逻 辑 关 系 在 计 算 机 内 存 中 的 具 体 存 储 方 式,实 实 在 在 数 据 在 内 存 中 的 变 化,关 注 的 是 “数 据 如 何 在 硬 件 中 实 际 存 放”。它 是 机 器 视 角 下 的 实 现 模 型,直 接 影 响 数 据 操 作 的 效 率。

在这里插入图片描述

逻 辑 结 构

         逻 辑 结 构 是 从 数 据 元 素 之 间 的 逻 辑 关 系 角 度 出 发,抽 象 描 述 数 据 元 素 之 间 的 关 联 方 式,不 涉 及 数 据 的 存 储 方 式 和 具 体 实 现。为 了 方 便 理 解,形 象 化 画 出 来 的。它 是 用 户 视 角 下 的 数 据 模 型,关 注 的 是 “数 据 元 素 如 何 相 互 连 接”。

在这里插入图片描述

单 链 表 的 操 作 实 现

代 码 全 貌 与 功 能 介 绍


         整 个 顺 序表 由 三 个 主 要 文 件 构 成:SList.h、SList.c 和 test.c。这 种 多 文 件 的 架 构 设 计,有 助 于 将 不 同 功 能 模 块 分 离,提 高 代 码 的 可 读 性、可 维 护 性 与 可 扩 展 性。

SList.h

         SList.h 包 含 了 单 链 表 所 需 的 头 文 件 引 用、常 量 定 义 以 及 函 数 声 明。

test.c

         test.c 是 单 链 表 的 主 逻 辑 文 件,负 责 处 理 用 户 输 入 和 代 码 流 程 的 控 制。

SList.c

SList.c 则 实 现 了 单 链 表 的 具 体 功 能 函 数。

下 面 展 示完 整 代 码

读 者 可 以 将 这 段 代 码 复 制 到 自 己 的 编 译 器 中 运 行。

SList.h

#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
//第一种写法
typedef int SLTDataType;
typedef struct SListNode
{SLTDataType data;struct SListNode* next;//指针的类型是结构体//SListNode* next;C语言不行,C++可以//STLnode* next;//编译器的查找规则,只能在重定义之后才能使用
}STLNode;
//第2种写法
//typedef int SLTDataType;
//
//struct SListNode
//{
//	SLTDataType data;
//	struct SListNode* next;//指针的类型是结构体
//	//SListNode* next;C语言不行,C++可以
//	//STLnode* next;//编译器的查找规则,只能在重定义之后才能使用
//};
//
//typedef struct SListNode STLNode;//创建结点并将值填入数据域中
STLNode* BuySTLNode(SLTDataType x);//输出
void STLPrint(STLNode* phead);//尾插
void STLPushBack(STLNode** phead, SLTDataType x);//头插
void STLPushFront(STLNode** pphead, SLTDataType x);//尾删
void STLPopBack(STLNode** phead);//头删
void STLPopFront(STLNode** pphead);//单链表查找
STLNode* STLFind(STLNode* pphead, SLTDataType x);//pos之前插入
void STLInsertFront(STLNode** pphead, STLNode* pos, SLTDataType x);//pos位置删除
void STLErase(STLNode** pphead, STLNode* pos);//pos后面插入
void STLInsertAfter(STLNode* pos, SLTDataType x);//pos位置后面删除
void STLEraseAfter(STLNode* pos);//链表销毁
void STLDestory(STLNode** pphead);//函数指针
void STLMiddlePos(int(*pf)(STLNode** pphead, SLTDataType x), STLNode** pphead, int num);void STLMiddlePush(int(*pf)(STLNode** pphead, SLTDataType x), STLNode** pphead);//从文件中加载
void STLLoadSList(STLNode** pphead);//将链表保存到文件
void STLSaveSList(STLNode** pphead);

test.c

#define _CRT_SECURE_NO_WARNINGS 1
#include "SList.h"
void menu()
{printf("****************************************************\n");printf("*****   0. STLExit          1. STLPrint        *****\n");printf("*****   2. STLPushBack      3. STLPopBack      *****\n");printf("*****   4. STLPushFront     5. STLPopFront     *****\n");printf("*****   6. STLInsertFront   7. STLErase        *****\n");printf("*****   8. STLInsertAfter   9. STLEraseAfter   *****\n");printf("****************************************************\n");
}
void test()
{int input = 0;STLNode* plist = NULL;STLLoadSList(&plist);do{menu();printf("请输入你想要进行的操作:>\n");scanf("%d", &input);switch (input){case 0:STLSaveSList(&plist);STLDestory(&plist);break;case 1:STLPrint(plist);break;case 2:STLMiddlePush(STLPushBack, &plist);break;case 3:STLPopBack(&plist);STLPrint(plist);break;case 4:STLMiddlePush(STLPushFront, &plist);break;case 5:STLPopFront(&plist);STLPrint(plist);break;case 6:STLMiddlePos(STLInsertFront, &plist, 6);break;case 7:STLMiddlePos(STLErase, &plist, 7);break;case 8:STLMiddlePos(STLInsertAfter, &plist, 8);break;case 9:STLMiddlePos(STLEraseAfter, &plist, 9);break;default:printf("无效输入,请重新输入\n");break;}} while (input);
}int main()
{test();return 0;
}

SList.c

#define _CRT_SECURE_NO_WARNINGS 1 
#include "SList.h"
void STLPrint(STLNode* phead)
{//空的顺序表可以打印,只是数据为空//assert(phead);//不能STLNode* cur = phead;//while(cur)while (cur != NULL)//cur->next != NULL最后一个数据没有输出,不能写{printf("%d->", cur->data);cur = cur->next;//cur的地址不断覆盖,不能++,不能保证空间是连续的//一块空间的初始地址和结束地址不一样,一块空间的结束地址和下一块空间的起始地址一样}printf("NULL\n");
}
STLNode* BuySTLNode(SLTDataType x)
{STLNode* newnode = (STLNode*)malloc(sizeof(STLNode));if (newnode == NULL){perror("SLPushBack");return NULL;}newnode->data = x;newnode->next = NULL;return newnode;
}
//上一个节点链接下一个节点的地址
//不为空链表的尾插,尾插的本质是原尾节点要存储新的尾节点的地址
void STLPushBack(STLNode** pphead, SLTDataType x)
{assert(pphead);//*pphead为地址一定不为空,值可以为空STLNode* newnode = BuySTLNode(x);if (*pphead == NULL){*pphead = newnode;}else{//找尾---尾结点STLNode* tail = *pphead;while (tail->next != NULL){tail = tail->next;}tail->next = newnode;//错误---头结点//while (tail != NULL)//{//	tail = tail->next;//}//tail = newnode;}
}
void STLPushFront(STLNode** pphead, SLTDataType x)
{assert(pphead);STLNode* newnode = BuySTLNode(x);newnode->next = *pphead;*pphead = newnode;
}
//一定不能为空,要断言
//pphead
void STLPopBack(STLNode** pphead)
{//必须有数据才能删除//检查assert(pphead);assert(*pphead);//if (*pphead == NULL)//{//	return;//}//只有一个节点//有多个节点if ((*pphead)->next == NULL){free(*pphead);*pphead = NULL;}else{//方法1//找尾STLNode* prev = NULL;STLNode* tail = *pphead;while (tail->next != NULL){prev = tail;tail = tail->next;}free(tail);tail = NULL;//tail是局部变量prev->next = NULL;//方法2//STLNode* tail = *pphead;//while (tail->next->next != NULL)//{//	tail = tail->next;//}//free(tail->next);//tail->next = NULL;}
}
void STLPopFront(STLNode** pphead)
{//检查assert(*pphead != NULL);//if (*pphead == NULL)//{//	return;//}STLNode* first = *pphead;*pphead = first->next;free(first);first = NULL;
}STLNode* STLFind(STLNode* pphead, SLTDataType x)
{STLNode* cur = pphead;while (cur != NULL){if (cur->data == x){return cur;}cur = cur->next;}return NULL;
}
void STLInsertFront(STLNode** pphead, STLNode* pos, SLTDataType x)
{//不能实现尾插assert(pos);assert(pphead);if (pos == *pphead){STLPushFront(pphead, x);}else{//找到pos的前一个位置STLNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}STLNode* newnode = BuySTLNode(x);prev->next = newnode;newnode->next = pos;}
}void STLErase(STLNode** pphead, STLNode* pos)
{assert(pphead);assert(pos);assert(*pphead);if (*pphead == pos){STLPopBack(pphead);}else{STLNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}prev->next = pos->next;free(pos);pos = NULL;//可以不写,因为pos为形参}
}
//pos后面插入
void STLInsertAfter(STLNode* pos, SLTDataType x)
{assert(pos);STLNode* newnode = BuySTLNode(x);newnode->next = pos->next;pos->next = newnode;//错误,原因是找不到pos->next//pos->next = newnode;//newnode->next = pos->next;
}
//假设有一个链表,只给出pos,让在前面插入,如何实现
//可以先在后面插入,然后交换//pos位置后面删除
void STLEraseAfter(STLNode* pos)
{assert(pos);assert(pos->next);//方法1//STLNode * del = pos->next;//pos->next = pos->next->next;//free(del);//del = NULL;//方法2STLNode* del = pos->next;pos->next = del->next;free(del);del = NULL;
}
void STLDestory(STLNode** pphead)
{assert(pphead);STLNode* cur = *pphead;while (cur){//逐个释放结点STLNode* next = cur->next;free(cur);cur = next;}*pphead = NULL;
}void STLMiddlePos(int(*pf)(STLNode** pphead, SLTDataType x), STLNode** pphead, int num)
{int x = 0;int y = 0;STLPrint(*pphead);if (num == 6){printf("请输入你想要插入的数字:>\n");scanf("%d", &x);printf("请输入你想要插入哪个数字的前面:>\n");scanf("%d", &y);STLNode* ret = STLFind(*pphead, y);STLInsertFront(pphead, ret, x);}else if (num == 7){printf("请输入你想要删除哪个数字:>\n");scanf("%d", &x);STLNode* ret = STLFind(*pphead, x);STLErase(pphead, ret);}else if (num == 8){printf("请输入你想要插入的数字:>\n");scanf("%d", &x);printf("请输入你想要插入哪个数字的后面:>\n");scanf("%d", &y);STLNode* ret = STLFind(*pphead, y);STLInsertAfter(ret, x);}else{printf("请输入你想要删除哪个数字后面的数字:>\n");scanf("%d", &x);STLNode* ret = STLFind(*pphead, x);STLEraseAfter(ret);}STLPrint(*pphead);
}void STLMiddlePush(int(*pf)(STLNode** pphead, SLTDataType x), STLNode** pphead)
{int x = 0;printf("请输入你想要插入的数字:>\n");scanf("%d", &x);pf(pphead, x);STLPrint(*pphead);
}void STLLoadSList(STLNode** pphead)
{FILE* pf = fopen("SList.txt", "r");if (pf == NULL){perror("STLLoadSList");return;}STLNode* cur = *pphead;SLTDataType data = 0;while (fscanf(pf, "%d", &data) == 1){STLPushBack(pphead, data);}fclose(pf);pf = NULL;
}
void STLSaveSList(STLNode** pphead)
{FILE* pf = fopen("SList.txt", "w");if (pf == NULL){perror("STLSaveSList");return;}STLNode* cur = *pphead;while (cur){fprintf(pf, "%d ", cur->data);cur = cur->next;}fclose(pf);pf = NULL;printf("保存文件成功\n");
}

单 链 表 的 功 能 说 明


代 码 效 果 展 示

菜 单 展 示

每 次 循 环 开 始 时 会 显 示 菜 单,内 容 包 括:
STLExit:退 出 程 序 并 将 单 链 表 保 存 到 文 件 中
STLPrint:输 出
STLPushBack:尾 插
STLPopBack:尾 删
STLPushFront:头 插
STLPopFront:头 删
STLFrontInsert:从 一 个 数 的 前 面 插 入
STLErase:擦 除 某 个 指 定 的 数
STLInsertAfter:从 一 个 数 的 后 面 插 入
STLEraseAfter:删 除 指 定 数 的 后 面 的 数 字

在这里插入图片描述

退 出 单 链 表(STLExit)

         输 入 0 后,程 序 会 将 单 链 表 中 的 信 息 保 存 到 文 件 “SList.txt” 中。释 放 内 存 并 销 毁 顺 序 表, 然 后 退 出 程 序。

在这里插入图片描述

输 出 单 链 表(STLPrint)

         输 入 1 后,程 序 会 在 屏 幕 上 显 示 目 前 已 经 保 存 的 单 链 表。

在这里插入图片描述

尾 插(STLPushBack)

         输 入 2 后,程 序 会 提 示 用 户 输 入 插 入 的 数 字。输 入 完 成 后,数 字 被 添 加 到 单 链 表 的 末 尾,然 后 输 出 修 改 后 的 单 链 表。

在这里插入图片描述

尾 删(STLPopBack)

         输 入 3 后,单 链 表 的 末 尾 的 一 个 数 字 会 被 删 除,然 后 输 出 修 改 后 的 单 链 表。

在这里插入图片描述

头 插(STLPushFront)

         输 入 4 后,程 序 会 提 示 用 户 输 入 插 入 的 数 字。输 入 完 成 后,数 字 被 添 加 到 单 链 表 的 第 一 个 位 置,然 后 输 出 修 改 后 的 单 链 表。

在这里插入图片描述

头 删(STLPopFront)

         输 入 5 后,单 链 表 的 第 一 个 位 置 的 数 字 会 被 删 除 并 输 出 删 除 后 的 单 链 表,然 后 输 出 修 改 后 的 单 链 表。

在这里插入图片描述

从 某 一 个 数 字 的 前 面 插 入(STLInsertFront)

         输 入 6 后,程 序 首 先 输 出 单 链 表,然 后 会 提 示 用 户 输 入 插 入 的 数 字 和 要 插 入 数 字 的 位 置。输 入 完 成 后,数 字 被 添 加 到 单 链 表 的 要 输 入 数 字 的 前 面,然 后 输 出 修 改 后 的 单 链 表。

在这里插入图片描述

删 除 指 定 的 数 字(STLErase)

         输 入 7 后,程 序 会 提 示 用 户 输 入 想 要 删 除 的 数 字。输 入 完 成 后,想 要 删 除 的 数 字 会 被 清 除,然 后 输 出 修 改 后 的 单 链 表。

在这里插入图片描述

从 某 一 个 数 字 的 后 面 插 入(STLInsertAfter)

         输 入 8 后,程 序 首 先 输 出 单 链 表,然 后 提 示 用 户 输 入 插 入 的 数 字 和 要 插 入 数 字 的 位 置。输 入 完 成 后,数 字 被 添 加 到 单 链 表 的 要 输 入 数 字 的 后 面,然 后 输 出 修 改 后 的 单 链 表。

在这里插入图片描述

删 除 指 定 数 字 后 面 的 数 字(STLEraseAfter)

         输 入 9 后,程 序 首 先 输 出 单 链 表,然 后 提 示 用 户 输 入 要 删 除 的 数 字。输 入 完 成 后,要 删 除 数 字 后 面 的 数 字 会 被 删 除,然 后 输 出 修 改 后 的 单 链 表。

在这里插入图片描述


代 码 详 解

SList.h

#pragma once

         #pragma once 是 一 个 预 处 理 指 令,用 于 防 止 头 文 件 被 重 复 包 含。在 大 型 项 目 中,多 个 源 文 件 可 能 会 包 含 同 一 个 头 文 件,使 用 #pragma once 可 以 确 保 头 文 件 的 内 容 只 被 编 译 一 次,避 免 重 复 定 义 的 错 误。

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

#include 指 令 用 于 引 入 必 要 的 标 准 库 头 文 件:

stdio.h:提 供 标 准 输 入 输 出 函 数,如 printf、scanf 等。
assert.h:提 供 assert 宏,用 于 在 运 行 时 检 查 程 序 的 断 言 条 件。
stdlib.h:提 供 内 存 管 理 函 数(如 malloc、realloc、free)和 其 他 标 准 库 函 数。

typedef int SLTDataType;

         #define 指 令 用 于 定 义 一 些 常 量:

typedef int SLTDataType:将 int 重 新 命 名 成 SLTDataType

typedef int SLTDataType;
typedef struct SListNode
{SLTDataType data; //数据域,存储节点数据struct SListNode* next;//指针域,指向下一个节点
}STLNode;

         定 义 了 一 个 名 为 SListNode 的 结 构 体 并 重 新 命 名 为 STLNode,用 于 表 示 整 个 单 链 表:

next:指 向 同 类 型 结 构 体 的 指 针,用 于 连 接 链 表 中 的 节 点。每 个 节 点 通 过 next 指 向 下 一 个 节 点,最 后 一 个 节 点 的 next 通 常 置 为 NULL(表 示 链 表 结 束)。

data:整 数,记 录 当 前 单 链 表 中 的 节 点 数 据。

注 意
1、必 须 使 用 struct 结 构 体 名* 声 明 指 针 域,如 struct SListNode* next;。
2、不 能 直 接 使 用 typedef 后 的 别 名 STLNode* next;因 为 typedef 是 在 结 构 体 声 明 之 后 才 生 效。

//创建结点并将值填入数据域中
STLNode* BuySTLNode(SLTDataType x);//输出
void STLPrint(STLNode* phead);//尾插
void STLPushBack(STLNode** phead, SLTDataType x);//头插
void STLPushFront(STLNode** pphead, SLTDataType x);//尾删
void STLPopBack(STLNode** phead);//头删
void STLPopFront(STLNode** pphead);//单链表查找
STLNode* STLFind(STLNode* pphead, SLTDataType x);//pos之前插入
void STLInsertFront(STLNode** pphead, STLNode* pos, SLTDataType x);//pos位置删除
void STLErase(STLNode** pphead, STLNode* pos);//pos后面插入
void STLInsertAfter(STLNode* pos, SLTDataType x);//pos位置后面删除
void STLEraseAfter(STLNode* pos);//链表销毁
void STLDestory(STLNode** pphead);//函数指针
void STLMiddlePos(int(*pf)(STLNode** pphead, SLTDataType x), STLNode** pphead, int num);void STLMiddlePush(int(*pf)(STLNode** pphead, SLTDataType x), STLNode** pphead);//从文件中加载
void STLLoadSList(STLNode** pphead);//将链表保存到文件
void STLSaveSList(STLNode** pphead);

函 数 声 明,定 义 了 对 单 链 表 进 行 操 作 的 各 个 功 能 函 数:
BuySTLNode:动 态 分 配 内 存 创 建 1 个 新 节 点,初 始 化 数 据 域 为 x,指 针 域 为 NULL。主 要 用 于 插 入 节 点 时 调 用。
STLPrint:遍 历 链 表 并 输 出 每 个 节 点 的 数 据,格 式 为 数 据 -> 数 据 -> NULL。
STLPushBack:在 链 表 尾 部 插 入 新 节 点,数 据 为 x。
STLPushFront:在 链 表 头 部 插 入 新 节 点,数 据 为 x。
STLPopBack:删 除 链 表 尾 部 节 点。
STLPopFront:删 除 链 表 头 部 节 点。
STLFind:遍 历 链 表 查 找 数 据 为 x 的 节 点。
STLInsertFront:在 目 标 节 点 pos 之 前 插 入 新 节 点,数 据 为 x。
STLErase:用 于 从 顺 序 表 中 删 除 指 定 的 数 字。
STLInsertAfter:在 目 标 节 点 pos 之 后 插 入 新 节 点,数 据 为 x。
STLEraseAfter:删 除 目 标 节 点 pos 的 后 继 节 点。
STLDestory:释 放 链 表 所 有 节 点 内 存,并 将 头 指 针 置 为 NULL。
STLMiddlePos:通 过 函 数 指 针 动 态 调 用 插 入 / 删 除 函 数,处 理 用 户 输 入 的 位 置 操 作。
STLMiddlePush:通 过 函 数 指 针 动 态 调 用 插 入 函 数,处 理 用 户 输 入 的 插 入 值。
STLLoadSList:从 文 件 中 读 取 数 据,通 过 尾 插 法 构 建 链 表。
STLSaveSList:将 链 表 数 据 写 入 文 件。

SList.c

遍 历 与 打 印(STLPrint)

void STLPrint(STLNode* phead)
{//空的顺序表可以打印,只是数据为空//assert(phead);//不能STLNode* cur = phead;//while(cur)while (cur != NULL)//cur->next != NULL最后一个数据没有输出,不能写{printf("%d->", cur->data);cur = cur->next;//cur的地址不断覆盖,不能++,不能保证空间是连续的//一块空间的初始地址和结束地址不一样,一块空间的结束地址和下一块空间的起始地址一样}printf("NULL\n");
}

注 意

1、允 许 空 链 表(直 接 输 出 NULL),这 里 不 能 断 言,如 果 断 言 程 序 会 报 错。不 需 要 断 言 头 指 针 phead 非 空 的 原 因 是:允 许 空 链 表 作 为 合 法 输 入,如 果 链 表 为 空 则 输 出 NULL。循 环 条 件 cur != NULL 确 保 不 会 访 问 空 指 针,即 使 phead 为 NULL 也 不 会 引 发 错 误。而 顺 序 表 需 要 断 言 是 为 了 检 查 指 针 的 有 效 性,在 空 的 顺 序 表 中 可 能 存 在 野 指 针,头 指 针 phead 是 野 指 针(未 初 始 化 或 指 向 已 释 放 的 内 存),直 接 解 引 用 *phead 会 触 发 未 定 义 行 为。
2、使 用 cur != NULL 而 非 cur->next != NULL,避 免 遗 漏 最 后 一 个 节 点。
3、使 用 cur = cur->next; 而 不 使 用 ++ 的 原 因 是 指 针 的 算 术 运 算 仅 适 用 于 连 续 内 存 的 场 景,此 时 相 邻 元 素 的 地 址 差 是 固 定 的(等 于 单 个 元 素 的 大 小)。单 链 表 的 节 点 地 址 是 动 态 分 配 的,相 邻 节 点 的 地 址 差 不 确 定,无 法 通 过 固 定 偏 移 量 访 问。只 能 通 过 节 点 的 next 指 针 获 取 后 继 节 点 的 地 址,即 cur = cur->next;

总 结

顺 序 表
1、顺 序 表 的 指 针 失 效 风 险 更 高
顺 序 表 的 表 头 指 针 直 接 指 向 内 存 块,若 指 针 无 效(如 NULL 或 野 指 针),第 一 步 访 问 就 会 崩 溃。
2、顺 序 表 的 遍 历 方 式 不 依 赖 后 续 指 针
顺 序 表 通 过 下 标 或 偏 移 量 遍 历,无 需 检 查 每 个 元 素 的 “下 一 个 指 针” 是 否 有 效,因 此 指 针 有 效 性 是 遍 历 的 前 提,必 须 在 开 头 断 言。

单 链 表
1、单 链 表 的 节 点 通 过 指 针 链 接,节 点 在 内 存 中 非 连 续 存 储,每 个 节 点 包 含 数 据 域 和 指 向 下 一 节 点 的 指 针 next。
2、表 头 指 针 可 能 为 NULL(空 链 表),此 时 直 接 返 回 即 可,无 需 访 问 内 存。

节 点 创 建 与 初 始 化(BuySTLNode)

STLNode* BuySTLNode(SLTDataType x)
{STLNode* newnode = (STLNode*)malloc(sizeof(STLNode));if (newnode == NULL){perror("SLPushBack");return NULL;}newnode->data = x;newnode->next = NULL;return newnode;
}

作 用:动 态 分 配 内 存 并 初 始 化 新 节 点,返 回 节 点 指 针。
内 存 安 全:malloc 后 检 查 NULL,防 止 内 存 分 配 失 败 导 致 程 序 崩 溃。

尾 插

void STLPushBack(STLNode** pphead, SLTDataType x)
{assert(pphead);//*pphead为地址一定不为空,值可以为空STLNode* newnode = BuySTLNode(x);if (*pphead == NULL){*pphead = newnode;}else{//找尾---尾结点STLNode* tail = *pphead;while (tail->next != NULL){tail = tail->next;}tail->next = newnode;//错误---头结点//while (tail != NULL)//{//	tail = tail->next;//}//tail = newnode;}
}

1、使 用 二 级 指 针 的 原 因 是 因 为 单 链 表 的 头 指 针 本 质 是 一 个 指 针 变 量,单 链 表 的 头 指 针 为 STLNode* plist,它 存 储 的 是 链 表 第 一 个 节 点 的 地 址,函 数 内 部 对 plist 的 修 改 不 会 影 响 实 参 pphead。当 函 数 参 数 为 二 级 指 针
STLNode
** pphead 时,pphead 指 向 实 参 plist 的 地 址。通 过 解 引 用 可 以 访 问 并 修 改 实 参 plist 的 值。
2、在 尾 插 时 应 先 创 建 节 点 然 后 遍 历 单 链 表 将 节 点 链 接 起 来。

头 插

void STLPushFront(STLNode** pphead, SLTDataType x)
{assert(pphead);STLNode* newnode = BuySTLNode(x);newnode->next = *pphead;*pphead = newnode;
}

         头 插 时 应 首 先 创 建 新 节 点,然 后 新 节 点 的 next 指 向 原 头 节 点 最 后 更 新 头 指 针 为 新 节 点 地 址。

尾 删

void STLPopBack(STLNode** pphead)
{//必须有数据才能删除//检查assert(pphead);assert(*pphead);//if (*pphead == NULL)//{//	return;//}//只有一个节点//有多个节点if ((*pphead)->next == NULL){free(*pphead);*pphead = NULL;}else{//方法1//找尾STLNode* prev = NULL;STLNode* tail = *pphead;while (tail->next != NULL){prev = tail;tail = tail->next;}free(tail);tail = NULL;//tail是局部变量prev->next = NULL;//方法2//STLNode* tail = *pphead;//while (tail->next->next != NULL)//{//	tail = tail->next;//}//free(tail->next);//tail->next = NULL;}
}

         该 函 数 通 过 二 级 指 针 操 作 链 表,在 确 保 链 表 非 空 的 前 提 下,通 过 遍 历 找 到 尾 节 点 及 其 前 驱,释 放 尾 节 点 并 正 确 更 新 指 针,实 现 单 链 表 的 尾 删 操 作。

头 删

void STLPopFront(STLNode** pphead)
{//检查assert(*pphead != NULL);//if (*pphead == NULL)//{//	return;//}STLNode* first = *pphead;*pphead = first->next;free(first);first = NULL;
}

         该 函 数 通 过 二 级 指 针 确 保 链 表 非 空 后,将 头 指 针 更 新 为 原 头 节 点 的 下 一 个 节 点,并 释 放 原 头 节 点 内 存,实 现 单 链 表 的 头 删 操 作。

查 找 数 字

STLNode* STLFind(STLNode* pphead, SLTDataType x)
{STLNode* cur = pphead;while (cur != NULL){if (cur->data == x){return cur;}cur = cur->next;}return NULL;
}

         该 函 数 遍 历 链 表,若 找 到 数 据 域 等 于 x 的 节 点 则 返 回 其 地 址,否 则 返 回 NULL,实 现 单 链 表 的 查 找 操 作。

指 定 数 字 的 后(前) 面 插 入

void STLInsertFront(STLNode** pphead, STLNode* pos, SLTDataType x)
{//不能实现尾插assert(pos);assert(pphead);if (pos == *pphead){STLPushFront(pphead, x);}else{//找到pos的前一个位置STLNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}STLNode* newnode = BuySTLNode(x);prev->next = newnode;newnode->next = pos;}
}
void STLInsertAfter(STLNode* pos, SLTDataType x)
{assert(pos);STLNode* newnode = BuySTLNode(x);newnode->next = pos->next;pos->next = newnode;//错误,原因是找不到pos->next//pos->next = newnode;//newnode->next = pos->next;
}

         STLInsertFront 函 数 在 给 定 节 点 pos 前 插 入 新 节 点(若 pos 是 头 节 点 则 调 用 头插 法),需 通 过 二 级 指 针 修 改 头 指 针;STLInsertAfter 函 数 在 给 定 节 点 pos 后 插 入 新 节 点,直 接 操 作 pos 的 next 指 针,无 需 二 级 指 针。

删 除 指 定 位 置 后 面 的 数字

void STLEraseAfter(STLNode* pos)
{assert(pos);assert(pos->next);//方法1//STLNode * del = pos->next;//pos->next = pos->next->next;//free(del);//del = NULL;//方法2STLNode* del = pos->next;pos->next = del->next;free(del);del = NULL;
}

         通 过 断 言 确 保 给 定 节 点 pos 及 其 后 继 节 点 存 在,删 除 pos 的 后 继 节 点 并 释 放 其 内 存,实 现 单 链 表 的 指 定 位 置 后 删 除 操 作。

数 据 持 久 化

void STLLoadSList(STLNode** pphead)
{FILE* pf = fopen("SList.txt", "r");if (pf == NULL){perror("STLLoadSList");return;}STLNode* cur = *pphead;SLTDataType data = 0;while (fscanf(pf, "%d", &data) == 1){STLPushBack(pphead, data);}fclose(pf);pf = NULL;
}
void STLSaveSList(STLNode** pphead)
{FILE* pf = fopen("SList.txt", "w");if (pf == NULL){perror("STLSaveSList");return;}STLNode* cur = *pphead;while (cur){fprintf(pf, "%d ", cur->data);cur = cur->next;}fclose(pf);pf = NULL;printf("保存文件成功\n");
}

         这 两 个 函 数 实 现 了 单 链 表 的 文 件 读 写 功 能,STLLoadSList 从 文 件 SList.txt 读取 整 数 数 据,通 过 尾 插 法 构 建 链 表;STLSaveSList 将 链 表 中 的 数 据 写 入SList.txt,格 式 为 空 格 分 隔 的 整 数 序 列。两 者 均 包 含 完 整 的 文 件 操 作 错 误 处 理,确 保 内 存 安 全 和 文 件 资 源 释 放。

test.c

菜 单 界 面

void menu()
{printf("****************************************************\n");printf("*****   0. STLExit          1. STLPrint        *****\n");printf("*****   2. STLPushBack      3. STLPopBack      *****\n");printf("*****   4. STLPushFront     5. STLPopFront     *****\n");printf("*****   6. STLInsertFront   7. STLErase        *****\n");printf("*****   8. STLInsertAfter   9. STLEraseAfter   *****\n");printf("****************************************************\n");
}

使 用 星 号 (*) 绘 制 边 框,提 供 清 晰 的 视 觉 界 面。
数 字 编 号 对 应 不 同 功 能,方 便 用 户 选 择。

主 循 环 逻 辑

void test()
{int input = 0;STLNode* plist = NULL;STLLoadSList(&plist);do{menu();printf("请输入你想要进行的操作:>\n");scanf("%d", &input);switch (input){case 0:STLSaveSList(&plist);STLDestory(&plist);break;case 1:STLPrint(plist);break;case 2:STLMiddlePush(STLPushBack, &plist);break;case 3:STLPopBack(&plist);STLPrint(plist);break;case 4:STLMiddlePush(STLPushFront, &plist);break;case 5:STLPopFront(&plist);STLPrint(plist);break;case 6:STLMiddlePos(STLInsertFront, &plist, 6);break;case 7:STLMiddlePos(STLErase, &plist, 7);break;case 8:STLMiddlePos(STLInsertAfter, &plist, 8);break;case 9:STLMiddlePos(STLEraseAfter, &plist, 9);break;default:printf("无效输入,请重新输入\n");break;}} while (input);
}

循 环 控 制:使 用 do-while 确 保 至 少 执 行 一 次 菜 单 选 择。
选 项 处 理:通 过 switch 分 支 调 用 不 同 功 能 函 数。
安 全 退 出:退 出 前 保 存 数 据 并 释 放 内 存,防 止 资 源 泄 漏。

程 序 入 口

int main()
{test();return 0;
}

         main 函 数 是 程 序 的 入 口 点,调 用 test 函 数 启 动 顺 序 表,最 后 返 回 0 表 示 程 序 正 常 结 束。

在这里插入图片描述


总 结

         至 此,关 于 数 据 结 构 单 链 表 的 探 索 暂 告 一 段 落,但 你 的 编 程 征 程 才 刚 刚 启 航。编 写 代 码 是 与 计 算 机 逻 辑 深 度 对 话,过 程 中 虽 会 在 结 构 设 计、算 法 实 现 的 困 境 里 挣 扎,但 这 些 磨 砺 加 深 了 对 代 码 逻 辑 和 数 据 组 织 的 理 解。愿 你 合 上 电 脑 后,灵 感 不 断,在 数 据 结 构 的 世 界 里 持 续 深 耕,书 写 属 于 自 己 的 编 程 传 奇,下 一 次 开 启,定 有 全 新 的 精 彩 等 待。小 编 期 待 重 逢,盼 下 次 阅 读 时 见 证 你 们 更 大 的 进 步,共 赴 代 码 之 约!


http://www.hkcw.cn/article/QuRpDXStvK.shtml

相关文章

【LLM相关知识点】关于LLM项目实施流程的简单整理(一)

【LLM相关知识点】关于LLM项目实施流程的简单整理&#xff08;一&#xff09; 文章目录 【LLM相关知识点】关于LLM项目实施流程的简单整理&#xff08;一&#xff09;零、学习计划梳理&#xff1a;结合ChatGPT从零开始学习LLM & 多模态大模型一、大模型相关应用场景和头部企…

Vue 核心技术与实战day07

1. vuex概述 2. 构建 vuex [多组件数据共享] 环境 <template><div id"app"><h1>根组件- {{ title }}- {{ count }}</h1><input :value"count" input"handleInput" type"text"><Son1></Son1>…

【android bluetooth 案例分析 04】【Carplay 详解 3】【Carplay 连接之车机主动连手机】

1. 背景 在前面的文章中&#xff0c;我们已经介绍了 carplay 在车机中的角色划分&#xff0c; 并实际分析了 手机主动连接车机的案例。 感兴趣可以 查看如下文章介绍。 【android bluetooth 案例分析 04】【Carplay 详解 1】【CarPlay 在车机侧的蓝牙通信原理与角色划分详解】…

【stm32开发板】单片机最小系统原理图设计

一、批量添加网络标签 可以选择浮动工具中的N&#xff0c;单独为引脚添加网络标签。 当芯片引脚非常多的时候&#xff0c;选中芯片&#xff0c;右键选择扇出网络标签/非连接标识 按住ctrl键即可选中多个引脚 点击将引脚名称填入网络名 就完成了引脚标签的批量添加 二、电源引…

Linux --OS和PCB

目录 认识冯诺依曼系统 操作系统概念与定位 1.概念 2.设计OS的目的 3.OS的核心功能 4.系统调⽤和库函数概念 深⼊理解进程概念&#xff0c;了解PCB 1.基本概念与基本操作 2.描述进程-PCB 基本概念 task_ struct 的内容分类 认识冯诺依曼系统 在计算机中小到个人的笔…

2025最新版在Windows上安装Redis(仅限开发环境)

使用一位GitHub的博主做的Redis-Windows,截止现在更新到8.0.2 Releases redis-windows/redis-windows GitHub https://github.com/redis-windows/redis-windows/releases 我使用6.2.18版本做例子,使用6.2以上版本,因为一些语法,比如lpop,rpop,zrange,zdiff集合操作比旧版有…

[python]Prophet‘ object has no attribute ‘stan_backend‘解决方法

测试环境&#xff1a; prophet1.1.4 写代码&#xff1a; from prophet import Prophet modelProphet() print(123) 在anaconda prompt里面没有报错&#xff0c;但是打开jupyter notebook会报错Prophet object has no attribute stan_backend&#xff0c;据此猜测jupyter应该…

Python----目标检测(《基于区域提议网络的实时目标检测方法》和Faster R-CNN)

一、《基于区域提议网络的实时目标检测方法》 1.1、基本信息 标题&#xff1a;Faster R-CNN: Towards Real-Time Object Detection with Region Proposal Networks 作者&#xff1a;任少卿&#xff08;中国科学技术大学、微软研究院&#xff09;、何凯明&#xff08;微软研究…

流媒体基础解析:从压缩到传输的基本了解

流媒体&#xff0c;又称为流式媒体&#xff0c;已成为现代网络视频传输的核心技术。其基本原理是将连续的影像和声音信息经过精心设计的压缩&#xff08;编码&#xff09;处理后&#xff0c;妥善存放在网站服务器上。随后&#xff0c;这些压缩后的数据通过网络高效传输至终端用…

【MFC】如何设置让exe的控制台不会跟着exe退出而退出

在 Windows 下&#xff0c;MFC 程序&#xff08;如 echo.exe&#xff09;如果用 AllocConsole 创建了控制台窗口&#xff0c;默认情况下&#xff0c;当主程序&#xff08;exe&#xff09;退出时&#xff0c;控制台窗口也会自动关闭。这是操作系统的行为&#xff0c;不能直接阻止…

图像风格迁移笔记

图像风格迁移 最早实现风格迁移的原理:损失函数内容损失函数风格损失函数融合内容损失函数与风格损失函数可以融合多种风格图片的效果同一个网络可以生成多种风格图像的效果效果改进最早实现风格迁移的原理: 最早出现的论文的实现想法是将风格图像、内容图像、白噪声图像输入…

浏览器隐私:原理与检测方法

引言 浏览器信号和详细信息是在线识别用户和防止欺诈的关键。这些数据包括用户代理字符串、JavaScript设置和屏幕分辨率等信息&#xff0c;有助于区分不同的浏览器。然而&#xff0c;一些用户会有意修改这些信号&#xff0c;使用用户代理欺骗等方法来隐藏自己的身份。虽然一些…

python:在 PyMOL 中如何查看和使用内置示例文件?

参阅&#xff1a;开源版PyMol安装保姆级教程 百度网盘下载 提取码&#xff1a;csub pip show pymol 简介: PyMOL是一个Python增强的分子图形工具。它擅长蛋白质、小分子、密度、表面和轨迹的3D可视化。它还包括分子编辑、射线追踪和动画。 可视化示例‌&#xff1a;打开 PyM…

设计模式——建造者设计模式(创建型)

摘要 本文详细介绍了建造者设计模式&#xff0c;这是一种创建型设计模式&#xff0c;旨在将复杂对象的构建过程与其表示分离&#xff0c;便于创建不同表示。文中阐述了其设计意图&#xff0c;如隐藏创建细节、提升代码可读性和可维护性&#xff0c;并通过构建电脑的示例加以说…

深入Java性能调优:原理详解与实战

一、JVM内存模型与GC机制 原理&#xff1a; 堆内存结构&#xff1a; 新生代&#xff1a;Eden 2个Survivor区&#xff08;Minor GC&#xff09; 老年代&#xff1a;长期存活对象&#xff08;Major GC/Full GC&#xff09; 元空间&#xff1a;类元信息&#xff08;替代永久代…

acwing刷题

目录 6122. 农夫约翰的奶酪块 6123. 哞叫时间 6122. 农夫约翰的奶酪块 #include <iostream> using namespace std; int res; int n, q; int X[1010][1010]; int Y[1010][1010]; int Z[1010][1010]; void solve() {int x, y, z;cin >> x >> y >> z;X…

姜老师的MBTI课程:MBTI是可以转变的

我们先来看内向和外向这条轴&#xff0c;I和E内向和外向受先天遗传因素的影响还是比较大的&#xff0c;因为它事关到了你的硬件&#xff0c;也就是大脑的模型。但是我们在大五人格的排雷避坑和这套课程里面都强调了一个观点&#xff0c;内向和外向各有优势&#xff0c;也各有不…

leetcode hot100刷题日记——34.将有序数组转换为二叉搜索树

First Blood&#xff1a;什么是平衡二叉搜索树&#xff1f; 二叉搜索树&#xff08;BST&#xff09;的性质 左小右大&#xff1a;每个节点的左子树中所有节点的值都小于该节点的值&#xff0c;右子树中所有节点的值都大于该节点的值。 子树也是BST&#xff1a;左子树和右子树也…

使用yocto搭建qemuarm64环境

环境 yocto下载 # 源码下载 git clone git://git.yoctoproject.org/poky git reset --hard b223b6d533a6d617134c1c5bec8ed31657dd1268 构建 # 编译镜像 export MACHINE"qemuarm64" . oe-init-build-env bitbake core-image-full-cmdline 运行 # 跑虚拟机 export …

探索TiDB数据库:WordPress在分布式数据库上的部署实践

作者&#xff1a; 江湖有缘 原文来源&#xff1a; https://tidb.net/blog/359d4e00 引言 在当今数据驱动的互联网应用中&#xff0c;数据库的性能与可扩展性已成为系统架构中的关键一环。WordPress 作为全球最流行的网站内容管理系统之一&#xff0c;传统上依赖于 MySQL 等…