当前位置: 首页 > news >正文

嵌入式 - 数据结构:数据结构基础与链表

目录

一、数据结构核心概念

1. 数据结构的本质

2. 代码质量的衡量标准

二、数据结构的分类体系

1. 逻辑结构

2. 存储结构

3. 常见数据结构

三、链表深度解析

1. 链表与顺序表的对比

2. 链表的分类及特点

3. 单向链表的实现

(1)节点定义

(2)空链表创建

(3)头插法

(4)链表遍历

(5)节点删除

四、工程管理工具 Makefile

1. Makefile 的作用

2. 基本使用流程

3. 语法规则

五、实战练习

1、 封装一个函数返回链表中第一个指定元素节点的地址

2、封装一个函数将链表中指定元素的值更新为新值

3、尾插法实现


一、数据结构核心概念

1. 数据结构的本质

程序的本质可以概括为 "程序 = 数据结构 + 算法",这一公式揭示了数据结构在程序设计中的核心地位。数据结构主要描述两方面内容:一是数据的存储和组织方式,二是操作这些数据对象的方法。

2. 代码质量的衡量标准

评价一段代码的优劣,通常从时间复杂度空间复杂度两个维度考量:

  • 时间复杂度:描述数据量增长与程序运行时间增长之间的比例关系,称为时间渐进复杂度函数。常见的时间复杂度从低到高排序如下:

    • O (1):运行时间恒定,不受数据量影响,如数组随机访问
    • O (logn):初期增长较快,数据量达到一定规模后趋于平缓,如二分查找
    • O (n):运行时间与数据量呈线性增长,如线性查找
    • O (nlogn):常见于高效排序算法,如快速排序、归并排序
    • O (n²)、O (n³):多项式级复杂度,多见于嵌套循环
    • O (2ⁿ):指数级复杂度,如递归实现的斐波那契数列
  • 空间复杂度:反映数据量增长与程序占用空间增长的比例关系。在实际开发中,往往需要在时间复杂度和空间复杂度之间做权衡,即所谓的 "以空间换时间" 或 "以时间换空间"。

二、数据结构的分类体系

1. 逻辑结构

逻辑结构描述数据元素之间的逻辑关系,主要分为:

  • 线性结构:元素之间为一对一关系,如各种结构
  • 非线性结构:包括一对多关系的结构和多对多关系的结构

2. 存储结构

存储结构关注数据在计算机中的物理存储方式,主要有四种:

  • 顺序存储:数据元素连续存放,如数组
  • 链式存储:通过指针链接分散的存储单元,如链表
  • 散列存储:利用哈希函数映射到存储空间,如哈希表
  • 索引存储:通过索引表快速定位数据位置

3. 常见数据结构

实际开发中常用的数据结构包括:

  • 顺序表
  • 链式表
  • 顺序栈
  • 链式栈
  • 顺序队列
  • 链式队列
  • 二叉树
  • 邻接表
  • 邻接矩阵

三、链表深度解析

1. 链表与顺序表的对比

顺序表(数组)和链表作为两种基本的线性结构,各有特点:

特性顺序表链表
存储空间连续不连续
空间利用需连续大空间,利用率低可利用零散小空间
访问效率随机访问方便(O (1))需顺序访问(O (n))
元素数量有限制(受初始化大小影响)理论上无上限
插入删除效率低(需移动元素)效率高(仅需修改指针)

顺序表的优势在于随机访问的高效性,而链表则在动态插入和删除操作上表现更优至。

2. 链表的分类及特点

  • 单向链表:节点仅包含指向下一节点的指针,访问方向单一

  • 双向链表:节点同时包含前驱和后继指针,可双向遍历

  • 循环链表:首尾节点相连,可从任一节点遍历整个链表

  • 内核链表:Linux 内核采用的通用链表结构,将指针嵌入数据结构而非作为外部包装

3. 单向链表的实现

(1)节点定义

单向链表节点通常包含数据域指针域两部分:

/* 链表存放数据的类型 */
typedef int datatype;/* 链表节点类型 */
typedef struct node {datatype data;      // 存放数据的空间struct node *pnext; // 存放下一个节点的地址
} linknode;//struct node 是结构体名称 是类型
//linknode 等价于上方 是类型(link-链 node-节点)

这种结构设计使得每个节点既可以存储数据,又能指向链表中的下一个节点。

(2)空链表创建

创建空链表时,需注意指针域的初始化:

  • 创建一个空的链表节点
  • data不需要赋值(最好赋值),空白节点不存放数据,主要为了保证链表操作的便利性
  • pnext必须赋值为NULL,表示该节点为最后一个节点
  • 将节点地址返回
/* 创建一个空链表 */
linknode *create_empty_linklist(void)
{linknode *ptmpnode = NULL;ptmpnode = malloc(sizeof(linknode));if (NULL == ptmpnode){perror("fail to malloc");return NULL;}ptmpnode->pnext = NULL;  // 空链表的指针必须指向NULLreturn ptmpnode;
}/* 创建新节点函数* 参数:data - 节点存储的数据* 返回值:成功返回节点指针,失败返回NULL */
Node* createNode(int data) {Node* newNode = (Node*)malloc(sizeof(Node));  // 动态分配内存空间if (newNode == NULL) {  // 内存分配失败检查printf("内存分配失败!\n");return NULL;}newNode->data = data;  // 设置节点数据newNode->next = NULL;  // 初始化指针域为NULLreturn newNode;        // 返回新节点地址
}

空链表的头节点不存放有效数据,主要用于简化链表操作,其指针域必须设置为 NULL至。

(3)头插法

头插法是在链表头部插入新节点的操作,步骤如下:

  1. 为新节点分配内存空间
  2. 将数据存入新节点的数据域
  3. 将新节点的指针域指向头节点原来的后继节点
  4. 更新头节点的指针域指向新节点
/* 插入一个节点(头插法) */
int insert_head_linklist(linknode *phead, datatype tmpdata)
{linknode *ptmpnode = NULL;// 申请空间ptmpnode = malloc(sizeof(linknode)); if (NULL == ptmpnode){perror("fail to malloc");return -1;}// 存放数据ptmpnode->data = tmpdata;// 存放下一个节点地址ptmpnode->pnext = phead->pnext;// 更新头节点的pnextphead->pnext = ptmpnode;return 0;
}

头插法的时间复杂度为 O (1),适合需要频繁在头部插入数据的场景至。

(4)链表遍历

遍历链表需从头节点开始,依次访问每个节点:

方法一:多用于遍历链表中所有节点元素
方法二:多用于找到链表最后一个节点

/*遍历链表节点元素*/
void show_linklist(linknode *phead)
{linknode *ptmpnode = NULL;ptmpnode = phead->pnext;//第一种遍历方法:全部成员都可访问while(ptmpnode != NULL){printf("%d ",ptmpnode->data);ptmpnode = ptmpnode->pnext;}// 多用于找到链表最后一个节点//  while(ptmpnode->pnext != NULL)// {//     printf("%d ",ptmpnode->data);//     ptmpnode = ptmpnode->pnext;// }printf("\n");return;
} 

遍历操作的时间复杂度为 O (n),需访问链表中的每个节点。实际开发中,可根据需求修改遍历函数,如添加数据过滤、统计等功能。

(5)节点删除

删除指定元素的节点需要两个指针协作:

  • 定义两个指针ptmpnode用来遍历链表查找要删除的节点元素,pprenode永远只想
  • ptmpnode的前一个节点
  • 当ptmpnode找到要删除的节点元素,让pprenode->pnext赋值为ptmpnode->pnext
  • 将要删除的节点元素释放
  • 让ptmpnode判断下一个节点元素是否要删除,直到该指针指向NULL为止
/* 删除指定的链表节点元素 */
int delete_linklist(linknode *phead, datatype tmpdata)
{linknode *pprenode = NULL;  // 前驱节点指针linknode *ptmpnode = NULL;  // 当前节点指针pprenode = phead;ptmpnode = phead->pnext;while (ptmpnode != NULL){if (ptmpnode->data == tmpdata){pprenode->pnext = ptmpnode->pnext;  // 跳过当前节点free(ptmpnode);  // 释放内存ptmpnode = pprenode->pnext;  // 继续检查下一个节点}else {// 未找到目标,两个指针同时后移ptmpnode = ptmpnode->pnext;pprenode = pprenode->pnext;}}return 0;
}

删除操作需注意避免内存泄漏,必须释放被删除节点的内存空间至。

四、工程管理工具 Makefile

1. Makefile 的作用

Makefile 是一款强大的工程管理工具,主要用于:

  • 管理代码的编译流程
  • 根据预设规则选择需要编译的代码
  • 通过依赖关系和时间戳判断是否需要重新编译

使用 Makefile 可以显著提高大型项目的编译效率,避免每次都重新编译所有文件。

2. 基本使用流程

  1. 在工程目录下创建名为 Makefile 或 makefile 的文件
  2. 编写编译规则,定义目标文件、依赖文件和编译命令
  3. 在终端执行make命令调用 Makefile 进行编译
  4. 编译成功后运行生成的可执行程序

3. 语法规则

Makefile 的核心语法规则为:

目标文件: 依赖文件列表生成目标的命令(必须以Tab开头)

例如,编译一个包含 main.c 和 linklist.c 的项目,Makefile 可写为:

app: main.o linklist.ogcc main.o linklist.o -o app
main.o: main.cgcc -c main.c -o main.o
linklist.o: linklist.cgcc -c linklist.c -o linklist.o
clean:rm -f *.o app

五、实战练习

1、 封装一个函数返回链表中第一个指定元素节点的地址

linknode *find_linklist(linknode *phead,datatype tmpdata)
{linknode *pprenode = NULL;linknode *ptmpnode = NULL; pprenode = phead;ptmpnode = phead->pnext;while(tmpdata != ptmpnode->data){ptmpnode = ptmpnode->pnext;pprenode = pprenode->pnext;}ptmpnode = pprenode->pnext;return ptmpnode;}

2、封装一个函数将链表中指定元素的值更新为新值

/*更新指定元素的值*/
int update_linklist(linknode *phead,datatype olddata,datatype newdata)
{linknode *ptmpnode = NULL;ptmpnode = phead->pnext;while (ptmpnode != NULL){if(ptmpnode->data == olddata){ptmpnode->data = newdata; }ptmpnode = ptmpnode->pnext;}return 0;
}

3、尾插法实现


/*尾插法:插入一个节点*/
int insert_tail_linklist(linknode *phead,datatype tmpdata)
{linknode *ptmpnode = NULL;linknode *ptail = phead;ptmpnode = malloc(sizeof(linknode));if(NULL == ptmpnode){perror("fall to malloc");return -1;}//遍历方法2:找到链表最后一个节点while(ptail->pnext != NULL){ptail = ptail->pnext;}//将尾节点的pnext指向当前节点ptail->pnext = ptmpnode;//更新数据和下一个节点地址ptmpnode->data = tmpdata;ptmpnode->pnext = NULL;return 0;}
http://www.dtcms.com/a/310980.html

相关文章:

  • opencv自定义滤波
  • 计算机网络:任播和负载均衡的区别
  • 机动车超时停车识别准确率↑32%:陌讯动态时序建模算法实战解析
  • c++显示优化
  • 原生JS使用svg-pan-zoom库平移和缩放svg
  • 【网络与爬虫 37】ScrapeFly深度解析:云端爬虫革命,告别复杂部署拥抱一键API
  • ICCV2025 | 对抗样本智能安全方向论文汇总 | 持续更新中~
  • 数字人开发01--后端服务配置
  • ABP VNext + Redis Bloom Filter:大规模缓存穿透防护与请求去重
  • 嵌入式第十八课!!数据结构篇入门及单向链表
  • Python 类三大方法体系深度解析:静态方法、类方法与实例方法
  • LeetCode 每日一题 2025/7/28-2025/8/3
  • js的BOM
  • Redis核心机制与实践深度解析:从持久化到分布式锁
  • 中科院开源HYPIR图像复原大模型:1.7秒,老照片变8K画质
  • 北斗变形监测在地质灾害预警中的应用
  • 机器学习消融实验:方法论演进、跨领域应用与前沿趋势
  • sqli-labs:Less-19关卡详细解析
  • 基于单片机智能雨刷器/汽车刮水器设计
  • CCF IVC 2025“汽车安全攻防赛” -- Crypto -- WriteUp
  • 进程 Vs 线程
  • 小迪23年-22~27——php简单回顾(1)
  • 滚珠花键在汽车制造中有哪些高要求?
  • 【05】VisionMaster入门到精通——圆查找
  • 内网渗透——红日靶场七
  • 排序算法-堆排序
  • MybatisPlus-静态工具DB
  • 在 AKS 中运行 Azure DevOps 私有代理-1
  • 云原生三剑客:Kubernetes + Docker + Spring Cloud 实战指南与深度整合
  • 基于 LFU 策略的存储缓存系统设计与实现