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

数据结构3-双向链表、循环链表

摘要:本文详细介绍了三种链表结构的实现:双向链表、循环链表和内核链表。主要内容包括:1. 双向链表的节点定义、创建、头插/尾插法、遍历、查找、修改、删除和销毁操作的C语言实现;2. 循环链表的特殊处理(首尾相连)及各操作的实现差异;3. 内核链表的特点(节点嵌入数据结构)及其与传统链表的区别。通过代码示例和操作步骤说明,系统讲解了每种链表的基本操作原理和实现要点,包括指针处理、内存管理等关键技术细节。

一、双向链表

(一)链表节点的定义

1、定义的代码实现:

/* 节点存放数据的类型 */
typedef int datatype;
/* 节点类型 */
typedef struct node
{
datatype data;                      
 //存放数据空间
        struct node *ppre;                 //存放前一个节点地址
        struct node *pnext;               //存放下一个节点地址
}linknode;

(二)空白链表的创建

1、步骤:

  1. 申请节点空间
  2. 对pnext和ppre赋值为NULL
  3. 返回空白节点地址即可

图例

2、代码实现:

/* 创建一个空链表 */

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、步骤:

  1. 申请节点
  2. 存放数据
  3. pnext赋值为phead->pnext
  4. ppre赋值为phead的地址
  5. phead->pnext赋值为新申请节点地址
  6. 如果有后一个节点,需要让后一个节点的ppre指向该节点

2、代码实现:

/* 链表头插法 */
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;

}

3、注意事项:

        与单向链表思想上的不同是:要判断ptmpnode->pnext;后面还有没有节点。

(四)链表尾插法

1、步骤:

  1. 申请节点
  2. 将节点的pnext赋值为NULL
  3. 找到链表最后一个节点
  4. 将节点的ppre赋值为最后一个节点地址
  5. 将最后一个节点的pnext赋值为新申请节

2、代码实现:

/* 链表尾插法 */
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、步骤:与单向链表类似

2、代码实现:

/* 链表的遍历 */
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、步骤:与单向链表类似

2、代码实现:

/* 链表的查询 */
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、步骤:与单向链表的类似

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;
}

(八)链表的删除

1、步骤:

  1. 找到要删除的节点
  2. 让删除节点的前一个节点的pnext指向要删除节点的后一个节点
  3. 让伤处节点的后一个节点的ppre指向要删除节点的前一个节点
  4. 将要删除的节点释掉

        <细节版>

  1. 定义两个指针ptmpnode、pfreenode,ptmpnode用来遍历寻找要删除的数据
  2. 将ptmpnode->pnext->ppre = ptmpnode->ppre;
  3. 将ptmpnode->ppre->pnext = ptmpnode->pnext;
  4. 令pfreenode = ptmpnode,ptmpnode往前进一步
  5. 释放pfreenode空间

图例

2、代码实现:

/* 链表的删除 */

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

}
else
{
ptmpnode = ptmpnode->pnext;
}
}
return 0;
}

3、注意事项:

1)设置两个指针的作用:

① 删除链表元素完要释放空间,所以需要定于一个指针(pfreenode后续称为A)指向此时的位置(ptmpnode后续称为B)。

② 若没有A,直接释放掉B之后我们就找不到被删除的下一个节点位置。

③ 所以需要A指向B此时的位置,然后让B往前再走一个节点到达下一个节点。然后释放掉A指向节点的空间。

2)解析释放空间的操作:

       free(pfreenode);   释放的是该指针指向的空间,而不是指针本身。该操作之后此指针就为悬垂指针。pfreenode仍然可以重新指向别的空间。所以为了误用,规范使用最好释放后立即置空。

(九)链表的销毁

1、步骤:与单向链表类似

2、代码实现:

/* 链表的销毁 */
int destroy_linklist(linknode **pphead)
{
linknode *ptmpnode = NULL;
linknode *pfreenode = NULL;
ptmpnode = *pphead;
pfreenode = ptmpnode;
while (ptmpnode != NULL)
{
ptmpnode = ptmpnode->pnext;
free(pfreenode);
pfreenode = ptmpnode;
}
*pphead = NULL;
return 0;
}

二、循环链表

(一)节点定义

1、步骤:与双向链表类似

2、代码实现:

/* 节点存放数据的类型 */
typedef int datatype;


/* 节点类型 */
typedef struct node
{
datatype data;                 //存放数据空间
struct node *ppre;           //存放前一个节点地址
struct node *pnext;         //存放下一个节点地址
}linknode

(二)链表创建

1、步骤:

  1. 申请节点
  2. pnext和ppre都指向自己

2、代码实现:

/* 创建空链表 */
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;
}

(三)链表头插法

1、步骤:

  1. 申请节点空间
  2. 存放数据
  3. 将pnext指向空白节点的pnext
  4. 将ppre指向空白节点
  5. 将空白节点的pnext指向新申请节点
  6. 将后续节点的ppre指向新申请节点

2、代码实现:

/* 头插法 */
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;
}

(四)链表尾插法

1、步骤:

  1. 申请节点空间
  2. 将数据存放到节点中
  3. 将pnext赋值为空白节点
  4. 将ppre赋值为之前的最后一个节点地址
  5. 将之前最后一个节点的pnext指向新申请节点
  6. 将空白节点的ppre指向新申请节点

2、代码实现:

/* 尾插法 */
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->pnext->ppre = ptmpnode;
ptmpnode->ppre->pnext = ptmpnode;
return 0;
}

(六)链表的遍历

1、步骤:与双向链表类似,循环链表最终循环条件由 != NULL修改为 != phea

2、代码实现:

/* 链表节点遍历 */
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;
}

(七)链表的查找

1、步骤:与双向链表类似

2、代码实现:

/* 链表的查找 */
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;
}

(八)链表的修改

1、步骤:与双向链表类似

2、代码实现:

/* 链表的修改 */
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;
}

(九)链表的删除

1、步骤:与双向链表类似

2、代码实现:

/* 链表的删除 */
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);

                        pfreenode = NULL;
                }
else
{
ptmpnode = ptmpnode->pnext;
}

        }
        return 0;
}

(十)链表的销毁

1、步骤:与双向链表类似

1、代码实现:

/* 链表的销毁 */
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;
}

三、内核链表

1、概念:

  1. Linux内核中所使用到的一种链表结构
  2. 使用同一种结构可以存储不同类型的链表
  3. 普通链表:链表节点中包含数据
  4. 内核链表:数据中包含链表节点

2、结构:

3、 内核链表、单向链表、双向链表和循环链表 区别与联系

  • 单向链表
    • 由一系列节点组成,每个节点包含数据域和一个指向下一个节点的指针。
    • 节点之间的连接是单向的,只能从链表头开始,沿着指针依次访问后续节点,无法反向遍历。
  • 双向链表
    • 同样由节点构成,每个节点除了包含数据域和指向下一个节点的指针外,还包含一个指向前一个节点的指针。
    • 可以从链表头向链表尾遍历,也可以从链表尾向链表头遍历,增加了遍历的灵活性。
  • 循环链表
    • 可以是单向循环链表或双向循环链表。在单向循环链表中,最后一个节点的指针指向链表头节点;在双向循环链表中,头节点的前驱指针指向尾节点,尾节点的后继指针指向头节点。
    • 形成一个封闭的环,使得遍历可以无限循环下去。
  • 内核链表
    • 是 Linux 内核中使用的一种链表实现方式,它将链表节点嵌入到其他数据结构中。节点只包含两个指针(前驱和后继),不包含数据域。
    • 通过这些指针将不同的数据结构连接成链表,使得链表操作与具体的数据结构解耦。

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

相关文章:

  • 14.Home-新鲜好物和人气推荐实现
  • 大模型|极简说清“数据并行”
  • 06-队列
  • Crawl4AI:开源的AI友好型网页爬虫与数据抓取工具
  • 电子秤利用Websocket做为Client向MES系统推送数据
  • 软件测试——接口自动化
  • STM32内部读写FLASH
  • 90、【OS】【Nuttx】【启动】栈溢出保护:配置项解析
  • Swift 实战:用队列巧解 LeetCode 346 数据流中的移动平均数
  • 服务器端口连通性的测试工具和方法
  • XXL-JOB调度中心、执行器、Job之间的关系
  • MQTT:消息详解
  • 备忘录记事本 任务清单 html
  • 基于铁头山羊STM32的平衡车电机转速开环闭环matlab仿真
  • 线性规划最优解
  • 饿了么招java开发咯
  • tarjan找SCC,缩点建DAG,找唯一源头节点
  • 强光干扰下误报率↓82%!陌讯多模态融合算法在火焰识别的落地优化
  • 不可变集合
  • nflsoi 7.31 题解
  • 信息化项目验收,项目成功的最后确认
  • Redis中的sdshdr的len和alloc那块的知识点详解
  • 【经验记录贴】在windows系统中启动服务
  • CMU-15445(7)——PROJECT#2-BPlusTree-Task#2Task#3
  • BGA 芯片贴片加工关键注意事项
  • Fiddler 中文版实战指南,如何构建高效的 API 调试工作流?
  • 第13届蓝桥杯Scratch_选拔赛_真题2021年11月27日
  • 老旧体育场馆照明翻新:预算有限?3 步实现 “低成本升级”
  • 在具身智能火热加持下,看 2025 年机器人学术年会中的热点主题。PNP机器人展示力控、灵巧手捕捉等案例。
  • 利用链上数据进行数字资产量化因子发现