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

数据结构(2) —— 双向链表、循环链表与内核链表

1 双向链表

操作功能函数定义核心步骤注意事项
节点定义typedef int datatype; typedef struct node { datatype data; struct node *ppre; struct node *pnext; } linknode;定义数据类型datatype(int)和节点类型linknode,包含数据域data、前驱指针ppre和后继指针pnext指针域需正确指向前后节点
创建空双向链表linknode *create_empty_linklist(void)1. 申请头节点空间2. 将头节点的pprepnext置为NULL3. 返回头节点地址内存分配失败时需处理错误
头插法int insert_head_linklist(linknode *phead, datatype tmpdata)1. 申请新节点并存储数据2. 新节点pnext指向头节点pnextppre指向头节点3. 头节点pnext指向新节点4. 若新节点后有节点,更新其后继节点的ppre处理新节点为第一个或非第一个节点的情况
尾插法int insert_tail_linklist(linknode *phead, datatype tmpdata)1. 申请新节点并存储数据2. 找到最后一个节点3. 最后节点pnext指向新节点,新节点ppre指向最后节点4. 新节点pnext置为NULL遍历找到尾节点时需判断pnext是否为NULL
遍历链表int show_linklist(linknode *phead)1. 从首元节点(头节点pnext)开始2. 依次打印节点数据,直到节点为NULL避免从头节点开始遍历(头节点无数据)
查找节点linknode *find_linklist(linknode *phead, datatype tmpdata)1. 从首元节点开始遍历2. 找到数据匹配节点返回该节点,否则返回NULL需完整遍历链表直至pnextNULL
修改节点int update_linklist(linknode *phead, datatype olddata, datatype newdata)1. 遍历链表查找旧数据节点2. 找到后修改为新数据3. 返回 0(成功)或 - 1(失败)可修改所有匹配的节点
删除节点int delete_linklist(linknode *phead, datatype tmpdata)1. 找到目标节点2. 前一节点pnext指向后一节点,后一节点ppre指向前一节点3. 释放目标节点内存需处理删除节点为尾节点的情况(无后一节点)
销毁链表int destroy_linklist(linknode **pphead)1. 从头节点开始依次释放每个节点2. 最后将头节点指针置为NULL使用二级指针确保头节点在外部被正确置空,避免野指针

双向链表通过双指针实现前后节点访问,操作时需特别注意指针关系的维护,尤其是插入和删除操作

1.1 节点定义

  • 定义节点结构体,包含数据域 data、指向前驱节点的指针 *ppre 和指向后继节点的指针 *pnext
  • 用 typedef 定义数据类型 datatype(这里为 int)和节点类型 linknode

typedef int datatype;
typedef struct node {
datatype data;           // 存放数据空间
struct node *ppre;       // 指向前驱节点的指针
struct node *pnext;      // 指向后继节点的指针
} linknode;

1.2 创建空双向链表

  • 申请节点空间,将新节点的 ppre 和 pnext 都置为 NULL,返回该空节点地址。

linknode *create_empty_linklist(void) {
linknode *ptmpnode = NULL;
ptmpnode = malloc(sizeof(linknode));
if (NULL == ptmpnode) {
perror("fail to malloc");
return NULL;
}
ptmpnode->ppre = NULL;
ptmpnode->pnext = NULL;
return ptmpnode;
}

1.3 链表的头插法

  • 申请新节点,存储数据,将新节点的 pnext 赋值为头节点的 pnextppre 赋值为头节点地址。
  • 再将头节点的 pnext 赋值为新节点地址,若新节点后有节点,需让后一节点的 ppre 指向新节点。

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;
ptmpnode->ppre = phead;
phead->pnext = ptmpnode;
if (ptmpnode->pnext != NULL) {
ptmpnode->pnext->ppre = ptmpnode;
}
return 0;
}

1.4 链表尾插

  • 申请新节点,存储数据,将新节点的 pnext 置为 NULL,找到链表最后一个节点,将其 pnext 指向新节点,新节点的 ppre 指向最后一个节点。

int insert_tail_linklist(linknode *phead, datatype tmpdata) {
linknode *ptmpnode = NULL;
linknode *plastnode = NULL;
ptmpnode = malloc(sizeof(linknode));
if (NULL == ptmpnode) {
perror("fail to malloc");
return -1;
}
plastnode = phead;
while (plastnode->pnext != NULL) {
plastnode = plastnode->pnext;
}
ptmpnode->data = tmpdata;
ptmpnode->pnext = NULL;
ptmpnode->ppre = plastnode;
plastnode->pnext = ptmpnode;
return 0;
}

1.5 链表的遍历

  • 从首元节点(头节点的 pnext 指向的节点)开始,依次打印节点数据,直到节点为 NULL

int show_linklist(linknode *phead) {
linknode *ptmpnode = NULL;
ptmpnode = phead->pnext;
while (ptmpnode != NULL) {
printf("%d ", ptmpnode->data);
ptmpnode = ptmpnode->pnext;
}
printf("\n");
return 0;
}

1.6 链表的查找

  • 从首元节点开始遍历,若找到数据等于目标数据的节点,返回该节点;否则返回 NULL

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

1.7 链表的修改

  • 遍历链表,找到数据为旧数据的节点,将其数据修改为新数据。

int update_linklist(linknode *phead, datatype olddata, datatype newdata)

{
linknode *ptmpnode = NULL;
int found = 0;  // 标记是否找到要修改的节点
ptmpnode = phead->pnext;
while (ptmpnode != NULL)

        {
if (ptmpnode->data == olddata)

                {
ptmpnode->data = newdata;
found = 1;
}
ptmpnode = ptmpnode->pnext;
}
return found ? 0 : -1;  // 找到并修改返回0,未找到返回-1
}

1.8 链表的删除

  • 找到要删除的节点,让该节点前一节点的 pnext 指向后一节点,后一节点的 ppre 指向前一节点,释放该节点。

int delete_linklist(linknode *phead, datatype tmpdata)

{
linknode *ptmpnode = NULL;
linknode *pfreernode = NULL;
ptmpnode = phead->pnext;

while (ptmpnode != NULL)

    {
if (ptmpnode->data == tmpdata)

        {
pfreernode = ptmpnode;
// 前一个节点指向后一个节点
ptmpnode->ppre->pnext = ptmpnode->pnext;
// 如果有后一个节点,让它的前驱指向当前节点的前驱
if (ptmpnode->pnext != NULL)

            {
ptmpnode->pnext->ppre = ptmpnode->ppre;
}
// 保存下一个节点地址
ptmpnode = ptmpnode->pnext;
// 释放当前节点
free(pfreernode);
}

        else

        {
// 不匹配则移动到下一个节点
ptmpnode = ptmpnode->pnext;
}
}
return 0;
}

1.9 链表的销毁

  • 从头节点开始,依次释放每个节点,最后将头节点置为 NULL

int destroy_linklist(linknode **pphead) {
linknode *ptmpnode = NULL;
linknode *pfreernode = NULL;
ptmpnode = *pphead;
pfreernode = ptmpnode;
while (ptmpnode != NULL) {
ptmpnode = ptmpnode->pnext;
free(pfreernode);
pfreernode = ptmpnode;
}
*pphead = NULL;
return 0;
}

2 循环链表

操作功能函数定义核心步骤注意事项
创建空链表linknode *create_empty_linklist(void)1. 申请头节点内存空间2. 头节点的pnextppre均指向自身1. 需检查malloc是否成功2. 初始循环结构的正确建立
头插法插入int insert_head_linklist(linknode *phead, datatype tmpdata)1. 申请新节点并赋值2. 新节点pnext指向头节点的pnext3. 新节点ppre指向头节点4. 调整原相邻节点指针指向新节点1. 确保头节点不为空2. 指针修改顺序避免断裂3. 检查内存分配结果
尾插法插入int insert_tail_linklist(linknode *phead, datatype tmpdata)1. 申请新节点并赋值2. 新节点pnext指向头节点3. 新节点ppre指向头节点的ppre4. 调整原尾节点和头节点指针1. 确保头节点不为空2. 正确找到尾节点(头节点的ppre)3. 检查内存分配结果
遍历链表int show_linklist(linknode *phead)1. 从phead->pnext开始遍历2. 循环访问直到节点等于头节点3. 输出每个节点的数据1. 处理空链表情况2. 循环终止条件正确(ptmpnode != phead
按值查找linknode *find_linklist(linknode *phead, datatype tmpdata)1. 从首节点开始遍历2. 比较节点数据与目标值3. 找到则返回节点,否则返回NULL1. 空链表直接返回NULL2. 只返回第一个匹配节点
按值修改int update_linklist(linknode *phead, datatype olddata, datatype newdata)1. 遍历所有节点2. 找到数据为olddata的节点3. 将其数据更新为newdata1. 会修改所有匹配的节点2. 需处理空链表情况
按值删除int delete_linklist(linknode *phead, datatype tmpdata)1. 遍历查找目标节点2. 调整前后节点指针跳过目标节点3. 释放目标节点内存1. 释放节点前先移动遍历指针2. 避免空指针操作3. 会删除所有匹配的节点
销毁链表int destroy_linklist(linknode **pphead)1. 遍历释放所有有效节点2. 释放头节点3. 将头指针置为NULL1. 使用二级指针确保头指针能被置空2. 释放顺序避免访问已释放内存3. 检查传入指针的有效性

2.1 特点

  • 头结点的ppre指向链表最后一个节点。
  • 最后节点的pnext指向第一个节点,形成循环结构。

2.2 节点定义

typedef int datatype; // 定义数据类型
typedef struct node {
datatype data;       // 存储数据
struct node *ppre;   // 指向前一个节点
struct node *pnext;  // 指向后一个节点
} linknode;

2.3 链表创建

  • 申请节点空间。
  • 新节点的pnextppre都指向自己,形成初始循环。

linknode *create_empty_linklist(void) {
linknode *ptmpnode = NULL;
ptmpnode = malloc(sizeof(linknode));
if (NULL == ptmpnode) {
perror("fail to malloc");
return NULL;
}
ptmpnode->pnext = ptmpnode->ppre = ptmpnode;
return ptmpnode;
}

2.4 链表头插法

  • 申请节点空间,存储数据。
  • 将新节点的pnext指向头节点的pnext
  • 将新节点的ppre指向头节点。
  • 更新头节点的pnext和原头节点下一个节点的ppre,指向新节点。

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;
ptmpnode->ppre = phead;
ptmpnode->pnext->ppre = ptmpnode;
ptmpnode->ppre->pnext = ptmpnode;
return 0;
}

2.5 链表尾插法

  • 申请节点空间,存储数据。
  • 新节点的pnext指向头节点。
  • 新节点的ppre指向头节点的ppre(即原最后一个节点)。
  • 更新原最后一个节点的pnext和头节点的ppre,指向新节点。

int insert_tail_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;
ptmpnode->ppre = phead->ppre;
ptmpnode->ppre->pnext = ptmpnode;
ptmpnode->pnext->ppre = ptmpnode;
return 0;
}

2.6 链表的遍历

  • 循环条件为当前节点不等于头节点,依次访问每个节点数据。

int show_linklist(linknode *phead) {
linknode *ptmpnode = NULL;
ptmpnode = phead->pnext;
while (ptmpnode != phead) {
printf("%d ", ptmpnode->data);
ptmpnode = ptmpnode->pnext;
}
printf("\n");
return 0;
}

2.7 链表查找

  • 从首节点开始遍历,若找到数据匹配的节点则返回该节点,否则返回NULL

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

2.8 链表的修改

  • 遍历链表,找到数据为olddata的节点,将其数据修改为newdata

int update_linklist(linknode *phead, datatype olddata, datatype newdata) {
linknode *ptmpnode = NULL;
ptmpnode = phead->pnext;
while (ptmpnode != phead) {
if (ptmpnode->data == olddata) {
ptmpnode->data = newdata;
}
ptmpnode = ptmpnode->pnext;
}
return 0;
}

2.9 链表的删除

  • 遍历链表,找到数据为tmpdata的节点,调整其前后节点的指针,断开该节点连接,释放节点空间。

int delete_linklist(linknode *phead, datatype tmpdata) {
linknode *ptmpnode = NULL;
linknode *pfreenode = NULL;
ptmpnode = phead->pnext;
while (ptmpnode != phead) {
if (ptmpnode->data == tmpdata) {
ptmpnode->ppre->pnext = ptmpnode->pnext;
ptmpnode->pnext->ppre = ptmpnode->ppre;
pfreenode = ptmpnode;
ptmpnode = ptmpnode->pnext;
free(pfreenode);
} else {
ptmpnode = ptmpnode->pnext;
}
}
return 0;
}

2.10 链表的销毁

  • 从首节点开始,依次释放每个节点空间,最后释放头节点并将头节点指针置NULL

int destroy_linklist(linknode **pphead) {
linknode *ptmpnode = NULL;
linknode *pfreenode = NULL;
ptmpnode = (*pphead)->pnext;
pfreenode = ptmpnode;
while (ptmpnode != *pphead) {
ptmpnode = ptmpnode->pnext;
free(pfreenode);
pfreenode = ptmpnode;
}
free(*pphead);
*pphead = NULL;
return 0;
}

3 内核链表

类别内容
概念是 Linux 内核中所使用的一种链表结构,使用同一种结构可以保存不同类型的表,分为普通链表(链表节点中包含数据)和内核链表(数据中包含链表节点)
结构描述 - 创建创建空白节点
结构描述 - 插入申请节点,并按照循环链表插入方式操作(插入的链表节点首地址即数据节点首地址)
结构描述 - 访问节点通过遍历找到每个节点的首地址,强制类型转换即可获得对应数据空间首地址
使用内核链表 - 基础结构与初始化- 定义 struct list_head 结构体,包含 next 和 prev 指针;- 用 INIT_LIST_HEAD 宏初始化空白头结点,使头结点的 next 和 prev 指针指向自身
使用内核链表 - 节点添加操作list_add:将新节点添加到指定头结点之后;- list_add_tail:将新节点添加到指定头结点之前(尾部)
使用内核链表 - 按序插入节点list_add_order 函数根据比较函数的返回值,将新节点按顺序插入链表
使用内核链表 - 节点删除操作list_del:从链表中删除指定节点,并对节点指针做特殊赋值;- list_del_init:删除节点后,还会将节点重新初始化为头结点形式
使用内核链表 - 节点移动操作list_move:将节点移动到另一个链表的头部;- list_move_tail:将节点移动到另一个链表的尾部
使用内核链表 - 链表状态判断list_empty:判断链表是否为空,若头结点的 next 指针指向自身则为空;- list_is_singular:判断链表是否只有一个节点,即非空且头结点的 next 和 prev 指针指向同一节点
使用内核链表 - 链表拼接操作__list_splice:将一个链表的所有元素移到另一个链表的前面;- list_splice_init:完成拼接后初始化原链表的头结点;- __list_append:将一个链表的所有元素添加到另一个链表的后面;- list_append_init:添加后初始化原链表头结点
使用内核链表 - 节点替换与链表旋转list_replace:用新节点替换旧节点;- list_replace_init:替换后初始化旧节点;- list_rotate_left:实现链表左旋,将第一个节点移到尾部
使用内核链表 - 数据结构查找通过 offsetof 和 container_of 宏,根据链表节点找到对应数据结构的首地址,还有 list_first_entrylist_last_entry 等宏用于查找特定位置的数据元素地址
使用内核链表 - 链表遍历提供多种遍历宏,如 list_for_each_entry 正向遍历所有数据元素,list_for_each_entry_safe 支持在遍历中修改指针,还有反向遍历及带安全判断的遍历宏
使用内核链表 - 节点空判断获取list_next_entry_or_null 和 list_prev_entry_or_null 宏用于获取下一个或上一个数据元素首地址(无则返回 NULL
http://www.dtcms.com/a/393912.html

相关文章:

  • 告别传统打版:用CLO 3D联动Substance,打造超写实数字服装
  • Linux | i.MX6ULL Sqlite3 移植和使用(第二十三章)
  • SpringBoot整合Smart Doc
  • 部署dataxweb
  • C#练习题——双向链表的创建,添加和删除
  • 大厂思维与“小快轻准”产品的矛盾
  • C++二进制转八进制
  • STL容器 --- 模拟实现 list
  • Java LTS版本进化秀:从8到21的欢乐升级之旅
  • yolo转tensorrt nano
  • paimon实时数据湖教程-分桶详解
  • kafka集群部署
  • Windows系统安装OpenSSL库最新版方法
  • 因果推断:关于工具变量的案例分析
  • 字节面试题:激活函数选择对模型梯度传播的影响
  • 5.Spring AI Alibaba
  • 如何优化Java并发编程以提高性能?
  • 【重量上下限报警灯红黄绿】2022-12-13
  • Node.js后端学习笔记:Express+MySQL
  • Ubuntu24.04 安装 禅道
  • StandardScaler,MinMaxScaler 学习
  • vscode+ssh连接server
  • 一文快速入门 HTTP 和 WebSocket 概念
  • Vue.js 项目创建指南
  • 核心策略、高级技巧、细节处理和心理
  • 算法优化的艺术:深入理解 Pow(x, n) 及其背后的思考
  • Projection Approximation Subspace Tracking PAST 算法
  • 容器化简单的 Java 应用程序
  • 【实证分析】上市公司并购数据dofile数据集(2005-2024年)
  • OceanBase备租户创建(三):通过带日志的物理备份恢复