【数据结构】手撕单链表
目录
一,链表的概念及结构
二,接口实现
1,单链表的创建
2,接口函数
3,动态创立新结点
4,打印
5,头插
6,头删
7,尾插
8,尾删
9,查找
10,单链表在pos位置之后插入x
11,单链表删除pos位置之后的值
12,销毁
三,源代码
LKList.h
LKList.c
四,总结
一,链表的概念及结构
链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的
而在数据结构中:
注意:
1,从上图可以看出,链式结构在逻辑上是连续的,但是在物理上不一定连续
2,现实中的结点一般都是从堆上申请出来的
3,从堆上申请的空间,是按照一定的策略来分配的,两次申请的空间可能连续,也可能不连续;
实际中链表的结构非常多样,今天我们来写一下单链表,此表一会其他的自然水到渠成!
二,接口实现
1,单链表的创建
//无头 + 单向 + 非循环链表增删查改实现
typedef int LKLDataType;
typedef struct LinKedListNode
{LKLDataType data;struct LinKedListNode* next;
}LKLNode;
首先创建一个结构体表示单链表,data是存储的值,LKLDataType是储存的值的数据类型,next是结点----指向下一个;
这里的LKLDataType是int的重命名,也可以说是数据类型的重命名,这样统一化方便后续更改;
2,接口函数
// 动态创立新结点
LKLNode* BuyLKLNode(LKLDataType x);
// 打印
void LKLPrint(LKLNode* phead);
// 头插
void LKLNodePushFront(LKLNode** phead, LKLDataType x);
// 头删
void LKLNodeBackFront(LKLNode** phead);
// 尾插
void LKLNodePushBack(LKLNode** phead, LKLDataType x);
// 尾删
void LKLNodePopBack(LKLNode** phead);
// 查找
LKLNode* LKLFind(LKLNode* phead, LKLDataType x);
// 单链表在pos位置之后插入x
void LKLInsertAfter(LKLNode* pos, LKLDataType x);
// 单链表删除pos位置之后的值
void LKLEraseAfter(LKLNode* pos);
// 销毁
void LKLDestroy(LKLNode** plist);
这是以上要实现的接口函数;
3,动态创立新结点
//动态创立新结点
LKLNode* BuyLKLNode(LKLDataType x)
{LKLNode* newnode = (LKLNode*)malloc(sizeof(LKLNode));newnode->data = x;newnode->next = NULL;return newnode;
}
后面创立新节点时直接调用此函数,一定要向堆区申请空间,这样函数结束空间会保留不会被回收;
4,打印
//打印
void LKLPrint(LKLNode* phead)
{assert(phead);LKLNode* cur = phead;while (cur){printf("%d->", cur->data);cur = cur->next;}printf("NULL");
}
打印也就是打印data的值,用cur=phead然后每次打印完都让cur走向下一个直到为空结束;
5,头插
//头插
void LKLNodePushFront(LKLNode** phead, LKLDataType x)
{assert(phead);LKLNode* newnode = BuyLKLNode(x);newnode->next = *phead;*phead = newnode;
}
先断言一下,既然插入数据那就要申请一个新节点,然后令新节点的next指向phead然后再令phead指向新节点;
6,头删
//头删
void LKLNodeBackFront(LKLNode** phead)
{assert(phead);//为空assert(*phead);//非空LKLNode* newnode = (*phead)->next;free(*phead);*phead = newnode;
}
还是先断言,有人会问为什么要断言两次?其实很好判断,哪个需要解引用那个就需要断言;
令一个变量newnode等于头结点的下一个,在释放头结点,在令头结点指向newnode即可;
7,尾插
//尾插
void LKLNodePushBack(LKLNode** phead, LKLDataType x)
{assert(phead);assert(*phead);LKLNode* newnode= BuyLKLNode(x);LKLNode* cur = *phead;//为空if (*phead == NULL){*phead = newnode;}//非空else{while (cur->next){cur = cur->next;}cur->next = newnode;}
}
还是先断言判断,然后要插入一个新的数据先申请一个新结点,如果头结点为空则直接让头结点指向新结点即可,如果头结点不为空,则需要找到next为空的结点,这里用一个循环搞定,然后再直接让next为空的结点指向新节点即可;
8,尾删
//尾删
void LKLNodePopBack(LKLNode** phead)
{assert(phead);//为空assert(*phead);//一个if ((*phead)->next==NULL){free(*phead);*phead = NULL;}//两个及以上else{LKLNode* tail = *phead;/*LKLNode* prev = NULL;while (tail->next){prev = tail;tail = tail->next;}free(prev->next);prev->next = NULL;*/while (tail->next->next){tail = tail->next;}free(tail->next);tail->next = NULL;}
}
还是先断言一下,然后这里有两种情况,链表只有一个结点,两个以上结点的时候;
当链表只有一个结点也就是头结点,直接头删即可;
两个以上结点的时候,我这里有两种解决方案;
方案一常规法:先用循环找到next为空的结点,并且在循环里保留上一个结点prev,然后释放next为空的结点再让prev的next指向空即可;
方案二:不需要标记上一个结点,直接原地判断,判断结点的next的next是否为空,否则继续向后推进,是则释放结点的next然后再令自己的next指向空也就相当于变成了尾结点;
9,查找
// 单链表查找
LKLNode* LKLFind(LKLNode* phead, LKLDataType x)
{assert(phead);LKLNode* pos = phead;while (pos){if (pos->data == x){return pos;}pos = pos->next;}return NULL;
}
老样子先断言一下,然后直接用循环遍历链表找到data是x的值然后返回此结点即可;
10,单链表在pos位置之后插入x
// 单链表在pos位置之后插入x
void LKLInsertAfter(LKLNode* pos, LKLDataType x)
{assert(pos);LKLNode* newnode= BuyLKLNode(x);LKLNode* cur = pos;newnode->next = cur->next;cur->next = newnode;
}
先断言,要插入数据先申请一个新结点,然后令新结点的next指向pos的next,再返回来让pos的next指向新结点;
11,单链表删除pos位置之后的值
// 单链表删除pos位置之后的值
void LKLEraseAfter(LKLNode* pos)
{assert(pos);assert(pos->next);LKLNode* cur = pos;LKLNode* newnode = cur->next->next;free(cur->next);cur->next = newnode;
}
要删除值首先要确保得有值,所以开始断言;
先定义一个变量newnode指向pos的next的next,然后再释放pos的next,再令pos指向newnode以达到删除之后的效果;
12,销毁
// 单链表的销毁
void LKLDestroy(LKLNode** phead)
{assert(phead);assert(*phead);LKLNode* cur = *phead;LKLNode* next = NULL;while (cur){next = cur->next;free(cur);cur = next;}
}
老样子那个需要解引用那个就先断言一下,然后用循环遍历,先标记下一个结点,然后释放自己,再让自己指向标记的结点直到为空结束;
三,源代码
LKList.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>//无头 + 单向 + 非循环链表增删查改实现
typedef int LKLDataType;
typedef struct LinKedListNode
{LKLDataType data;struct LinKedListNode* next;
}LKLNode;// 动态创立新结点
LKLNode* BuyLKLNode(LKLDataType x);
// 打印
void LKLPrint(LKLNode* phead);
// 头插
void LKLNodePushFront(LKLNode** phead, LKLDataType x);
// 头删
void LKLNodeBackFront(LKLNode** phead);
// 尾插
void LKLNodePushBack(LKLNode** phead, LKLDataType x);
// 尾删
void LKLNodePopBack(LKLNode** phead);
// 查找
LKLNode* LKLFind(LKLNode* phead, LKLDataType x);
// 单链表在pos位置之后插入x
void LKLInsertAfter(LKLNode* pos, LKLDataType x);
// 单链表删除pos位置之后的值
void LKLEraseAfter(LKLNode* pos);
// 销毁
void LKLDestroy(LKLNode** plist);
LKList.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"LKList.h"//动态创立新结点
LKLNode* BuyLKLNode(LKLDataType x)
{LKLNode* newnode = (LKLNode*)malloc(sizeof(LKLNode));newnode->data = x;newnode->next = NULL;return newnode;
}
//头插
void LKLNodePushFront(LKLNode** phead, LKLDataType x)
{assert(phead);LKLNode* newnode = BuyLKLNode(x);newnode->next = *phead;*phead = newnode;
}
//头删
void LKLNodeBackFront(LKLNode** phead)
{assert(phead);//为空assert(*phead);//非空LKLNode* newnode = (*phead)->next;free(*phead);*phead = newnode;
}
//尾插
void LKLNodePushBack(LKLNode** phead, LKLDataType x)
{assert(phead);assert(*phead);LKLNode* newnode= BuyLKLNode(x);LKLNode* cur = *phead;//为空if (*phead == NULL){*phead = newnode;}//非空else{while (cur->next){cur = cur->next;}cur->next = newnode;}
}
//尾删
void LKLNodePopBack(LKLNode** phead)
{assert(phead);//为空assert(*phead);//一个if ((*phead)->next==NULL){free(*phead);*phead = NULL;}//两个及以上else{LKLNode* tail = *phead;/*LKLNode* prev = NULL;while (tail->next){prev = tail;tail = tail->next;}free(prev->next);prev->next = NULL;*/while (tail->next->next){tail = tail->next;}free(tail->next);tail->next = NULL;}
}
// 单链表查找
LKLNode* LKLFind(LKLNode* phead, LKLDataType x)
{assert(phead);LKLNode* pos = phead;while (pos){if (pos->data == x){return pos;}pos = pos->next;}return NULL;
}
// 单链表在pos位置之后插入x
void LKLInsertAfter(LKLNode* pos, LKLDataType x)
{assert(pos);LKLNode* newnode= BuyLKLNode(x);LKLNode* cur = pos;newnode->next = cur->next;cur->next = newnode;
}
// 单链表删除pos位置之后的值
void LKLEraseAfter(LKLNode* pos)
{assert(pos);assert(pos->next);LKLNode* cur = pos;LKLNode* newnode = cur->next->next;free(cur->next);cur->next = newnode;
}// 单链表的销毁
void LKLDestroy(LKLNode** phead)
{assert(phead);assert(*phead);LKLNode* cur = *phead;LKLNode* next = NULL;while (cur){next = cur->next;free(cur);cur = next;}
}
//打印
void LKLPrint(LKLNode* phead)
{assert(phead);LKLNode* cur = phead;while (cur){printf("%d->", cur->data);cur = cur->next;}printf("NULL");
}
四,总结
做数据结构的题目画图很重要,小伙伴们刚开始喜欢用脑子去构图想象,但遇到复杂的情况会紊乱的,画图最为可观方便,可以培养一个良好的画图习惯;
如有不足之处欢迎来补充交流!
完结。。。
相关文章:

【数据结构】手撕单链表
目录 一,链表的概念及结构 二,接口实现 1,单链表的创建 2,接口函数 3,动态创立新结点 4,打印 5,头插 6,头删 7,尾插 8,尾删 9,查找 10ÿ…...

两个git本地如何配置两个ssh密钥for mac
我是在mac上操作的。windows上也差不多一样操作。 1.找到本地的.ssh文件。我的文件结构如下如: 文件结构: (1)两个known_hosts文件是自动生成的,不用管 (2)readme文件是我个人记事本记录笔记…...

iOS逆向进阶:iOS进程间通信方案深入探究与local socket介绍
在移动应用开发中,进程间通信(Inter-Process Communication,IPC)是一项至关重要的技术,用于不同应用之间的协作和数据共享。在iOS生态系统中,进程和线程是基本的概念,而进程间通信方案则为应用的…...

qt day 1
this->setWindowIcon(QIcon("D:\\zhuomian\\wodepeizhenshi.png"));//設置窗口的iconthis->setWindowTitle("鵬哥快聊");//更改名字this->setFixedSize(500,400);//設置尺寸QLabel *qlnew QLabel(this);//創建一個標簽ql->resize(QSize(500,20…...
针对java中list.parallelStream()的多线程数据安全问题我们采用什么方法最好呢?
当使用List.parallelStream()方法进行多线程处理时,可能会涉及到数据安全问题。下面是一些常见的方法来处理parallelStream()的多线程数据安全问题: 1. 使用线程安全的集合:Java中提供了线程安全的集合类,如CopyOnWriteArrayList…...

校园用电安全管理系统可以识别违规电器吗
校园用电安全管理系统是处理恶意用电问题有效手段之一,系统具有实时监测、异常预警、监测设备运行状态、远程控制用电等功能,可以从根本上管理学校用电量,制定合理的用电计划,限制用电成本,避免各种恶意用电行为&#…...

前端(十五)——开源一个用react封装的图片预览组件
👵博主:小猫娃来啦 👵文章核心:开源一个react封装的图片预览组件 文章目录 组件开源代码下载地址运行效果展示实现思路使用思路和api实现的功能数据和入口部分代码展示 组件开源代码下载地址 Gitee:点此跳转下载 CSDN…...

idea新建Java-maven项目时,出现Dependency ‘ xxx(jar包名)‘ not found的解决方案
项目场景: 项目场景:使用idea创建maven项目时,导入简单依赖时(本文以mysql-connector-java为例)。 问题描述 问题: 首先,在创建新的maven项目中,出现下列两种情况: &am…...

C# 获取Windows系统版本注意事项
首先通过微软官方文档:https://learn.microsoft.com/zh-cn/windows/win32/sysinfo/operating-system-version了解各个操作系统对应的版本号 下面介绍3种获取版本号的方式及弊端 1. Environment.OSVersion.Version OperatingSystem os Environment.OSVersion;// 判断…...
STM32设计的宠物投喂器(正点原子mini开发板+2.8寸屏)
一、设计需求 【1】 项目背景 在竞争日益激烈的今天,各行各业为提高竞争力,纷纷推出了各种新、奇的事物来吸引消费者。经过长时间的市场调查,发现广大市民及民营企业家大多还采用传统的人工喂养方式,这种方式不但耗费了大量的人力资源,而且由于现在的人力成本的不断增加…...

Python编程——深入了解不可变的元组
作者:Insist-- 个人主页:insist--个人主页 本文专栏:Python专栏 专栏介绍:本专栏为免费专栏,并且会持续更新python基础知识,欢迎各位订阅关注。 目录 一、元组是什么 二、元组的定义 1、相同类型组成元组…...

JVM——类加载与字节码技术—类加载器+运行期优化
5.类加载器 jdk的类加载器具有层级关系。 启动类加载器》扩展类加载器》应用程序类加载器》自定义类加载器 对应类加载器只会负责加载对应目录的类。 双亲委派上级机制 应用程序类加载器加载一个类之前会先查询上级加载器是否已经加载过了该类。然后再让上级询问上上级。都…...

[linux实战] 华为云耀云服务器L实例 Java、node环境配置
系列文章目录 第一章 [linux实战] 华为云耀云服务器L实例 Java、node环境配置 文章目录 系列文章目录前言一、任务拆解二、修改密码三、配置安全规则四、远程登录并更新apt五、安装、配置JDK环境5.1、安装openjdk,选择8版本5.2、检查jdk配置 六、安装、配置git6.1、安装git6.2…...

python面试:使用cProfile剖析程序性能
我们需要安装tuna:pip install tuna 程序执行完毕后,我们会得到一个results.prof,在CMD中输入指令:“tuna results.prof”。 import time import cProfile import pstatsdef add(x, y):resulting_sum 0resulting_sum xresulti…...
leetcode-188-买卖股票的最佳时机 IV
1. 问题描述 https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-iv/description/ 2. 解题代码 public class Solution {public int MaxProfit(int k, int[] prices) {if(prices.Length<2){return 0;}if(k0){return 0;}List<int> listValuenew List<…...

Grounded Language-Image Pre-training论文笔记
Title:Grounded Language-Image Pre-training Code 文章目录 1. 背景2. 方法(1)Unified Formulation传统目标检测grounding目标检测 (2)Language-Aware Deep Fusion(3)Pre-training with Scala…...

成集云 | 钉钉财务费用单同步至畅捷通 | 解决方案
源系统成集云目标系统 方案介绍 财务管理作为企业管理中重要的组成部分,在企业的发展和成长中扮演着重要角色,成集云以钉钉费用单OA审批与畅捷通TCloud系统为例,与钉钉连接器深度融合,通过数据处理和字段匹配实现了费用…...
Redis——》死锁
推荐链接: 总结——》【Java】 总结——》【Mysql】 总结——》【Redis】 总结——》【Kafka】 总结——》【Spring】 总结——》【SpringBoot】 总结——》【MyBatis、MyBatis-Plus】 总结——》【Linux】 总结——》【MongoD…...

URL重定向漏洞
URL重定向漏洞 1. URL重定向1.1. 漏洞位置 2. URL重定向基础演示2.1. 查找漏洞2.1.1. 测试漏洞2.1.2. 加载完情况2.1.3. 验证漏洞2.1.4. 成功验证 2.2. 代码修改2.2.1. 用户端代码修改2.2.2. 攻击端代码修改 2.3. 利用思路2.3.1. 用户端2.3.1.1. 验证跳转 2.3.2. 攻击端2.3.2.1…...

JavaScript(函数,作用域和闭包)
目录 一,什么是函数1.1,常用系统函数1.2,函数声明 1.3,函数表达式二,预解析2.1,函数自调用 2.2,回调函数三,变量的作用域3.1,隐式全局变量 四,作用域与块级作…...
浏览器访问 AWS ECS 上部署的 Docker 容器(监听 80 端口)
✅ 一、ECS 服务配置 Dockerfile 确保监听 80 端口 EXPOSE 80 CMD ["nginx", "-g", "daemon off;"]或 EXPOSE 80 CMD ["python3", "-m", "http.server", "80"]任务定义(Task Definition&…...

XCTF-web-easyupload
试了试php,php7,pht,phtml等,都没有用 尝试.user.ini 抓包修改将.user.ini修改为jpg图片 在上传一个123.jpg 用蚁剑连接,得到flag...
CVPR 2025 MIMO: 支持视觉指代和像素grounding 的医学视觉语言模型
CVPR 2025 | MIMO:支持视觉指代和像素对齐的医学视觉语言模型 论文信息 标题:MIMO: A medical vision language model with visual referring multimodal input and pixel grounding multimodal output作者:Yanyuan Chen, Dexuan Xu, Yu Hu…...

376. Wiggle Subsequence
376. Wiggle Subsequence 代码 class Solution { public:int wiggleMaxLength(vector<int>& nums) {int n nums.size();int res 1;int prediff 0;int curdiff 0;for(int i 0;i < n-1;i){curdiff nums[i1] - nums[i];if( (prediff > 0 && curdif…...

Linux --进程控制
本文从以下五个方面来初步认识进程控制: 目录 进程创建 进程终止 进程等待 进程替换 模拟实现一个微型shell 进程创建 在Linux系统中我们可以在一个进程使用系统调用fork()来创建子进程,创建出来的进程就是子进程,原来的进程为父进程。…...

微软PowerBI考试 PL300-在 Power BI 中清理、转换和加载数据
微软PowerBI考试 PL300-在 Power BI 中清理、转换和加载数据 Power Query 具有大量专门帮助您清理和准备数据以供分析的功能。 您将了解如何简化复杂模型、更改数据类型、重命名对象和透视数据。 您还将了解如何分析列,以便知晓哪些列包含有价值的数据,…...
代码随想录刷题day30
1、零钱兑换II 给你一个整数数组 coins 表示不同面额的硬币,另给一个整数 amount 表示总金额。 请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额,返回 0 。 假设每一种面额的硬币有无限个。 题目数据保证结果符合 32 位带…...

Python基于历史模拟方法实现投资组合风险管理的VaR与ES模型项目实战
说明:这是一个机器学习实战项目(附带数据代码文档),如需数据代码文档可以直接到文章最后关注获取。 1.项目背景 在金融市场日益复杂和波动加剧的背景下,风险管理成为金融机构和个人投资者关注的核心议题之一。VaR&…...
LRU 缓存机制详解与实现(Java版) + 力扣解决
📌 LRU 缓存机制详解与实现(Java版) 一、📖 问题背景 在日常开发中,我们经常会使用 缓存(Cache) 来提升性能。但由于内存有限,缓存不可能无限增长,于是需要策略决定&am…...
uniapp 字符包含的相关方法
在uniapp中,如果你想检查一个字符串是否包含另一个子字符串,你可以使用JavaScript中的includes()方法或者indexOf()方法。这两种方法都可以达到目的,但它们在处理方式和返回值上有所不同。 使用includes()方法 includes()方法用于判断一个字…...