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

数据结构——顺序表和单向链表(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.dtcms.com/a/365177.html

相关文章:

  • MybatisPlus 根据实体类获取对应的Mapper
  • 硬件开发1-51单片机2-按键、中断
  • Process Lasso:高效管理和优化计算机进程
  • 并查集_路径压缩
  • [嵌入式embed][Qt]Qt5.12+Opencv4.x+Cmake4.x_用Qt编译linux-Opencv库 测试
  • Linux 用户的 Windows 改造之旅
  • linux命名管道的使用
  • 关于linux数据库编程——sqlite3
  • Unity 中 打包 assetsBundle
  • C语言字符函数和字符串函数(1)
  • 《网络安全实战:CC攻击(应用层)与DDoS攻击(网络层)的底层逻辑与防御体系》​
  • 基于SpringBoot+Vue开发的环境保护监督管理网站
  • 如何通过控制台查看向量检索服务使用数据
  • Vue Router原理及SPA页面刷新解析
  • 融云:当我们谈论 AI 重构业务时,我们到底在谈论什么
  • SAM TTS网页官网入口 – 在线版微软tts在线语音合成助手
  • 【TRAE调教指南之MCP篇】FastMCP:快速制作自己的MCP服务
  • 对锁的总结
  • Agent 热潮遇冷?Manus 为何仍是 “版本神”
  • 充电枪结构设计-经验总结
  • 具身智能让人形机器人 “活” 起来:懂语言、能感知、会行动,智能进化再提速
  • docker安装rabbitmq(4.1.4-management)
  • 客户分层是什么?提升企业运营效率
  • 【python】运算符及语句
  • 数据结构:栈和队列(上)
  • 低代码革命遇瓶颈?这个“套娃神技“才是破局关键!
  • 【FastDDS】Layer DDS之Domain ( 05-Creating a DomainParticipant)
  • 关于linux网络编程——3
  • 扫地日记:有鹿巡扫机器人在景区被人类“调戏”的365天
  • ansible总结2