单链表的实现(数据结构)

单链表的实现(数据结构)

一. 单链表的实现

我们在上一篇中简单的认识了链表的组成和结构,并打印出链表,那么今天就来具体实现一下单链表对于数据增加、删减、插入等。

接下来就是我们在链表中对于数据的增、删、插的实现,对于我们的链表来说在任何地方增加数据都需要来申请一个新的结点,首先呢,SLDatatype是我们更改int类型的名称之后得来的,SL是结构体的名称,我们先申请一个结构体大小的新结点node,然后再将新结点中的x的值赋给data,再让新结点的next指向NULL,申请新结点的函数写好之后我们就来写关于数据的增、删、插等,所以我们直接先来完成申请新结点的代码:

SLDatatype* SLBuyNode(SLDatatype x) { SL* node = (SL*)malloc(sizeof(SL)); if (node == NULL) { perror("malloc fail!"); exit(1); } node->data = x; node->next = NULL; return node; }

 尾插

//尾插函数 void SLPuchBack(SL** pphead, SLDatatype x) { //申请新结点 //*pphead-->&plist SL* newnode = SLBuyNode(x); if (*pphead == NULL) { *pphead = newnode; } else { //尾结点-->新结点 SL* pcur = *pphead;//找尾结点 while (pcur->next) { pcur = pcur->next; } pcur->next = newnode; } } void SLTest01() { SL* plist = NULL; SLPuchBack(&plist, 1); SLprintf(plist); SLPuchBack(&plist, 2); SLPuchBack(&plist, 3); SLprintf(plist); } int main() { SLTest01(); return 0; } 

在实现让任何代码之前,我们都因该将思路理清楚,尾插该注意什么,怎么去是实现?首先一定是要找到最后一个结点,pphead是我们的头结点,我们一贯会将pphead赋给一个新的指针pcur,使用while循环找到最后一个结点,但是如果while里面是pcur的那我们不就直接跳出循环,那我们还怎么找最后一个结点呢?所以我们不如使用while(pcur->next),也就是当我们pcur指向3的时候我们的next刚好指向的是NULL,停止了循环,刚好pcur还指向尾结点,然后我们在将newnode赋值给我们的最后一个结点。在上面的代码中我们要注意的一点是SLPuchBack函数中传入的是形参,这个时候我们要使用传值调用,因为如果使用传值调用的话,形参的改变并不影响实参,所以在测试函数SLTest01中传入的是&plist,然而plist本身就是一个指针,所以我们在SLPuchBack中要使用二级指针即**pphead。

 头插:对于头插就简单很多,我们只需要申请一个新的结点,然后将结点与原本的头结点连接,在让pphead指向现在新的结点。

//头插函数 void SLPuchFront(SL** pphead, SLDatatype x) { assert(pphead); SL* newnode = SLBuyNode(x); newnode->next = *pphead; *pphead = newnode; } 

尾删:对于尾删我们并不能简单的将最后一个结点删除,因为这样会让前面一个指针变成野指针,可以把最后一个结点前面的结点的next指向NULL,然后将最后一个结点释放掉。

//尾删函数 void SLPopBack(SL** pphead) { assert(pphead && *pphead); //假如原本只有一个结点 if ((*pphead)->next == NULL) { free(*pphead); *pphead = NULL; } else { SL* pcur = *pphead; SL* front = NULL; while (pcur->next) { front = pcur; pcur = pcur->next; } front->next = NULL; free(pcur); pcur = NULL; }

头删: 

//头删函数 void SLPopFront(SL** pphead) { assert(pphead && *pphead); SL* pcur = (*pphead)->next; free(*pphead); *pphead = pcur; }

 在指定位置之前插入数据

void SLInsertFront(SL** pphead, SL* pos, SLDatatype x) { assert(pphead && pos); SL* newnode = SLBuyNode(x); SL* pcur = *pphead; if (pos == *pphead) { newnode->next = pos; *pphead = newnode; } else { while (pcur->next != pos) { pcur = pcur->next; } newnode->next = pos; pcur->next = newnode; } }

 在指定位置之后插入数据

void SLInsertAfert(SL* pos, SLDatatype x) { assert(pos); SL* newnode = SLBuyNode(x); newnode->next = pos->next; pos->next = newnode; }

 删除pos结点

void SLErase(SL** pphead, SL* pos) { assert(pphead && *pphead); assert(pos); SL* pcur = *pphead; if (pos == *pphead) { SLPopFront(pphead); } else { while (pcur->next != pos) { pcur = pcur->next; } pcur->next = pos->next; free(pos); pos = NULL; } 

 删除pos之后的结点

void SLEraseAfert(SL* pos) { assert(pos&&pos->next); SL* deal = pos->next; pos->next = pos->next->next; free(deal); deal = NULL; }

 销毁链表

void SLDestroy(SL** pphead) { SL* pcur = *pphead; while (pcur) { SL* next = pcur->next; free(pcur); pcur = next; } *pphead = NULL; }

上面关于链表的指定位置插入、删除等算法,我们要注意的就是在我们更改链表中的数据的时候,要更改的这个数据会影响其他的数据吗?如果影响我们应该怎么做,提前设置好几个指针变量还是其他的办法,寻找某一个结点的时候while循环的条件是什么,这些我们都要注意,上面已经给出较为详细的代码,大家可以参考,另外就是注意free指针之后一定要将其置为空指针NULL,单链表完成之后我们就要拿一些算法题来练手,如何使用单链表来完成一些算法题。

二. 算法题

移除链表元素

https://leetcode.cn/problems/remove-linked-list-elements/description/

这个题目是移除链表元素,我们有两个思路一个就是利用我们原本的单链表对指定位置的结点进行删除,还有就是创建一个新的链表然后将符合的元素移到新的链表中,我们可以来实现第二中思路: 创建一个新链表,遍历原链表,将不等于给定值  val  的节点依次添加到新链表中。
 
1. 创建新链表的哑节点(dummy node):
- 目的是简化操作,避免处理新链表头节点为空的特殊情况。
- 例如,可以创建一个值为  0  的哑节点,将其  next  指针初始化为  null 。
2. 遍历原链表:
- 从原链表的头节点  head  开始遍历。
- 检查当前节点的值是否等于  val 。
- 如果不等于  val ,则将该节点添加到新链表中。
3. 添加节点到新链表:
- 将原链表中不等于  val  的节点的  next  指针指向新链表的末尾节点的  next 。
- 更新新链表的末尾节点为该节点。
4. 返回新链表的头节点:
 - 新链表的头节点为哑节点的  next 。
 通过以上步骤,就可以使用创建新链表的方法删除原链表中所有值为  val  的节点,并返回新的头节点。

 

#include <stdio.h> #include <stdlib.h> struct ListNode { int val; struct ListNode* next; }; struct ListNode* createNode(int val) { struct ListNode* newNode = (struct ListNode*)malloc(sizeof(struct ListNode)); newNode->val = val; newNode->next = NULL; return newNode; } struct ListNode* removeElements(struct ListNode* head, int val) { struct ListNode* dummy = createNode(0); struct ListNode* curr = dummy; while (head) { if (head->val!= val) { curr->next = head; curr = curr->next; } head = head->next; } curr->next = NULL; return dummy->next; } void printList(struct ListNode* head) { while (head) { printf("%d -> ", head->val); head = head->next; } printf("NULL\n"); } void freeList(struct ListNode* head) { struct ListNode* temp; while (head) { temp = head; head = head->next; free(temp); } } int main() { // 创建链表 1->2->6->3->4->5->6 struct ListNode* head = createNode(1); head->next = createNode(2); head->next->next = createNode(6); head->next->next->next = createNode(3); head->next->next->next->next = createNode(4); head->next->next->next->next->next = createNode(5); head->next->next->next->next->next->next = createNode(6); int val = 6; struct ListNode* newHead = removeElements(head, val); printList(newHead); freeList(newHead); return 0; }

Read more

【优选算法 | 滑动窗口】滑动窗口算法:高效处理子数组和子串问题

【优选算法 | 滑动窗口】滑动窗口算法:高效处理子数组和子串问题

算法相关知识点可以通过点击以下链接进行学习一起加油!双指针 在本篇文章中,我们将深入剖析滑动窗口算法的核心原理。从基础概念到实战应用,带你了解如何利用滑动窗口高效解决连续子数组和子串等问题。无论你是算法入门的新手,还是希望提升代码效率的高手,滑动窗口都将成为你优化算法的重要武器! 🌈个人主页:是店小二呀 🌈C/C++专栏:C语言\ C++ 🌈初/高阶数据结构专栏: 初阶数据结构\ 高阶数据结构 🌈Linux专栏: Linux 🌈算法专栏:算法 🌈Mysql专栏:Mysql 🌈你可知:无人扶我青云志 我自踏雪至山巅 文章目录 * 209.长度最小的子数组 * 3.无重复字符的最长子串 * 1004.最大连续1的个数 ||| * 1658.将 x 减到0的最小操作数 * 904.水果成篮 * 438. 找到字符串中所有字母异位词 * 30. 串联所有单词的子串 * 76.最小覆盖子串 209.长度最小的子数组

By Ne0inhk
YOLO11算法深度解析:四大工业场景实战,开源数据集助力AI质检落地

YOLO11算法深度解析:四大工业场景实战,开源数据集助力AI质检落地

在工业智造的时代浪潮中,产品质量是企业立足之本。传统缺陷检测依赖人工,效率低、易漏检,成为制约产线自动化升级的瓶颈。如今,随着Ultralytics YOLO11的发布,工业质检正式迈入高精度、高速度、高适应性的AI新阶段。 一、YOLO11算法深度解析:为何如此适配工业缺陷检测? 算法架构与工业需求的完美契合 小目标检测能力突破 工业缺陷通常尺寸微小、特征不明显。YOLO11通过以下创新解决这一痛点: * 多尺度特征金字塔优化:在特征提取阶段实现深浅层特征的高效融合 * 自适应感受野设计:自动调整卷积核感受野,捕获不同尺度缺陷特征 * 细粒度特征增强模块:专门针对微小缺陷的特征提取进行强化 复杂环境鲁棒性设计 工业现场光照变化、粉尘干扰、设备振动等问题普遍存在: * 数据增强策略优化:专门针对工业场景的光照变化、模糊、噪声等进行增强 * 注意力机制融合:EMA、CBAM等注意力模块的集成,提升模型抗干扰能力 * 动态阈值调整:根据环境变化自动调整检测阈值,保持稳定检出率 实时性能与精度平衡 高速生产线要求毫秒级响应: * 轻量化Backbo

By Ne0inhk
Hadoop 核心组件详解:HDFS、YARN、MapReduce 如何各司其职?

Hadoop 核心组件详解:HDFS、YARN、MapReduce 如何各司其职?

Hadoop 核心组件详解:HDFS、YARN、MapReduce 如何各司其职? * 一、Hadoop 核心组件全景图 * 二、HDFS:分布式文件系统 * 1. 架构角色 * 2. 读写流程(流程图) * HDFS 写数据流程 * HDFS 读数据流程 * 三、YARN:资源调度与管理系统 * 1. 架构角色 * 2. YARN 的任务提交流程 * 四、MapReduce:分布式计算框架 * 1. 核心思想 * 2. 经典 WordCount 代码示例 * 3. MapReduce 完整执行流程 * 五、总结:三驾马车的协同 🌺The Begin🌺点点关注,收藏不迷路🌺 大数据时代,面对海量数据的存储与计算,

By Ne0inhk
Flutter 组件 easter 适配鸿蒙 HarmonyOS 实战:天文学节气算法,构建全球化复活节周期与民俗历法治理架构

Flutter 组件 easter 适配鸿蒙 HarmonyOS 实战:天文学节气算法,构建全球化复活节周期与民俗历法治理架构

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter 组件 easter 适配鸿蒙 HarmonyOS 实战:天文学节气算法,构建全球化复活节周期与民俗历法治理架构 前言 在鸿蒙(OpenHarmony)生态迈向全球化部署、涉及跨区域文化适配(I18n)及复杂变动日期计算的背景下,如何精确推演具备“阴阳历混合特性”的全球性节日(如复活节),已成为决定跨国类应用“运营确定性”的核心技术难点。在鸿蒙设备这类强调 AOT 极致性能与低功耗常驻服务(AOD)的环境下,如果应用依然依赖手动配置的“节日死表”,由于由于复活节日期在全球范围内的复杂游移性,极易由于由于配置滞后导致海外营销活动的时序错乱。 我们需要一种能够实现高精度天文学推演、支持百年尺度计算且具备纯 Dart 离线运作能力的历法预判方案。 easter 为 Flutter 开发者引入了基于高斯算法(Gauss's algorithm)或曼氏算法(Meeus&

By Ne0inhk