news 2026/9/14 18:04:30

【C 数据结构】list 链式表

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【C 数据结构】list 链式表

目录

链式表的分类方式

按节点连接方式分类

按存储结构分类

按功能扩展分类

带头双向循环动态链表模拟实现


链式表的分类方式

链式表(链表)根据不同的结构和特性,可以分为以下几类:

按节点连接方式分类
  1. 单向链表
    每个节点包含数据和指向下一个节点的指针,只能单向遍历。

    • 优点:结构简单,内存占用较小。
    • 缺点:无法反向遍历,删除节点需从头查找前驱节点。
  2. 双向链表
    每个节点包含数据、指向下一个节点的指针和指向前一个节点的指针。

    • 优点:支持双向遍历,删除和插入操作更高效。
    • 缺点:每个节点多占用一个指针空间,内存开销较大。
  3. 循环链表
    尾节点指向头节点,形成闭环。可分为单向循环链表和双向循环链表。

    • 优点:适合环形数据处理(如轮询调度)。
    • 缺点:需注意循环终止条件,避免无限循环。
按存储结构分类
  1. 静态链表
    使用数组模拟链表,通过数组下标代替指针。

    • 优点:无需动态内存分配,适合嵌入式系统等受限环境。
    • 缺点:容量固定,灵活性差。
  2. 动态链表
    节点通过动态内存分配(如malloc)创建,内存可动态扩展。

    • 优点:灵活性强,内存利用率高。
    • 缺点:需手动管理内存,易产生内存泄漏。
按功能扩展分类
  1. 带头节点的链表
    在链表头部添加一个不存储数据的节点(头节点),简化插入/删除操作。

    • 优点:统一操作逻辑,避免对头节点的特殊处理。
  2. 不带头节点的链表
    直接以第一个数据节点作为头节点。

    • 优点:节省一个节点的空间。
    • 缺点:需单独处理头节点操作。

而今天我要模拟实现的是带头双向循环动态链表。

带头双向循环动态链表模拟实现

链表的结构结构体:

typedef int DCLDataType; typedef struct DCListNode { DCLDataType data; // 存储数据元素的值 struct DCListNode* prev; // 存放前驱结点的指针 struct DCListNode* next; // 存放后继结点的指针 }DCListNode;

链表的初始化:

// 链表初始化 DCListNode* DCListInit() { DCListNode* head = (DCListNode*)malloc(sizeof(DCListNode)); if (head == NULL) { perror("malloc"); exit(1); } head->next = head->prev = head; return head; }

注意点:在该链式表内,因为要实现的循环链表,所以需要在初始化的时候需要进行一个头尾相连向头指针。

链式表的销毁:

// 销毁链表 void DCListDestroy(DCListNode* L) { if (L == NULL) { return; } DCListNode* dest ,*head = L; L = L->next; while (L != head) { dest = L; L = L->next; free(dest); dest = NULL; } free(head); head = NULL; return; }

因为是一个循环链表需要进行特殊的处理,先从链式表头指针往后的第一个节点进行销毁,遇到头指针停止循环销毁,对头指针单独销毁。

获取链式表的为序i节点:

// 获取链表的位序i的结点 DCListNode* DCListGetElem(DCListNode* L, int i) { if (i <= 0 || L == NULL)//错误操作,指针为空 { return NULL; } DCListNode* node = L->next; int I = 1; for (I = 1; I < i && L != node; I++) { node = node->next; } if (L == node)//超出链式表的最大访问,遇到 return NULL; return node; }

1.判断头指针是否为空或者i小于等于0

2.声明节点跳过头指针,开始查找。

3.超出最大链式的极限返回NULL ,没有就返回node 节点

在pos位置后插入值为x的结点 和 头插 尾插

void DCListInsert(DCListNode* pos, DCLDataType x) { DCListNode* news = (DCListNode*)malloc(sizeof(DCListNode)); if (news == NULL) { perror("malloc"); exit(1); } news->next = pos->next; news->prev = pos; pos->next->prev = news; pos->next = news; news->data = x; return; } // 头插 void DCListPushFront(DCListNode* L, DCLDataType x) { assert(L != NULL); DCListInsert(L, x); return; } // 尾插 void DCListPushBack(DCListNode* L, DCLDataType x) { assert(L); DCListInsert(L->prev, x); return; }

节点插入都要先申请一个节点,这个节点为了方便解释设为C点,要插入某个节点前面我们设A点下一个节点为B,节点的各个节点一定要遵循,A->B的节点最后改不然会导致找不到B点,或者可以用其他方式来实现,总之要保证B点有能够存取的地址的方式。

我的步骤就是先把申请来的新地址,先把新节点C的前后指针存入B和A,把B的后指针存入C,再把A的前指针存入C,再把C的data存入x。

头插和尾插都只需要把对应的地址传参即可,如果要实现对应位置插入就用刚才的DCListGetElem函数利用起来,就可以实现对应位置插入。

//对应位置插入 void DCListPushNode(DCListNode* L, DCLDataType x,int i) { DCListNode* node = DCListGetElem(L, i); assert(node); DCListInsert(node, x); return; }

删除pos位置的结点头删和尾删

// 删除pos位置的结点 void DCListDelete(DCListNode* pos) { assert(pos != NULL); if (pos == pos->next) { return; } pos->next->prev = pos->prev; pos->prev->next = pos->next; free(pos); pos = NULL; return; } // 头删 void DCListPopFront(DCListNode* L) { assert(L != NULL); DCListDelete(L->next); return; } // 尾删 void DCListPopBack(DCListNode* L) { assert(L != NULL); DCListDelete(&L->prev); return; }

对应位置删除只关注前后指针前后指针从新链接就可以。当前位置的前指针节点的后指针 改成 当前位置的指针的后一个节点;当前位置的后指针节点的前指针 改成 当前位置的指针的前一个节点;这样及保证前后指针完美链接,也可以不用声明多的变量,只用pos 进行空间释放。

注意点:

头删时把头指针的往后一个节点进行释放,而不是头指针释放,所以传参传L->next;尾删就是把头指针的L->prev 进行释放。原则就是保证删除的不是头指针,只有销毁才需要把头指针进行销毁。

打印输出:

// 打印链表中的元素 void DCListPrint(DCListNode* L) { assert(L!=NULL); DCListNode* node = L->next; while (L!=node) { printf("%d -> ",node->data); node = node->next; } printf("\n"); return; }

模拟实现

void List_test() { DCListNode* head = DCListInit(); DCListPushBack(head, 1); DCListPushBack(head, 2); DCListPushBack(head, 3); DCListPushBack(head, 4); DCListPushBack(head, 5); DCListPushBack(head, 6); DCListPrint(head); DCListPushFront(head, 2); DCListPushFront(head, 3); DCListPushFront(head, 4); DCListPushFront(head, 5); DCListPushFront(head, 6); DCListPrint(head); DCListPopFront(head); DCListPrint(head); DCListPopFront(head); DCListPrint(head); DCListPopFront(head); DCListPrint(head); DCListDestroy(head); return; } int main() { List_test(); return 0; }

感谢观看!

悠仁さん

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/14 18:01:05

COMSOL相场模拟入门:从金属枝晶到雪花形貌的复现与调优

这几天在COMSOL里做相场模拟&#xff0c;目标很具体&#xff1a;复现金属枝晶生长&#xff0c;再把对称性改成六次&#xff0c;看看能不能长出接近雪花的形貌。这个题目听起来唬人&#xff0c;实际跑通之后你会发现&#xff0c;相场模拟最麻烦的往往不是方程本身&#xff0c;而…

作者头像 李华
网站建设 2026/9/14 18:00:20

LNMP架构详解:Nginx与PHP-FPM动静分离配置实战

作为Nginx系列文章的第三篇&#xff0c;这篇我打算把LNMP从零到能跑完整拆一遍。前两篇聊过Nginx的基础配置和虚拟主机玩法&#xff0c;但很多朋友在真正部署PHP项目时还是卡壳&#xff1a;要么是PHP-FPM与Nginx对接不上&#xff0c;返回502&#xff1b;要么是静态图片和JS请求…

作者头像 李华
网站建设 2026/9/14 17:59:00

PHP多进程信号处理与优雅关闭实践

1. 多进程PHP环境中的信号处理挑战在构建高并发的PHP服务时&#xff0c;多进程架构是常见的解决方案。当主进程fork出多个子进程后&#xff0c;一个经常被忽视但至关重要的问题是&#xff1a;如何精确控制不同子进程对系统信号的响应行为&#xff1f;特别是在需要优雅关闭服务时…

作者头像 李华
网站建设 2026/9/14 17:56:57

【C 数据结构】 树 二叉树 堆 (链式二叉树模拟实现篇)

目录 链式二叉树的模拟实现 二叉树的数据结构 基本功能实现 树初始化和销毁 遍历方式 遍历方式的概念解释&#xff1a; 遍历方式的代码模拟实现&#xff1a; 层序遍历 计算树的节点个数 二叉树叶子结点个数 二叉树k层结点个数 二叉数的最大深度 查找元素 判断是否…

作者头像 李华