数据结构之链表(单向链表与双向链表)

一,链表描述

链表是一种常见的重要的数据结构,是动态地进行存储分配的一种结构。常用于需存储的数据的数目无法事先确定。

1.链表的一般结构

链表的组成:
头指针:存放一个地址,该地址指向一个元素
结点:用户需要的实际数据和链接节点的指针

2.结点结构体类型的定义

链表结点结构的 一般形式:

struct  结构体名{  数据成员表;struct  结构体名  *指针变量名;};

3.结点的动态分配

形式是:malloc(存储区字节数)
该函数返回存储区的首地址。

释放存储区用如下函数: free(p); 它表示释放由p指向的存储空间。

用结构体建立链表:

struct student{  int num;float score;struct student *next ;};

其中成员num和score用来存放结点中的有用数据(用户需要用到的数据),next是指针类型的成员,它指向struct student类型数据(这就是next所在的结构体类型)

二,链表的设计与实现

1.单向链表

1.1 单向链表的组成:

        1)头指针
2)结点
数据域   (保存实际数据)
指针域   (保存下一个结点地址)

1.2  单向链表的设计实现:

1)定义结点类型
typedef int data_t;
typedef strcut  node
{data_t  data;strcut node *next;
}node_t;
2)单向链表功能算法实现

      (1) 链表创建
int slist_create(node_t**,data_t);
(2)  数据添加
(2.1)  头插
int slist_addhead(node_t**,data_t)
(2.2)  尾插
int slist_addtail(node_t**,data_t)
(2.3)  中间插入
int slist_insert(node_t**,data_t pos,data_t new);
(3)  数据删除
int  slist_delete(node_t**,data_t);
(4)  数据查询
node_t* slist_query(node_t*,data_t); 
(5)  数据更新
int  slist_update(node_t*,data_t old,data_t new);
(6)  数据遍历
void slist_showall(node_t*);
(7)  链表回收
void slist_destroy(node_t**);

1.3 单向链表示例程序

该示例程序涉及到链表创建、数据添加(头插、尾插、中间插入)、数据删除、数据查询、数据更新、数据遍历、链表回收。

slist.c

#include <stdio.h>
#include <stdlib.h>typedef int data_t;typedef struct node
{data_t data;struct node *next;
} node_t;/* 1. 链表创建(创建一个空头指针,返回成功与否) */
int slist_create(node_t **head, data_t value)
{*head = (node_t *)malloc(sizeof(node_t));if (*head == NULL){perror("malloc");return -1;}(*head)->data = value;(*head)->next = NULL;return 0;
}/*头插法 */
int slist_addhead(node_t **head, data_t value)
{node_t *new = (node_t *)malloc(sizeof(node_t));if (!new){perror("malloc");return -1;}new->data = value;new->next = *head;*head = new;return 0;
}/*尾插法 */
int slist_addtail(node_t **head, data_t value)
{node_t *new = (node_t *)malloc(sizeof(node_t));if (!new){perror("malloc");return -1;}new->data = value;new->next = NULL;if (*head == NULL){*head = new;}else{node_t *p = *head;while (p->next != NULL)p = p->next;p->next = new;}return 0;
}/*中间插入:在指定数据 pos 后插入 new */
int slist_insert(node_t **head, data_t pos, data_t newval)
{node_t *p = *head;while (p && p->data != pos){p = p->next;}if (!p){printf("未找到插入位置 %d\n", pos);return -1;}node_t *new = (node_t *)malloc(sizeof(node_t));if (!new){perror("malloc");return -1;}new->data = newval;new->next = p->next;p->next = new;return 0;
}/* 3. 删除结点(按值删除) */
int slist_delete(node_t **head, data_t value)
{node_t *p = *head, *q = NULL;while (p && p->data != value){q = p;p = p->next;}if (!p){printf("未找到要删除的值 %d\n", value);return -1;}if (q == NULL)*head = p->next; // 删除头结点elseq->next = p->next;free(p);return 0;
}/* 4. 查询(返回结点指针) */
node_t *slist_query(node_t *head, data_t value)
{while (head){if (head->data == value)return head;head = head->next;}return NULL;
}/* 5. 更新 */
int slist_update(node_t *head, data_t old, data_t newval)
{node_t *p = slist_query(head, old);if (!p){printf("未找到需要更新的值 %d\n", old);return -1;}p->data = newval;return 0;
}/* 6. 遍历 */
void slist_showall(node_t *head)
{printf("链表内容: ");while (head){printf("%d -> ", head->data);head = head->next;}printf("NULL\n");
}/* 7. 链表回收 */
void slist_destroy(node_t **head)
{node_t *p = *head;while (p){node_t *tmp = p;p = p->next;free(tmp);}*head = NULL;
}/* ============= 测试主函数 ============= */
int main(void)
{node_t *head = NULL;/* 创建链表 */slist_create(&head, 10);slist_addhead(&head, 5);slist_addtail(&head, 20);slist_addtail(&head, 30);slist_insert(&head, 20, 25); // 在20后插入25slist_showall(head);/* 删除 */slist_delete(&head, 10);slist_showall(head);/* 查询并更新 */node_t *res = slist_query(head, 25);if (res)printf("找到结点: %d\n", res->data);slist_update(head, 25, 99);slist_showall(head);/* 回收 */slist_destroy(&head);slist_showall(head);return 0;
}

运行结果:

1.4 链表数据结构的小结

         应用场景: 
1)链式结构是一种动态增加数据的结构,如果存储的数据数量事先无法确定,使用链表是比较合适的;
2)常用于对数据进行频繁的增加或者删除的情况

         缺点:
查询效率比较低,主要原因是链表数据必须从头结点开始遍历。

2. 双向链表设计实现

说明:双向链表是在单向链表的基础上,指针域增加了存储上一个结点地址的指针变量(前驱指针)
优势:由于双向链表具有后继指针,也有前驱指针,所以对数据的增加,删除更加方便快捷,原因是链表结点的插入和删除,往往会影响上一个结点,相比于单向链表需要查找上一个结点,双向链表就可以通过结点的前驱指针直接获取。

2.1双向链表的组成

       1)   头指针
2)   结点:
数据域   (保存实际数据)
指针域   (保存下一个结点地址)  
(保存上一个结点地址) 

2.2  双向链表的设计实现:

1)  定义结点类型:          

 typedef int data_t;typedef strcut node{data_t  data;strcut  node   *prev;strcut  node   *next;}node_t;

            2)  双向链表功能算法实现:
(1)  链表创建
int dlist_create(node_t**,data_t);
(2) 数据添加
(2.1)  头插
int dlist_addhead(node_t**,data_t)
(2.2)  尾插
int dlist_addtail(node_t**,data_t)
(2.3)  中间插入
int dlist_insert(node_t** head,data_t pos,data_t new);
(3)  数据删除
int  dlist_delete(node_t**,data_t);
(4)  数据查询
node_t* dlist_query(node_t*,data_t); 
(5)  数据更新
int  dlist_update(node_t*,data_t old,data_t new);
(6)  数据遍历
void dlist_showall(node_t*);
(7)  链表回收
void dlist_destroy(node_t**);


2.3 示例程序:

dlist.c

#include <stdio.h>
#include <stdlib.h>typedef int data_t;// 定义双向链表结点
typedef struct node {data_t data;struct node *prev;struct node *next;
} node_t;/*链表创建 */
int dlist_create(node_t **head, data_t value) {*head = (node_t *)malloc(sizeof(node_t));if (*head == NULL) return -1;(*head)->data = value;(*head)->prev = NULL;(*head)->next = NULL;return 0;
}/*头插 */
int dlist_addhead(node_t **head, data_t value) {node_t *newnode = (node_t *)malloc(sizeof(node_t));if (newnode == NULL) return -1;newnode->data = value;newnode->prev = NULL;newnode->next = *head;if (*head != NULL) {(*head)->prev = newnode;}*head = newnode;return 0;
}/*尾插 */
int dlist_addtail(node_t **head, data_t value) {node_t *newnode = (node_t *)malloc(sizeof(node_t));if (newnode == NULL) return -1;newnode->data = value;newnode->next = NULL;if (*head == NULL) {newnode->prev = NULL;*head = newnode;return 0;}node_t *p = *head;while (p->next != NULL) {p = p->next;}p->next = newnode;newnode->prev = p;return 0;
}/*中间插入(在pos位置前插入新节点) */
int dlist_insert(node_t **head, data_t pos, data_t value) {node_t *p = *head;while (p != NULL && p->data != pos) {p = p->next;}if (p == NULL) return -1;node_t *newnode = (node_t *)malloc(sizeof(node_t));if (newnode == NULL) return -1;newnode->data = value;newnode->next = p;newnode->prev = p->prev;if (p->prev != NULL) {p->prev->next = newnode;} else {*head = newnode;  // 插在头部}p->prev = newnode;return 0;
}/*删除节点(删除值为value的第一个节点) */
int dlist_delete(node_t **head, data_t value) {node_t *p = *head;while (p != NULL && p->data != value) {p = p->next;}if (p == NULL) return -1;if (p->prev != NULL) {p->prev->next = p->next;} else {*head = p->next;  // 删除头节点}if (p->next != NULL) {p->next->prev = p->prev;}free(p);return 0;
}/*数据查询 */
node_t* dlist_query(node_t *head, data_t value) {node_t *p = head;while (p != NULL) {if (p->data == value) {return p;}p = p->next;}return NULL;
}/*数据更新 */
int dlist_update(node_t *head, data_t old, data_t new) {node_t *p = head;while (p != NULL) {if (p->data == old) {p->data = new;return 0;}p = p->next;}return -1;
}/*遍历(正向) */
void dlist_showall(node_t *head) {node_t *p = head;printf("DList: ");while (p != NULL) {printf("%d ", p->data);p = p->next;}printf("\n");
}/*链表回收 */
void dlist_destroy(node_t **head) {node_t *p = *head;while (p != NULL) {node_t *tmp = p;p = p->next;free(tmp);}*head = NULL;
}/* 测试主函数 */
int main() {node_t *head = NULL;// 创建链表dlist_create(&head, 10);dlist_addhead(&head, 5);dlist_addtail(&head, 20);dlist_addtail(&head, 30);dlist_insert(&head, 20, 15);  // 在20前插入15dlist_showall(head);// 查询node_t *q = dlist_query(head, 15);if (q) printf("Found: %d\n", q->data);// 更新dlist_update(head, 15, 18);dlist_showall(head);// 删除dlist_delete(&head, 10);dlist_showall(head);// 回收dlist_destroy(&head);return 0;
}

运行结果:

链表小结:

1. 定义

  • 单向链表(Singly Linked List)
    每个节点只有一个指针 next,指向下一个节点。
    结构简单,内存开销小,但只能 单方向遍历

  • 双向链表(Doubly Linked List)
    每个节点有两个指针:prev(前驱)和 next(后继)。
    可以 双向遍历,删除/插入更方便,但每个节点占用内存更大。

2. 节点结构

单向链表

typedef int data_t;
typedef struct node {data_t data;struct node *next;
} node_t;

双向链表

typedef int data_t;
typedef struct node {data_t data;struct node *prev;struct node *next;
} node_t;

3. 基本功能函数

功能单向链表 (slist)双向链表 (dlist)
创建链表slist_create()dlist_create()
头插入slist_addhead()dlist_addhead()
尾插入slist_addtail()dlist_addtail()
中间插入slist_insert()dlist_insert()
删除节点slist_delete()dlist_delete()
查询节点slist_query()dlist_query()
更新节点slist_update()dlist_update()
遍历输出slist_showall() (等同于 print)dlist_showall() (等同于 print)
销毁链表slist_destroy()dlist_destroy()

4. 主要区别

对比点单向链表双向链表
内存占用小,每个节点只需 next 指针大,每个节点需 prev + next
遍历方向只能从头到尾可从头到尾,也能从尾到头
插入效率需要找到插入点的前驱节点直接利用 prev / next 修改指针,效率更高
删除效率需要找到删除节点的前驱节点直接用 prev / next 修改即可
实现复杂度简单稍复杂
适用场景内存紧张、简单队列/栈结构频繁插入/删除、需要双向遍历

5. 输出函数

单向链表输出

void slist_showall(node_t* head) {node_t* p = head;while (p) {printf("%4d\n", p->data);  // 每行一个,右对齐p = p->next;}
}

双向链表输出

void dlist_showall(node_t* head) {node_t* p = head;while (p) {printf("%4d\n", p->data);p = p->next;}
}

总结:

  • 单向链表:结构简单,适合空间敏感、逻辑简单的场景(如栈、队列)。

  • 双向链表:操作灵活,适合需要频繁插入/删除和双向遍历的场景(如 LRU 缓存、双端队列)。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/98387.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/98387.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

从反向代理到负载均衡:Nginx + Tomcat 构建高可用Web服务架构

从反向代理到负载均衡&#xff1a;Nginx Tomcat 构建高可用Web服务架构 文章目录从反向代理到负载均衡&#xff1a;Nginx Tomcat 构建高可用Web服务架构一、基础铺垫&#xff1a;什么是反向代理&#xff1f;1.1 反向代理的核心原理1.2 Nginx反向代理实战配置步骤1&#xff1a…

Simulink中使用Test sequence单元测试

一、Tips 在对simulink模型进行Test sequence单元测试时&#xff0c;如果采取书写测试用例的话&#xff0c;有以下操作。 1、使用”fprintf(‘time%f\n’, t);“来打印当前step的时间&#xff1b; 二、数据类型转换 1、double类型 -> boolean类型 clc; clear all;% 1、doubl…

【mysql】SQL自连接:什么时候需要,什么时候不需要?

SQL自连接:什么时候需要,什么时候不需要? 通过具体示例和对比解析,彻底搞懂SQL自连接的使用场景 在处理SQL查询时,尤其是当表中存在自引用关系(如referee_id引用同一张表的id)时,很多开发者会疑惑:这个查询到底需不需要自连接?本文将通过多个具体示例,带你彻底弄清何…

「美」创新在于人,而不是产品 - AxureMost 落葵网

添加图片注释&#xff0c;不超过 140 字&#xff08;可选&#xff09; 第一章&#xff1a;创新的心理学 创新与心理安全 蜡烛问题&#xff1a;卡尔邓克尔的蜡烛问题实验揭示了创造性思维的重要性。通过颠覆对盒子用途的先入为主观念&#xff0c;参与者能够找到创新性的解决方案…

新规则,新游戏:AI时代下的战略重构与商业实践

当你的客服AI能够真正像员工一样理解客户的行业术语&#xff0c;当AI能主动从大量的客户咨询中筛选出高价值潜在客户 —— 这已经不再是理想中才能存在的场景&#xff0c;而是当下 “人工智能 ” 行动深入推进中&#xff0c;企业智能化转型的真实写照。 "人工智能 " …

ScanNet: Richly-annotated 3D Reconstructions of Indoor Scenes 数据集构建

paper link: paperlink Abstract: 这个数据集是个RGB-D视频数据集&#xff0c;在707个不同空间中获取了1513个扫描的场景&#xff0c;250w个视图&#xff0c;并且标注了相机位姿&#xff0c;表面重建&#xff0c;语义分割。本数据集共有20人扫描500名工作者进行标注。 数据集…

c语言期末复习

一、选择题(10道) 1、以下哪个不是C语言的关键字? A) int B) float C) string D) while (答案:C) 2、表达式 5 / 2 的结果是: A) 2.5 B) 2 C) 3 D) 2.0 (答案:B) 3、指针变量存储的是: A) 变量的值 B) 变量的地址 C) 变量的类型 D) 变量的名称 (答案:B) 4、以…

JLINK 调试器单步调试单片机

0 JLINK 调试器单步调试单片机 1 物理层1.1 调整电压和开发板一致2 环境搭建 2.1 安装 JLink_Windows_V862_x86_642.2 vscode 配置 {"version": "0.2.0","configurations": [{"name": "(gdb) 启动","type": "…

大模型(LLM)安全保障机制(技术、标准、管理)

大模型&#xff08;LLM&#xff09;的安全保障涉及技术、标准、管理等多个层面。下面我将结合其核心风险&#xff0c;为你梳理主要的安全机制、相关标准框架以及一些实践建议。为了让您快速了解大模型面临的主要风险及相应的应对机制&#xff0c;我准备了一个表格&#xff1a;安…

虚拟机之CentOS、网络设置的有趣问题

前言 年初射出的子弹&#xff0c;今天中了。 年初埋下的坑&#xff0c;今年踩了。 回首过往&#xff0c;why&#xff1f; because&#xff1a;当时下载VMware的时候。没有设置网络。 重点——使用VMware安装CentOS 9 使用VMware安装CentOS Stream 9_哔哩哔哩_bilibili 总…

Biomni:来自斯坦福的通用型生物医学 AI 智能体,科研“虚拟助手“来了!

在当今生物医学研究中&#xff0c;实验手段和数据量正以前所未有的速度膨胀。从基因组学、单细胞组学到多模态数据&#xff0c;再到可穿戴设备的健康监测&#xff0c;科研人员每天都在与庞大的数据和复杂的分析流程打交道。 然而&#xff0c;实验设计琐碎、工具分散、跨学科整合…

移植后 eto 阳性 干扰素 α1b、白介素 - 2 dli

在异基因造血干细胞移植&#xff08;allo-HSCT&#xff09;后仍存在 AML1-ETO&#xff08;ETO&#xff09;融合基因阳性的患者中&#xff0c;干扰素 α1b 联合白介素 - 2&#xff08;IL-2&#xff09; 是临床中探索用于清除微小残留病&#xff08;MRD&#xff09;、降低复发风险…

防止接口被薅羊毛(防刷)(DAY 002)

背景&#xff1a;短信验证码接口被不法分子用来做灰产&#xff08;短信邮箱轰炸机&#xff09; 如何避免⾃⼰的⽹站成为”⾁鸡“或者被刷&#xff1f; 增加图形验证码&#xff08;开发⼈员&#xff09;单IP请求次数限制&#xff08;开发⼈员&#xff09; 防刷之图形验证码&…

【RabbitMQ】----RabbitMQ 的7种工作模式

1.Simple(简单模式) P:⽣产者,也就是要发送消息的程序 C:消费者,消息的接收者 Queue:消息队列,图中⻩⾊背景部分.类似⼀个邮箱,可以缓存消息;⽣产者向其中投递消息,消费者从其中取出消息. 特点:⼀个⽣产者P&#xff0c;⼀个消费者C,消息只能被消费⼀次.也称为点对点(Point-to-P…

今日分享:C++ -- list 容器

&#x1f60e;【博客主页&#xff1a;你最爱的小傻瓜】&#x1f60e; &#x1f914;【本文内容&#xff1a;C list容器 &#x1f60d;】&#x1f914; --------------------------------------------------------------------------------------------------------------------…

【Python】数据可视化之分布图

分布图主要用来展示某些现象或数据在地理空间、时间或其他维度上的分布情况。它可以清晰地反映出数据的空间位置、数量、密度等特征&#xff0c;帮助人们更好地理解数据的内在规律和相互关系。 目录 单变量分布 变量关系组图 双变量关系 核密度估计 山脊分布图 单变量分布…

DDD+WebAPI实战

DDD+WebAPI实战 DDD(领域驱动设计,Domain-Driven Design)是一种面向对象的设计方法,它强调将业务逻辑封装在模型中,并通过这些模型来驱动整个应用的设计。在.NET环境中,特别是在使用ASP.NET Core和Web API构建应用时,DDD可以帮助我们更好地组织代码,使得业务逻辑更加清…

人力资源管理的思维方法学习笔记1

北京师范大学政府管理学院1.课程介绍&#xff1a; 讲述视角上&#xff0c;本课程侧重人力资源管理的思维方式&#xff0c;即人力资源管理理论和时间的不同视角和主导范式的分析。这既是对人力资源管理理论发展的凝练&#xff0c;也是对人力资源管理实践演进过程的总结。对于把握…

适应新环境:Trae编辑器下的IDEA快捷键定制

介绍&#xff1a;学习如何在Trae编辑器中配置IntelliJ IDEA风格的快捷键&#xff0c;减少开发环境间的切换成本&#xff0c;提升编码效率。通过安装插件或手动调整&#xff0c;让你更快适应新工具大家好&#xff0c;我是凯哥Java本文标签&#xff1a;代码编辑效率、Trae快捷键、…

基于YOLO8的汽车碰撞事故检测系统【数据集+源码+文章】

基于YOLOv8和Streamlit的汽车碰撞事故检测系统 文末附下载地址 开发目的 随着城市化进程的加快和机动车保有量的持续攀升&#xff0c;道路交通安全问题日益突出&#xff0c;汽车碰撞事故频发不仅严重威胁驾乘人员的生命安全&#xff0c;也对公共秩序、应急响应效率及交通管理…