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

嵌入式 数据结构学习(四) 双向链表详解与工程管理

一、双向链表基础

双向链表示意图(指针永远指向内存开始的区域)

二、双向链表核心操作

1. 创建双向链表

c

/*** 创建双向链表头节点* @return 成功返回链表指针,失败返回NULL*/
struct DouLinkList* CreateDouLinkList()
{// 分配链表头结构体内存struct DouLinkList* dl = (struct DouLinkList*)malloc(sizeof(struct DouLinkList));if(NULL == dl){fprintf(stderr, "CreateDouLinkList malloc error\n");return NULL;}// 初始化链表为空状态dl->head = NULL;  // 头指针置空dl->clen = 0;     // 长度计数器归零return dl;
}

2. 头插法插入节点

 图解第一种情况:

 图解第二种情况:

 

c

/*** 在链表头部插入新节点* @param dl 链表指针* @param data 要插入的数据指针* @return 成功返回0,失败返回1*/
int InsertHeadDouLinkList(struct DouLinkList* dl, struct DATATYPE* data)
{// 为新节点分配内存struct DouNode* newnode = (struct DouNode*)malloc(sizeof(struct DouNode));if(NULL == newnode){fprintf(stderr, "InsertHeadDouLinkList malloc failed\n");return 1;}// 拷贝数据到新节点memcpy(&newnode->data, data, sizeof(struct DATATYPE));// 初始化新节点指针newnode->next = NULL;newnode->prev = NULL;// 新节点指向原头节点newnode->next = dl->head;// 如果原链表非空,设置原头节点的前驱指针if(dl->head){dl->head->prev = newnode;}// 更新链表头指针dl->head = newnode;// 链表长度增加dl->clen++;return 0;
}

3. 双向遍历链表

c

/*** 遍历打印链表内容* @param dl 链表指针* @param dir 遍历方向(FORWADR正向/BACKWADR反向)* @return 总是返回0*/
int ShowDouLinkList(struct DouLinkList* dl, DIR dir)
{struct DouNode* tmp = dl->head;if(FORWADR == dir)  // 正向遍历{printf("Forward traversal:\n");while(tmp){// 打印节点数据printf("Name: %s, Sex: %c, Age: %d, Score: %d\n", tmp->data.name, tmp->data.sex, tmp->data.age, tmp->data.score);tmp = tmp->next;  // 移动到下一个节点}}else if(BACKWADR == dir)  // 反向遍历{printf("Backward traversal:\n");// 先移动到链表尾部while(tmp->next){tmp = tmp->next;}// 从尾部向前遍历while(tmp){printf("Name: %s, Sex: %c, Age: %d, Score: %d\n", tmp->data.name, tmp->data.sex, tmp->data.age, tmp->data.score);tmp = tmp->prev;  // 移动到前一个节点}}return 0;
}

判断双链表是否为空
int IsEmptyDouLinkList(struct DouLinkList* dl) 
{
    return 0 == dl->clen; // 若链表长度(clen)为0,返回1(真),否则返回0(假)
}

获取双链表长度
int GetSizeDouLinkList(struct DouLinkList* dl) 
{
    return dl->clen; // 直接返回链表长度(clen)
}

4. 尾插法插入节点

c

/*** 在链表尾部插入新节点* @param dl 链表指针* @param data 要插入的数据指针* @return 成功返回0,失败返回1*/
int InserTailDouLinkList(struct DouLinkList* dl, struct DATATYPE* data)
{// 空链表直接调用头插法if(IsEmptyDouLinkList(dl)){return InsertHeadDouLinkList(dl, data);}// 查找尾节点struct DouNode* tmp = dl->head;while(tmp->next){tmp = tmp->next;}// 创建新节点struct DouNode* newnode = malloc(sizeof(struct DouNode));if(newnode == NULL){fprintf(stderr, "InserTailDouLinkList malloc failed\n");return 1;}// 初始化新节点memcpy(&newnode->data, data, sizeof(struct DATATYPE));newnode->next = NULL;       // 尾节点的next为NULLnewnode->prev = tmp;        // 前驱指向原尾节点// 将新节点链接到链表尾部tmp->next = newnode;// 链表长度增加dl->clen++;return 0;
}

5. 指定位置插入节点

 

c

/*** 在指定位置插入新节点* @param dl 链表指针* @param data 要插入的数据指针* @param pos 插入位置(0-based)* @return 成功返回0,失败返回1*/
int InserPosDouLinkList(struct DouLinkList* dl, struct DATATYPE* data, int pos)
{int len = GetSizeDouLinkList(dl);// 检查位置合法性if(pos < 0 || pos > len){fprintf(stderr, "Invalid position %d\n", pos);return 1;}// 处理头插和尾插特殊情况if(0 == pos){return InsertHeadDouLinkList(dl, data);}else if(pos == len){return InserTailDouLinkList(dl, data);}// 创建新节点struct DouNode* newnode = malloc(sizeof(struct DouNode));if(newnode == NULL){fprintf(stderr, "InserPosDouLinkList malloc failed\n");return 1;}// 初始化新节点数据memcpy(&newnode->data, data, sizeof(struct DATATYPE));newnode->next = NULL;newnode->prev = NULL;// 定位到插入位置的前一个节点struct DouNode* tmp = dl->head;for(int i = 0; i < pos - 1; ++i){tmp = tmp->next;}// 重新链接指针newnode->next = tmp->next;  // 新节点后继指向原位置节点newnode->prev = tmp;        // 新节点前驱指向前驱节点tmp->next->prev = newnode;  // 原位置节点的前驱指向新节点tmp->next = newnode;        // 前驱节点的后继指向新节点// 链表长度增加dl->clen++;return 0;
}

6. 查找节点

c

/*** 按姓名查找节点* @param dl 链表指针* @param name 要查找的姓名* @return 找到返回节点指针,未找到返回NULL*/
struct DouNode* FindDouLinkList(struct DouLinkList* dl, char *name)
{// 空链表直接返回NULLif(IsEmptyDouLinkList(dl)){return NULL;}struct DouNode* tmp = dl->head;// 遍历链表查找匹配节点while(tmp){if(strcmp(tmp->data.name, name) == 0){return tmp;  // 找到匹配节点}tmp = tmp->next;  // 继续检查下一个节点}return NULL;  // 遍历结束未找到
}

7. 修改节点数据

c

/*** 修改指定节点的数据* @param dl 链表指针* @param name 要修改的节点姓名* @param data 新数据指针* @return 成功返回0,失败返回1*/
int ModifyDouLinkList(struct DouLinkList* dl, char *name, struct DATATYPE* data)
{// 查找目标节点struct DouNode* ret = FindDouLinkList(dl, name);if(NULL == ret){fprintf(stderr, "Node %s not found\n", name);return 1;}// 拷贝新数据到目标节点memcpy(&ret->data, data, sizeof(struct DATATYPE));return 0;
}

8. 双向链表逆序

c

/*** 反转双向链表* @param dl 链表指针* @return 成功返回0,失败返回1*/
int RevertDouLinkList(struct DouLinkList* dl)
{int len = GetSizeDouLinkList(dl);// 长度小于2无需反转if(len < 2){return 1;}// 初始化三个工作指针struct DouNode* Prev = NULL;      // 前驱指针struct DouNode* Tmp = dl->head;   // 当前指针struct DouNode* Next = Tmp->next; // 后继指针while(1){// 反转当前节点的指针Tmp->prev = Next;  // 原后继变为前驱Tmp->next = Prev;  // 原前驱变为后继// 移动指针Prev = Tmp;    // 前驱指针前进Tmp = Next;    // 当前指针前进// 检查是否到达链表末尾if(NULL == Tmp){break;}// 更新后继指针Next = Next->next;}// 更新链表头指针dl->head = Prev;return 0;
}

9. 删除指定节点

c

/*** 删除指定节点* @param dl 链表指针* @param name 要删除的节点姓名* @return 成功返回0,失败返回1*/
int DeleteDouLinkList(struct DouLinkList* dl, char* name)
{// 查找目标节点struct DouNode* tmp = FindDouLinkList(dl, name);if(NULL == tmp){fprintf(stderr, "Node %s not found\n", name);return 1;}// 处理头节点情况if(tmp == dl->head){dl->head = dl->head->next;if(dl->head){dl->head->prev = NULL;  // 新头节点的前驱置空}}// 处理尾节点情况else if(NULL == tmp->next){if(tmp->prev){tmp->prev->next = NULL;  // 前驱节点的后继置空}else{dl->head = NULL;  // 链表只有一个节点的情况}}// 处理中间节点情况else{// 更新后继节点的前驱指针if(tmp->next) {tmp->next->prev = tmp->prev;}// 更新前驱节点的后继指针if(tmp->prev) {tmp->prev->next = tmp->next;}}// 释放节点内存free(tmp);// 链表长度减少dl->clen--;return 0;
}

10. 销毁链表

c

/*** 销毁整个链表* @param dl 链表指针* @return 总是返回0*/
int DestroyDouLinkList(struct DouLinkList* dl)
{struct DouNode* tmp = dl->head;// 循环释放所有节点while(tmp){dl->head = dl->head->next;  // 头指针后移free(tmp);                  // 释放当前节点tmp = dl->head;             // 指向新的头节点}// 释放链表头结构free(dl);return 0;
}

三、Makefile工程管理

基础版本

makefile

# 简单Makefile示例
# 目标:依赖
a.out: main.c ./doulink.c# 编译命令(前面必须是tab)gcc main.c doulink.c# 清理目标
clean:rm a.out

推荐版本

makefile

# 定义编译器和编译选项
CC = gcc
CFLAGS = -Wall -g  # 开启所有警告和调试信息# 定义目标可执行文件
TARGET = app# 定义源文件
SRCS = main.c doulink.c# 默认目标规则
$(TARGET): $(SRCS)$(CC) $(CFLAGS) -o $@ $^# 清理规则
clean:rm $(TARGET).PHONY: clean  # 声明clean为伪目标

使用说明

  1. 编译:make (自动执行第一条规则)

  2. 运行:./app (或./a.out如果是基础版本)

  3. 清理:make clean (删除生成的可执行文件)

四、双向链表VS单向链表对比

特性单向链表双向链表
遍历方向仅能正向遍历可双向遍历
节点结构data + nextdata + prev + next
插入删除删除需遍历找前驱(O(n))直接操作前驱节点(O(1))
内存占用较小(少一个指针)较大(多一个指针)
适用场景简单线性数据,内存紧张需要频繁查找前驱或反向遍历

五、嵌入式开发建议

  1. 资源受限系统

    • 优先考虑单向链表节省内存

    • 静态分配节点内存池避免碎片

  2. 实时性要求高

    • 双向链表删除操作更高效

    • 可考虑使用循环双向链表

  3. 调试技巧

    bash

    gdb ./your_program
    (gdb) b DouLinkList.c:100  # 在指定行设断点
    (gdb) p *node              # 查看节点内容
    (gdb) bt                   # 查看调用栈
  4. 性能优化

    • 维护尾指针加速尾插操作

    • 使用内存池预分配节点

http://www.dtcms.com/a/267622.html

相关文章:

  • 3dmax标准材质转物理材质插件,支持VR材质和CR材质转换成功物理材质,支持多维子材质
  • vscode工具使用技巧
  • Spring AI介绍:Java开发者迈向智能应用的新利器
  • Delta、Jackknife、Bootstrap
  • n8n完全指南:从入门到精通的工作流自动化实践
  • 闲庭信步使用SV搭建图像测试平台:第三十一课——基于神经网络的手写数字识别
  • RS触发器Multisim电路仿真——硬件工程师笔记
  • 【unitrix】 4.18 类型级二进制数加法实现解析(add.rs)
  • .NET9 实现斐波那契数列(FibonacciSequence)性能测试
  • Windows内存泄漏自动化
  • 部署greenplum7.2双节点集群
  • Softhub软件下载站实战开发(十三):软件管理前端分片上传实现
  • 【部署与总结】从本地运行到公网服务器的全过程
  • Qt智能指针
  • 408第三季part2 - 计算机网络 - 计算机网络分层结构
  • Python数据分析基础04:预测性数据分析
  • 非负矩阵分解(NMF)的python应用 ,基因分析,以胰腺癌上皮亚簇为实例,NMF在癌症研究中的优势
  • 支持多方式拼接图片的软件
  • Zama 机密区块链协议Litepaper
  • RAL-2025 | 清华大学数字孪生驱动的机器人视觉导航!VR-Robo:面向视觉机器人导航与运动的现实-模拟-现实框架
  • 【ES6】Latex总结笔记生成器(网页版)
  • RocketMQ一键启动_window
  • 黑马点评系列问题之基础篇06初识redis测试连接redis失败
  • 硬件嵌入式工程师学习路线终极总结(二):Makefile用法及变量——你的项目“自动化指挥官”!
  • 深度学习5(深层神经网络 + 参数和超参数)
  • Ubuntu 20.04 编译安装FFmpeg及错误分析与解决方案
  • 数据结构:数组:插入操作(Insert)与删除操作(Delete)
  • PageRank:互联网的马尔可夫链平衡态
  • 利用已有的 PostgreSQL 和 ZooKeeper 服务,启动dolphinscheduler-standalone-server3.1.9 镜像
  • Redis基础(6):SpringDataRedis