数据结构——顺序表和单向链表(2)

目录

前言

一、单向链表

1、基本概念

2、单向链表的设计

(1)节点设计

(2)初始化空单向链表

(3)、初始化数据节点

(4)数据节点

(5)判断链表是否为空

(6)遍历链表

(7)删除数据

(8)销毁整个链表

(9)修改数据


前言

        在软件的世界里,程序 = 数据结构 + 算法。这句广为流传的名言,深刻地揭示了数据结构的核心地位。它是我们组织、存储和管理数据的艺术,是构建高效、稳定应用程序的基石。而在众多数据结构中,线性表作为最基础、最常用的一种,其重要性不言而喻。

        顺序表和单向链表,正是线性表两种最经典的实现方式。它们如同“一体两面”,代表了计算机中两种最根本的物理存储思想:连续的空间离散的空间

  • 顺序表凭借其底层连续的物理结构,带来了无与伦比的随机访问性能,仿佛一本页码清晰的书籍,我们可以根据目录瞬间翻到任何一页。然而,这种连续性的代价便是在中间进行插入或删除时,可能引发大规模的“数据搬迁”,效率堪忧。

  • 单向链表则巧妙地利用指针,将离散的内存空间“串联”起来。它牺牲了随机访问的能力,换来了在任意位置高效插入与删除的灵活性,如同一条环环相扣的链条,只需改变节点的指向,即可轻松完成结构的调整。

        理解二者的内在原理、优缺点以及适用场景,是每一位开发者内功修炼的必经之路。这不仅是为了应对面试官的考问,更是为了在未来的开发中,能够根据实际需求,做出最合理的技术选型,写出性能更优、更优雅的代码。


一、单向链表

1、基本概念

概念:        

        链式存储的线性表,简称链表(线性关系+链式存储)

图解:

说明:

        既然顺序存储中的数据因为挤在一起而导致需要成片移动,那很容易想到的解决方案是将数据离散的存储在不同的内存块中,然后用指针将它们串起来,这种朴素的思路所形成的链式线性表,就是所谓链表(单向链表(单向循环链表)、双向链表(双向循环链表))


2、单向链表的设计

1、节点设计
2、初始化空单向链表(初始化头节点)
3、初始化数据节点
4、增删查改单向链表// 前提:判断链表是否为空a、增:头插法、尾插法b、查:遍历链表c、删:删除链表中的某个数据、销毁整个链表d、改:修改链表中的某个数据

(1)节点设计

说明:

        单向链表节点包含两个域,一个数据域一个指针域,数据域存放用户的数据,指针域指向本节点或指向相邻下一个节点。

typedef struct node
{// 数据域int data;  // 指针域struct node* next_p;    // 指向相邻的下一个节点的指针}node_t, *node_p;

(2)初始化空单向链表

概念:

        首先链表有两种常见的形式,一种是带有所谓的头节点的,一种是不带头节点的,所谓的头节点是不存放有效数据的节点,仅仅用来方便操作

图解:

        

注意:

        头节点head_node是可选的,为了方便某些操作,建议大家有头节点好一些

        头节点的指针:head_node的next_p

说明:

        由于头节点不存放有效数据的,因此如果空链表中带有头节点,那么头指针(head_node的next_p)将永远不变,这会给以后的链表操作带来些许便捷

图解:

示例代码:

/*** @brief  初始化空单向链表(初始化头节点)* @note   None* @param  None* @retval 成功:返回指向这个头节点的指针*         失败:返回NULL
*/
node_p LINK_LIST_InitHeadNode(void)
{// 1、给头节点申请一个内存空间(堆内存)node_p p = malloc(sizeof(node_t));bzero(p, sizeof(node_t));// 2、将头节点的next_p指针指向NULLif ( p!= NULL){// 数据域// 指针域p->next_p = NULL;   // 防止指针乱指}else{return NULL;}// 3、成功返回头节点return p;
}

(3)、初始化数据节点

说明:

        数据节点

图解:

示例代码:

        

/*** @brief  初始化数据节点* @note   None* @param  data:数据节点中的数据* @retval 成功:返回指向这个数据节点的指针*         失败:返回NULL
*/
node_p LINK_LIST_InitDataNode(int data)
{// 1、给数据节点申请一个内存空间(堆内存)node_p p = malloc(sizeof(node_t));bzero(p, sizeof(node_t));// 2、将数据节点的next_p指针指向NULL,并且将传进来的数据对此节点的数据进行赋值if ( p != NULL){// 数据域p->data   = data;   // 数据赋值// 指针域p->next_p = NULL;   // 防止指针乱指}else{return NULL;}// 3、成功返回数据节点return p;}

(4)数据节点

说明:

        将数据节点插入到链表中,一种头插法(链表中的头节点的后面插进去),一种尾插法(链表中的最后一个节点后面插入进去)

图解:

头插法:

尾插法:

示例代码:

/*** @brief  插入数据(头插法)* @note   None* @param  head_node:头节点*         new_node: 要插入的数据节点* @retval None
*/
void LINK_LIST_InsertHeadDataNode(node_p head_node, node_p new_node)
{// 1、先让new_node里面的next_p指向data_node  (head_node->next_p里面有data_node的地址)new_node->next_p = head_node->next_p;// 2、再让head_node里面的next_p指向new_nodehead_node->next_p = new_node;
}/*** @brief  插入数据(尾插法)* @note   None* @param  head_node:头节点    *         new_node: 要插入的数据节点* @retval None
*/
void LINK_LIST_TailInsertDataNode(node_p head_node, node_p new_node)
{// 1、设置一个中间指针,将指针指向单向链表的末尾节点node_p tmp_p = NULL;for (tmp_p = head_node; tmp_p->next_p != NULL; tmp_p = tmp_p->next_p);// 2、让tmp_p的next_p指向新节点tmp_p->next_p = new_node;// 3、再让new_node的next_p指向NULLnew_node->next_p = NULL;
}

(5)判断链表是否为空

说明:

        如果头节点的next_p指向的是NULL,那么就可以证明这个链表是空的

图解:

示例代码:

/*** @brief  判断链表是否为空* @note   None* @param  head_node:头节点    * @retval 如果链表为空:返回true*         如果链表非空:返回false
*/
bool LINK_LIST_IfEmpty(node_p head_node)
{return head_node->next_p == NULL;
}

(6)遍历链表

说明:

        遍历整个链表,并逐个打印里面的数据

图解:

示例代码:

/*** @brief  遍历整个链表并打印出里面的数据* @note   None* @param  head_node:头节点    * @retval 成功:返回0*         失败:返回-1
*/
int LINK_LIST_ShowListData(node_p head_node)
{// 1、判断链表是否为空,是的话,返回-1if (LINK_LIST_IfEmpty(head_node))return -1;// 2、遍历整个链表,并逐个打印里面的数据node_p tmp_p = NULL;int    i     = 0;printf("======================链表中的数据==========================\n");for ( tmp_p = head_node->next_p; tmp_p!=NULL; tmp_p=tmp_p->next_p){printf("链表中的第%d的节点, 数据为: %d\n", i, tmp_p->data);i++;}printf("===========================================================\n");// 3、成功返回0return 0;
}

(7)删除数据

说明:

        根据数据来删除链表中的节点

图解:

示例代码:

/*** @brief  删除数据* @note   根据数据的值找到链表中的节点,并将其删除* @param  head_node:头节点  *         del_data: 要删除的数据   * @retval 成功:返回0*         失败:返回-1
*/
int LINK_LIST_DelDataNode(node_p head_node, int del_data)
{// 1、判断链表是否为空,是的话,返回-1if (LINK_LIST_IfEmpty(head_node))return -1;// 2、移动到要删除的节点数据那里去node_p tmp_p     = NULL;node_p last_node = NULL;node_p del_node  = NULL;node_p next_node = NULL;// a、从头到尾遍历一遍,找到要删除的数据for (tmp_p = head_node; tmp_p->next_p!=NULL; tmp_p=tmp_p->next_p){// b、判断要删除的数据if ( (tmp_p->next_p->data) == del_data){// c、保存删除数据的上一个节点和下一个节点、及删除节点last_node = tmp_p;del_node  = tmp_p->next_p;next_node = del_node->next_p; break;}}// 3、绕过原链表要删除的节点last_node->next_p = next_node;del_node->next_p  = NULL;// 4、释放要删除节点的资源free(del_node);// 5、成功返回0return 0;}

(8)销毁整个链表

说明:

        从头节点开始,将其后面的数据节点一个个删除,最后再删除头节点本身

图解:

示例代码:

/*** @brief  销毁链表* @note   None* @param  head_node:头节点    * @retval None
*/
void LINK_LIST_UnInit(node_p head_node)
{// 1、如果链表为空,那么直接释放头节点空间即可if (LINK_LIST_IfEmpty(head_node)){free(head_node);return;}// 2、node_p tmp_p      = NULL;               // 遍历node_p last_node  = head_node;node_p del_node   = head_node->next_p;node_p next_node  = del_node->next_p;int num = 0;// 如果链表只有一个数据节点if (next_node == NULL){last_node->next_p = next_node;free(del_node);}// 否则else{for (tmp_p = head_node; tmp_p->next_p!=NULL; tmp_p=tmp_p->next_p){// a、删除节点并释放其内存last_node->next_p = next_node;free(del_node);printf("num == %d\n", num);num++;// b、轮回继续del_node  = next_node;next_node = next_node->next_p;}// 少删除了一个节点数据last_node->next_p = next_node;free(del_node);}// 3、释放头节点free(head_node);  
}

(9)修改数据

说明:

        通过数据值,找到链表中的节点,并修改里面的数据

图解:

示例代码:

/*** @brief  修改数据* @note   根据数据的值来找到链表中的节点,对里面的数据进行修改* @param  head_node:  头节点  *         data:       要修改的数据*         change_data:修改的数据* @retval 成功:返回0*         失败:返回-1
*/
int LINK_LIST_ChangeNodeData(node_p head_node, int data, int change_data)
{// 1、如果链表为空,那么直接释放头节点空间即可if (LINK_LIST_IfEmpty(head_node))return -1;// 2、遍历整个链表,找到数据的节点,并修该节点的相应的数据node_p tmp_p = NULL;// a、遍历所有可能for (tmp_p = head_node->next_p; tmp_p!=NULL; tmp_p=tmp_p->next_p){// b、找到要修改的数据if ((tmp_p->data) == data)      // 根据学号{tmp_p->data = change_data;  // 修改这个学生的其它信息break;}   }// 3、成功返回0return 0;
}

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

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

相关文章

More Effective C++ 条款26:限制某个类所能产生的对象数量

More Effective C 条款26:限制某个类所能产生的对象数量核心思想:通过控制类的实例化过程,限制程序中该类的对象数量,可以防止资源过度使用,确保系统资源合理分配,并实现单例或有限实例模式。 &#x1f680…

CMS系统维护中常见的安全威胁及防护指南!

内容管理系统(CMS)已成为网站建设的核心工具,但随之而来的安全风险却常被低估。超过70%的网站使用CMS构建,而其中近半数曾遭遇安全漏洞威胁。作为运维人员和开发者,了解这些安全威胁并采取相应防护措施至关重要。 一、…

springboot knife4j 接口文档入门与实战

Spring Boot3 Knife4j 项目地址https://gitee.com/supervol/loong-springboot-study(记得给个start,感谢)Knife4j 介绍在国内 Java 开发领域,Knife4j 是一款广受欢迎的 API 文档工具,它基于 OpenAPI 规范,在…

Spring Boot 事务失效的八大原因及解决方案详解

在 Spring Boot 项目开发中,声明式事务管理通过 Transactional 注解提供了极大的便利。但许多开发者都曾遇到过事务不生效的困扰。本文将详细分析导致 Spring Boot 事务失效的八大常见情况,并提供相应的解决方案。1. 数据库引擎不支持事务问题分析&#…

数据结构:顺序栈与链栈的原理、实现及应用

数据结构详解:顺序栈与链栈的原理、实现及应用 1. 引言:栈的核心概念 栈(Stack)是一种重要的线性数据结构,它遵循后进先出(Last In First Out, LIFO)的原则。这意味着最后一个被添加到栈中的元素…

apipost 8.x 脚本循环调用接口

apipost 8.x 脚本循环调用接口背景实现先说整体逻辑:最后背景 上周为了找某OA 偶尔出现的诡异现象,需要用测试工具来压测,看看这个问题能否重现。以前用过Jmeter,但是没有装,正好有个国产的apipost看看如何&#xff1…

STM32 - Embedded IDE - GCC - 使用 GCC 链接脚本限制 Flash 区域

导言如上所示,Keil限制flash区域只需要在IROM1里将Start框框与Size框框填入具体信息即可。比如bootloader程序一般从0x8000000开始,大小0x10000(64KB)。此时,flash的范围被限制在0x8000000 ~ 0x800FFFF。 另外&#xf…

Jenkins和Fastlane的原理、优缺点、用法、如何选择

Jenkins 和 Fastlane 是软件开发中用于自动化流程的工具一、Jenkins实现自动化打包1.1具体实现步骤安装与配置:首先在服务器上安装 Jenkins,可以通过官方提供的安装包进行安装,支持多种操作系统。安装完成后,通过 Web 界面进行初始…

DOM常见的操作有哪些?

1.DOM文档对象模型(DOM)是HTML和XML文档的编程接口它提供了对文档结构化表述,并定义了一种方式可以使从程序中对该结构进行访问,从而改变文档的结构,样式和内容任何HTML或XML文档都可以用DOM表示一个由节点构成的层级结…

【Kubernetes】知识点3

25. 说明Job与CronJob的功能。答:Job:一次性作业,处理短暂的一次性任务,仅执行一次,并保证处理的一个或者多个 Pod 成功结束。CronJob:周期性作业,可以指定每过多少周期执行一次任务。26. Kuber…

LINUX-网络编程-TCP-UDP

1.目的:不同主机,进程间通信。2.解决的问题1)主机与主机之间物理层面必须互相联通。2)进程与进程在软件层面必须互通。IP地址:计算机的软件地址,用来标识计算机设备MAC地址:计算机的硬件地址&am…

目标检测定位损失函数:Smooth L1 loss 、IOU loss及其变体

Smooth L1 Loss 概述 Smooth L1 Loss(平滑 L1 损失),是一个在回归任务,特别是计算机视觉中的目标检测领域(如 Faster R-CNN, SSD)非常核心的损失函数。 xxx 表示模型的预测值,yyy 表示真实值&am…

Android开发之fileprovider配置路径path详细说明

第一步在清单文件配置fileprovider属性<providerandroid:name"androidx.core.content.FileProvider"android:authorities"${applicationId}.fileprovider"android:exported"false"android:grantUriPermissions"true"><meta-d…

【ComfyUI】图像描述词润色总结

在 ComfyUI 的工作流中&#xff0c;图像反推描述词能帮我们从图像里抽取语义信息&#xff0c;但这些原始描述往往还显得生硬&#xff0c;缺乏创意或流畅性。为了让提示词更自然、更有表现力&#xff0c;就需要“润色”环节。润色节点的任务&#xff0c;不是重新生成描述&#x…

java面试中经常会问到的IO、NIO问题有哪些(基础版)

文章目录一、IO 基础与分类二、NIO 核心组件与原理三、NIO 与 BIO 的实战对比四、AIO 与 NIO 的区别五、Netty 相关&#xff08;NIO 的高级应用&#xff09;总结Java 中的 IO&#xff08;输入输出&#xff09;和 NIO&#xff08;非阻塞 IO&#xff09;是面试中的重要考点&#…

时序数据库选型指南:如何为工业场景挑选最强“数据底座”

工业4.0时代&#xff0c;工厂化身为巨大的数据生产中心。数以万计的传感器、PLC和设备每时每刻都在产生着海量的时间序列数据&#xff08;Time-Series Data&#xff09;&#xff1a;温度、压力、流速、振动、设备状态……这些带时间戳的数据是工业互联网的血液&#xff0c;蕴含…

【排序算法】冒泡 选排 插排 快排 归并

一、冒泡排序// 冒泡排序var bubbleSort function (arr) {const len arr.length;for (let i 0; i < len; i) {let isSwap false;for (let j 0; j < len - 1; j) {// 每一次遍历都要比较相邻元素的大小&#xff0c;如果满足条件就交换位置if (arr[j] > arr[j 1])…

电子病历空缺句的语言学特征描述与自动分类探析(以GPT-5为例)(中)

语言学特征刻画(特征库) 句法特征 句法特征是识别 SYN 类电子病历空缺句的核心语言学维度,其量化分析通过构建依存句法结构的形式化指标,实现对语法不完整性的客观描述。该类特征主要包括依存树不完备指标、谓词-论元覆盖率及从属连词未闭合三类核心参数,共同构成 SYN 类…

InnoDB存储引擎-事务

1. 事务概述事务可由一条简单的SQL语句组成,也可以由一组复杂的SQL语句组成. 事务是访问并更新数据库中各种数据项的一个程序执行单元. 在事务中的操作, 要么都做修改, 要么都不做. 对于 InnoDB存储引擎而言, 其默认的事务隔离级别 RR , 完全遵循和满足了事务的 ACID 特性. 1.1…

web项目的目录结构

web项目的目录结构 WEB-INF 存放class文件、jar文件和配置文件&#xff0c;对于用户来说该文件夹是不可见的WEB-INF/web.xml web应用程序的描述文件&#xff0c;用来配置资源&#xff0c;如servlet、过滤器、监听器等WEB-INF/classes 用于存放class文件&#xff0c;也是该web应…