02线性表 - 链表

这里是只讲干货不讲废话的炽念,这个系列的文章是为了我自己以后复习数据结构而写,所以可能会用一种我自己能够听懂的方式来描述,不会像书本上那么枯燥和无聊,且全系列的代码均是可运行的代码,关键地方会给出注释^_^

全文1W+字

版本:C++17

编译器:Clion 2023.3.24

暂时只给出代码,不会涉及到基础知识的讲解

 1.链表的定义

1.1定义链表结点

// 链表的核心是定义结点
typedef struct list_node{ // C语言中必须要先声明 list_node,不然编译器会报错,C++可以忽略这个问题Element value;                // 结点存储的数据元素struct list_node *next;       // 指向下一个结点的指针
}ListNode;

1.2定义链表头结点

// 链表的表头
typedef struct {ListNode head;        // 头结点,通常不存储实际数据,用作链表的起始点int len;              // 链表的长度,用于记录链表中实际结点的数量
}LinkList;

2.链表的实现函数

2.1.表的创建以及删除

// 创建链表和删除链表
LinkList *createLinkList();                   // 创建一个链表void releaseLinkList(LinkList* linkList);     // 释放一个链表

 

2.2.向表中插入元素

// 三种插入方式
int insertLinkList(LinkList *linkList, int pos, Element val);   // 任意位置上插入int headInsertLinkList(LinkList *linkList, Element val);        // 头插法int tailInsertLinkList(LinkList *linkList, Element val);        // 尾插法

 

2.3.表的展示

void showLinkList(LinkList *linkList);                      // 显示链表内的元素

 

2.4.删除表中的元素

int deleteLinkList(LinkList *linkList, Element val);        // 删除链表中的特定元素

 

2.5逆转链表

void reverseLinklist(LinkList *linkList);                   // 逆转链表

 

 

 

 

3.实现函数的代码

3.1 createLinkList函数的实现

函数定义:
LinkList *createLinkList();

 

函数功能:
该函数用于创建一个空的链表,并返回链表的头结点指针

 

实现思路:
a.申请一个类型为LinkList,大小为sizeof(LinkList)的头结点b.判空,判断申请是否成功c.初始化LinkList->len = 0, LinkList->head.value = 0(根据实际情况来定义这个值)d.初始化LinkList->head.next = NULLe.返回指向新创建链表头结点的指针

 

具体实现代码:
LinkList *createLinkList() {LinkList *linkList = (LinkList *) malloc(sizeof (LinkList));if (!linkList){printf("Malloc linklist failed\n");return NULL;}linkList->len = 0;              // 表示链表的长度linkList->head.value = 0;       // 根据具体的场景来定义这个值linkList->head.next = NULL;printf("Create linklist success!\n");return linkList;
}

 

测试代码:
int main(){LinkList *linkList= createLinkList();return 0;
}

 

测试结果: 

 

 

3.2 releaseLinkList函数的实现

函数定义:
void releaseLinkList(LinkList* linkList);
函数功能:
释放链表中的所有节点(包括头节点),避免内存泄漏
实现思路:

方法一:头删法

a.初始化:
获取链表头结点地址并赋值给 node 指针。
node->next 指向待删除结点的下一个结点,用于备份b.循环删除:
循环遍历链表,条件为 node->next 不为空。
使用 tmp 指针指向要删除的结点 (node->next)。
将 node 的 next 指针指向 tmp 的下一个结点 (tmp->next),移除 tmp 指向的结点。
释放 tmp 指向的结点内存。
更新链表长度,减去 1c.释放头结点:
循环结束后,释放头结点的内存

方法二:循环删除法

a.初始化: 
将 node 指针指向链表的头结点的下一个结点,准备开始遍历链表b.循环删除:
使用 while 循环遍历链表,直到到达链表末尾。在循环中:
备份当前要删除的结点到 tmp 指针
将当前结点的 next 指针指向下一个结点,从而将 tmp 指向的结点从链表中移除
释放 tmp 指向的结点的内存
更新链表长度,减去 1c.释放头结点: 
最后释放头结点的内存
具体实现代码:
方法一:头删法(不断删除头结点后的那个结点,直到为空)
void releaseLinkList1(LinkList *linkList) {if (linkList){ListNode *node = &linkList->head;// 不断的删除头结点的下一个指向,直到为空(头删法)while (node->next){ListNode *tmp = node->next; // 备份要删除的结点node->next = tmp->next;     // 这里为什么不是node = node->next;free(tmp);                  // 因为这样已经移动了头结点,会失去对前一个节点的引用,可能导致内存泄露linkList->len--;}printf("Release success! LinkList have %d node\n",linkList->len);free(linkList);}
}

 

 

方法二:循环删除法
void releaseLinkList2(LinkList *linkList){if (linkList) {ListNode *node = linkList->head.next;// 使用循环遍历链表,释放除头结点外的每个节点(循环删除法)while (node) {ListNode *tmp = node;           // 备份当前节点node = node->next;              // 移动到下一个节点free(tmp);                      // 释放当前节点linkList->len--;}printf("Release success! LinkList have %d node\n",linkList->len);free(linkList);}
}

 

 

测试代码:

因为需要知道链表中还剩下多少元素,所以先引入加入函数

1.头删法测试代码:
int main(){LinkList *linkList= createLinkList();for (int i = 0; i < 5; i++){headInsertLinkList(linkList, i+100);}showLinkList(linkList);insertLinkList(linkList, 2, 888);showLinkList(linkList);insertLinkList(linkList, 1, 888);showLinkList(linkList);tailInsertLinkList(linkList, 999);showLinkList(linkList);tailInsertLinkList(linkList, 111);showLinkList(linkList);releaseLinkList1(linkList);return 0;
}

 

2.循环删除法测试代码:
int main(){LinkList *linkList= createLinkList();for (int i = 0; i < 5; i++){headInsertLinkList(linkList, i+100);}showLinkList(linkList);insertLinkList(linkList, 2, 888);showLinkList(linkList);insertLinkList(linkList, 1, 888);showLinkList(linkList);tailInsertLinkList(linkList, 999);showLinkList(linkList);tailInsertLinkList(linkList, 111);showLinkList(linkList);releaseLinkList2(linkList);return 0;
}

 

测试结果:
1.头删法测试结果:

2.循环删除法测试结果:

 

 

 3.3 insertLinkList && headInsertLinkList && tailInsertLinkList 函数的实现

函数定义:
int insertLinkList(LinkList *linkList, int pos, Element val);   // 任意位置上插入int headInsertLinkList(LinkList *linkList, Element val);        // 头插法int tailInsertLinkList(LinkList *linkList, Element val);        // 尾插法
函数功能:

1.insertLinkList

在链表的指定位置插入新节点

 

 2.headInsertLinkList

在链表头部插入新节点

 

 3.tailInsertLinkList

 在链表尾部插入新节点
实现思路:

1.insertLinkList

a.判断链表是否为空,判断插入位置是否合法* 在 pos 的位置上插入,就要找到 pos-1 的位置* pos = 1 逻辑位置上的插入,就定义 cnt = 0 是头节点的位置* pos = 0 逻辑位置上的插入,就定义 cnt = -1 是头节点的位置b.通过遍历找到待插入结点的前一个结点c.判断 node 是否为空(逻辑完备性)d.申请结点,将新节点 newNode->next 设为 NULL,将新节点的 value 设为 vale.将 newNode->next 赋值为 node->next    //这两步一定要注意顺序f.将 node->next 赋值为 newNode          //这两步一定要注意顺序g.更新链表长度,LinkList->len++

2.headInsertLinkList

a.判断链表是否为空b.申请结点,将新节点 newNode->next 设为 NULL,将新节点的 value 设为 valc.将 newNode->next 赋值为 LinkList->head.next    //这两步一定要注意顺序d.将 LinkList->head.next 赋值为 newNode->next    //这两步一定要注意顺序e.更新链表长度,LinkList->len++

3.tailInsertLinkList

a.判断链表是否为空b.申请结点,将新节点 newNode->next 设为 NULL,将新节点的 value 设为 valc.判断 LinkList 是否为只有头节点的链表d.如果是只有头节点的链表,则直接插入到头节点后*即 LinkList->head.next = newNode;e.如果不是,则不断循环找到尾节点,之后插入到尾节点后*即 node->next = newNode;g.更新链表长度,LinkList->len++

 

 

具体实现代码:

1.insertLinkList

int insertLinkList(LinkList *linkList, int pos, Element val) {if (!linkList){printf("LinkList is null. Insert error!\n");return -1;}if (pos < 1 || pos > linkList->len + 1){printf("Insert position out of range!\n");return -1;}ListNode *node = &linkList->head;int cnt = 0;while (node && cnt < pos - 1){      // 找到待插入位置的前一个位置node = node->next;cnt++;}// 判断是找到了pos-1的位置,还是遍历完链表都没有找到if (node == NULL){                  // 为了逻辑完备性,多加一段代码printf("Insert position out of range!\n");return -1;}ListNode *newNode = (ListNode *) malloc(sizeof (ListNode)); //这段是核心代码newNode->next =  NULL;newNode->value = val;newNode->next = node->next;         // 这里注意顺序,一定要先保存当前结点的下一个结点的位置node->next = newNode;linkList->len++;return 0;
}

2.headInsertLinkList

int headInsertLinkList(LinkList *linkList, Element val) {if (!linkList){printf("LinkList is null. Insert error!\n");return -1;}ListNode *newNode = (ListNode *) malloc(sizeof (ListNode));if (newNode == NULL) {printf("Memory allocation failed.\n");return -1;}newNode->next = NULL;newNode->value = val;newNode->next = linkList->head.next;linkList->head.next = newNode;linkList->len++;return 0;
}

 

3.tailInsertLinkList

int tailInsertLinkList(LinkList *linkList, Element val) {if (!linkList) {printf("LinkList is null. Insert error!\n");return -1;}ListNode *newNode = (ListNode *) malloc(sizeof(ListNode));if (newNode == NULL) {printf("Memory allocation failed.\n");return -1;}newNode->value = val;newNode->next = NULL; // 初始化新节点的 next 指针为空if (linkList->head.next == NULL) { // 链表为空,直接将新节点插入到头节点linkList->head.next = newNode;} else {ListNode *node = linkList->head.next;while (node->next != NULL) { // 找到最后一个节点node = node->next;}node->next = newNode; // 将最后一个节点的 next 指向新节点}linkList->len++;return 0;
}

测试代码:

1.insertLinkList

int main(){LinkList *linkList= createLinkList();for (int i = 0; i < 5; i++){headInsertLinkList(linkList, i+100);}showLinkList(linkList);insertLinkList(linkList, 5, 888);showLinkList(linkList);insertLinkList(linkList, 2, 888);showLinkList(linkList);insertLinkList(linkList, 1, 888);showLinkList(linkList);insertLinkList(linkList,999, 888);showLinkList(linkList);releaseLinkList2(linkList);return 0;
}

 2.headInsertLinkList 

int main(){LinkList *linkList= createLinkList();for (int i = 0; i < 5; i++){headInsertLinkList(linkList, i+100);}showLinkList(linkList);releaseLinkList2(linkList);return 0;
}

 3.tailInsertLinkList

int main(){LinkList *linkList= createLinkList();for (int i = 0; i < 5; i++){tailInsertLinkList(linkList, i+100);}showLinkList(linkList);releaseLinkList2(linkList);return 0;
}

 

 

测试结果:

1.insertLinkList

 2.headInsertLinkList 

 3.tailInsertLinkList

3.4 void showLinkList(LinkList *linkList);

函数定义:
void showLinkList(LinkList *linkList);              // 显示链表内的元素

 

函数功能:
将链表中的所有元素依次打印到控制台
实现思路:
a.判断传入链表是否为空b.定义 node 为头节点后的第一个节点c.遍历链表,同时打印node->val,直到链表为空
具体实现代码:
void showLinkList(LinkList *linkList) {if (!linkList){printf("LinkList is null. Show error!\n");return;}ListNode *node = linkList->head.next;while (node){printf("%d ", node->value);node = node->next;}printf("\n");
}

 

测试代码:
int main(){LinkList *linkList= createLinkList();for (int i = 0; i < 5; i++){headInsertLinkList(linkList, i+100);}showLinkList(linkList);releaseLinkList2(linkList);return 0;
}

 

测试结果:
 

3.5 deleteLinkList函数的实现

函数定义:
int deleteLinkList(LinkList *linkList, Element val)
函数功能:
从链表中删除第一个值为 val 的节点
实现思路:
a.判断传入的链表是否为空b.用循环找到待删除节点的 前一个 结点c.判断链表的下一个结点是否为空(逻辑完备性)d.用一个指针 tmp 指向待删除的结点e.将 node->next(待删除结点的前一个结点) 设为 tmp->next(删除结点的下一个结点)f.释放掉 tmp 的空间g.更新链表长度,LinkList->len--
具体实现代码:
int deleteLinkList(LinkList *linkList, Element val) {if (!linkList) {printf("LinkList is null. Delete error!\n");return -1;}ListNode *node = &linkList->head;while (node->next && node->next->value != val){node = node->next;}if (!node->next){printf("Find element failed! Delete failed!\n");return -1;}ListNode *tmp = node->next;        // 注意顺序node->next = tmp->next;free(tmp);linkList->len--;return 0;
}
测试代码:
int main(){LinkList *linkList= createLinkList();for (int i = 0; i < 5; i++){headInsertLinkList(linkList, i+100);}showLinkList(linkList);deleteLinkList(linkList, 104);showLinkList(linkList);deleteLinkList(linkList, 100);showLinkList(linkList);deleteLinkList(linkList, 104);showLinkList(linkList);deleteLinkList(linkList, 102);showLinkList(linkList);releaseLinkList2(linkList);return 0;
}
测试结果:

3.6 reverseLinklist函数的实现

函数定义:
void reverseLinklist(LinkList *linkList);
函数功能:
将给定的单链表反转
实现思路:

方法一:

a.判断当前链表是否为空b.初始化 
cur = LinkList->head.next            (头节点的下一个)
pre = NULL
LinkList->head.next = NULL           (注意cur一定要在之前就要赋值)c.循环
备份当前结点                          (pre = cur)
移动cur避免丢失                       (cur = cur->next)
将pre->next = LinkList->head.next    (不断往头节点后插入结点)
将 LinkList->head.next = pre         (将头节点指向正确的位置)

方法二:

a.判断当前链表是否为空b.初始化 
cur = LinkList->head.next             (头节点的下一个)
nxt = NULL
LinkList->head.next = NULL            (注意cur一定要在之前就要赋值)c.循环
备份下一个结点                         (nxt = cur->next)
将 cur->next = LinkList->head.next    (不断往头节点后插入结点)
将 LinkList->head.next = cur          (将头节点指向正确的位置)
cur = nxt                             (避免结点丢失)

方法三:

a. 将链表所有的结点全部压入到栈中(除了头节点)
b. 利用栈先进后出的特性,出栈一个元素,头节点就连接一个元素
c. 注意使用 尾插法 来连接元素

具体实现代码:

方法一(备份当前节点,反转当前结点):

void reverseLinklist1(LinkList *linkList) {if (!linkList) {printf("LinkList is null. Delete error!\n");return;}ListNode *cur = linkList->head.next;ListNode *pre = NULL;linkList->head.next = NULL;while(cur){pre = cur;cur = cur->next;pre->next = linkList->head.next;linkList->head.next = pre;}printf("Reverse success!\n");
}

 

方法二(备份下一个结点,反转当前结点):

void reverseLinklist2(LinkList *linkList) {if (!linkList) {printf("LinkList is null. Delete error!\n");return;}ListNode *cur = linkList->head.next;ListNode *nxt = NULL;linkList->head.next = NULL;while (cur){nxt = cur->next;cur->next = linkList->head.next;linkList->head.next = cur;cur = nxt;}printf("Reverse success!\n");
}

方法三:

这里在栈的部分写,到时候放一个链接

测试代码:

方法一:

int main(){LinkList *linkList= createLinkList();for (int i = 0; i < 5; i++){headInsertLinkList(linkList, i+100);}showLinkList(linkList);reverseLinklist1(linkList);releaseLinkList2(linkList);return 0;
}

方法二:

int main(){LinkList *linkList= createLinkList();for (int i = 0; i < 5; i++){headInsertLinkList(linkList, i+100);}showLinkList(linkList);reverseLinklist2(linkList);releaseLinkList2(linkList);return 0;
}

测试结果:

方法一:


 

方法二:

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

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

相关文章

十六、【机器学习】【监督学习】- 支持向量回归 (SVR)

系列文章目录 第一章 【机器学习】初识机器学习 第二章 【机器学习】【监督学习】- 逻辑回归算法 (Logistic Regression) 第三章 【机器学习】【监督学习】- 支持向量机 (SVM) 第四章【机器学习】【监督学习】- K-近邻算法 (K-NN) 第五章【机器学习】【监督学习】- 决策树…

GaussDB常见调优指南

文章目录 GaussDB常见调优指南一. Analyze 统计信息解析二. Explain 分布式计划解析三. 性能调优总体策略详解四. 性能调优之坏味道 SQL 识别五. 性能调优之好味道表定义六. 性能调优之 SQL 改写七. 性能调优之路径干预八. 性能调优之 Plan hint 运用九. 性能调优之 GUC 参数调…

C学习(数据结构)-->单链表习题

目录 一、环形链表 题一&#xff1a;环形链表 思路&#xff1a; 思考一&#xff1a;为什么&#xff1f; 思考二&#xff1a;快指针一次走3步、4步、......n步&#xff0c;能否相遇 step1&#xff1a; step2&#xff1a; 代码&#xff1a; 题二&#xff1a; 环形链表 I…

SAE J1939协议入门(一)

一、SAE J1939是什么 SAE J1939&#xff08;以下简称J1939&#xff09;是由汽车工程师协会&#xff08;SAE &#xff09;定义的标准&#xff0c;专门用于提供微处理器系统之间的串行数据通信。虽然CAN存在并且被广泛用于小型车辆&#xff0c;但J1939被设计为大型车辆复杂网络的…

深度挖掘行情接口:股票市场中的关键金融数据API接口解析

在股票市场里&#xff0c;存在若干常见的股票行情数据接口&#xff0c;每一种接口皆具备独特的功能与用途。以下为一些常见的金融数据 API 接口&#xff0c;其涵盖了广泛的金融数据内容&#xff0c;其中就包含股票行情数据&#xff1a; 实时行情接口 实时行情接口&#xff1a…

恒创科技:如何解决“服务器 IP 地址无法被找到”的错误

如何解决“服务器 IP 地址无法被找到”的错误?此错误通常出现在你的设备无法使用其分配的 IP 地址与网络服务器通信时。问题的来源可能多种多样&#xff0c;从简单的拼写错误到复杂的 DNS 和路由问题。以下是对“服务器 IP 地址无法找到”的常见原因以及可以采取的解决办法。 …

万界星空科技MES系统生产计划管理的功能

MES系统&#xff08;Manufacturing Execution System&#xff0c;制造执行系统&#xff09;的生产计划管理功能是其核心功能之一&#xff0c;旨在将企业的生产计划转化为实际的生产操作&#xff0c;并通过实时监控和调整来确保生产活动的顺利进行。以下是MES系统生产计划管理功…

SpringData JPA Mongodb 查询部分字段

JPA 网上用的好像不多&#xff0c;找了好多材料以后最终找了这个可行的方案&#xff1a; Query(fields "{tender_id:1,_id:0}")List<MGPltTender> findByTenderIdIsNotNull(PageRequest pageRequest); 调用&#xff1a; Sort sort Sort.by(popularType.getC…

【二维区域和检索-矩阵不可变】python刷题记录

一夜无解&#xff0c;痛苦&#xff0c;遂拜倒于灵神门下&#xff0c;妄做狂徒也&#xff01; . - 力扣&#xff08;LeetCode&#xff09; 灵神秒解如下&#xff1a; class NumMatrix:#二维初始矩阵def __init__(self, matrix: List[List[int]]):mlen(matrix)nlen(matrix[0])#…

vue和微信小程序的区别、比较

找到一篇很好的关于vue和小程序之间的理解文章&#xff0c;在此分享一下&#xff1a; 前端 - vue和微信小程序的区别、比较 - 个人文章 - SegmentFault 思否https://segmentfault.com/a/1190000015684864

单片机程序设计模式

RTOS:多任务拆分交叉执行 Q:状态机和多任务模式有什么区别 Q:任务创建和任务调度器是什么&#xff1f; 裸机程序的设计模式可以分为&#xff1a;轮询、前后台、定时器驱动、基于状态机。前面三种方 法都无法解决一个问题&#xff1a;假设有 A、B 两个都很耗时的函数&#xf…

从PyTorch官方的一篇教程说开去(2 - 源码)

先上图&#xff0c;上篇文章的运行结果&#xff0c;可以看到&#xff0c;算法在迭代了200来次左右达到人生巅峰&#xff0c;倒立摆金枪不倒&#xff0c;可以扛住连续200次操作。不幸的是&#xff0c;然后就出现了大幅度的回撤&#xff0c;每况愈下&#xff0c;在600次时候居然和…

JVM知识点总结(全网最详细)!!!!

JVM知识总结 运行时数据区域程序计数器Java虚拟机栈局部变量表 StackOverflowError异常和OutOfMemoryError异常本地方法栈Java堆方法区运行时常量池 对象的创建对象的内存分配对象的内存布局对象头实例数据对齐填充 对象的访问定位使用句柄直接指针使用句柄和直接指针的优缺点 …

PHP房产中介租房卖房平台微信小程序系统源码

​&#x1f3e0;【租房卖房新选择】揭秘房产中介小程序&#xff0c;一键搞定置业大事&#xff01;&#x1f3e1; &#x1f50d;【开篇&#xff1a;告别繁琐&#xff0c;拥抱便捷】&#x1f50d; 还在为找房子跑断腿&#xff1f;为卖房发愁吗&#xff1f;今天给大家安利一个超…

【.NET全栈】ASP.NET开发Web应用——AJAX开发技术

文章目录 前言一、ASP.NET AJAX基础1、AJAX技术简介2、ASP.NET AJAX技术架构 二、ASP.NET AJAX服务器端扩展1、声明ScriptManager控件2、使用ScriptManager分发自定义脚本3、在ScriptManager中注册Web服务4、处理ScriptManager中的异常5、编程控制ScriptManager控件6、使用Upda…

如何高效定制视频扩散模型?卡内基梅隆提出VADER:通过奖励梯度进行视频扩散对齐

论文链接&#xff1a;https://arxiv.org/pdf/2407.08737 git链接&#xff1a;https://vader-vid.github.io/ 亮点直击&#xff1a; 引入奖励模型梯度对齐方法&#xff1a;VADER通过利用奖励模型的梯度&#xff0c;对多种视频扩散模型进行调整和对齐&#xff0c;包括文本到视频和…

如何评估 5G 毫米波相控阵天线模块

5G 新无线电 (5G NR) 是空中接口或无线接入网络 (RAN) 技术的行业标准和全球规范。它涵盖 6 GHz 及以下频率&#xff08;称为 FR1&#xff09;和 24 GHz 至 50 GHz 或更高频段&#xff08;称为 FR2 或 mmWave&#xff09;的运行。该技术可用于固定或移动接入、回程和日益流行的…

一些简单的基本知识(与C基本一致)

一、注释 1.单行注释&#xff1a;//&#xff08;快捷键&#xff1a;ctrlshift&#xff1f;&#xff0c;可以选择多行&#xff09; 2.多行注释&#xff1a;/* 文本 */ 二、变量 变量的作用是给一段内存空间起名&#xff0c;方便操作内存中的数据。 通过赋予某数据的…

逆向案例二十五——webpack所需模块函数很多,某翼云登录参数逆向。

解决步骤&#xff1a; 网址&#xff1a;aHR0cHM6Ly9tLmN0eXVuLmNuL3dhcC9tYWluL2F1dGgvbG9naW4 不说废话&#xff0c;密码有加密&#xff0c;直接搜索找到疑似加密位置打上断点。 再控制台打印&#xff0c;分析加密函数 有三个处理过程&#xff0c;b[g]得到的是用户名,b[f] 对…

数据结构初阶·排序算法(内排序)

目录 前言&#xff1a; 1 冒泡排序 2 选择排序 3 插入排序 4 希尔排序 5 快速排序 5.1 Hoare版本 5.2 挖坑法 5.3 前后指针法 5.4 非递归快排 6 归并排序 6.1递归版本归并 6.2 非递归版本归并 7 计数排序 8 排序总结 前言&#xff1a; 目前常见的排序算法有9种…