【数据结构】手撕单链表
目录
一,链表的概念及结构
二,接口实现
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,隐式全局变量 四,作用域与块级作…...
ARM Jazelle技术:硬件加速Java字节码执行详解
1. ARM Jazelle技术概述Jazelle技术是ARM架构中用于硬件加速Java字节码执行的关键扩展,最早出现在ARMv5TE架构中。这项技术通过在处理器内部集成Java字节码执行单元,实现了Java虚拟机(JVM)功能的硬件化。与传统的软件解释器相比,Jazelle能够将…...
独立开发者如何利用 Taotoken 以更低成本试验多种 AI 模型能力
🚀 告别海外账号与网络限制!稳定直连全球优质大模型,限时半价接入中。 👉 点击领取海量免费额度 独立开发者如何利用 Taotoken 以更低成本试验多种 AI 模型能力 对于独立开发者或小型工作室而言,在产品开发的早期阶段…...
基于Python/Flask的洗车店业务管理系统设计与实现
1. 项目概述:从“洗车”到“洗车服务”的数字化重构最近在GitHub上看到一个挺有意思的项目,叫“washing-cars”。光看名字,你可能会觉得这只是一个关于洗车的小工具或者记录表。但当我深入进去,才发现它远不止于此。这个项目本质上…...
多数人支持!微软或把 Xbox 重新品牌化为 XBOX,回归最初形式
Xbox 品牌重塑:从民意调查到账号更名微软 Xbox 首席执行官阿莎夏尔马在 X(原推特)上发起民意调查,询问粉丝微软应使用 Xbox 还是 XBOX,结果多数人支持 XBOX,随后公司将其 X 账号更名。不过,Xbox…...
FinalBurn Neo:终极开源街机模拟器技术深度解析
FinalBurn Neo:终极开源街机模拟器技术深度解析 【免费下载链接】FBNeo FinalBurn Neo - We are Team FBNeo. 项目地址: https://gitcode.com/gh_mirrors/fb/FBNeo FinalBurn Neo(简称FBNeo)是一款专业级的开源街机模拟器,…...
开源无人机任务控制系统:微服务架构与自主飞行开发实战
1. 项目概述:一个开源的无人机任务控制系统如果你和我一样,玩过一段时间无人机,从最初的“一键起飞”到后来想实现一些自动化的航线飞行,你可能会发现,市面上成熟的任务规划软件(比如DJI的Pilot 2或一些地面…...
CircuitPython HID设备模拟:从键盘鼠标到数据记录实战指南
1. 项目概述:从微控制器到智能交互设备在嵌入式开发的世界里,让一块小小的开发板“假装”成键盘或鼠标,直接控制你的电脑,这听起来像是极客的魔法,但其实是基于一个非常成熟且标准化的协议:HID。HID&#x…...
AI对话记忆管理实战:memory-organizer库解决长上下文难题
1. 项目概述:一个为AI记忆体“瘦身”与“归档”的利器最近在折腾一些本地大语言模型(LLM)的应用,比如搭建个人知识库助手或者长期对话机器人,一个绕不开的痛点就是“记忆”的管理。模型本身没有持久记忆,每…...
Midjourney极简艺术风格实战手册(2024V6.2最新适配版):含17个已验证失效词黑名单与8组高通过率--sref权重组合
更多请点击: https://intelliparadigm.com 第一章:Midjourney极简艺术风格的核心定义与美学边界 极简艺术风格在 Midjourney 中并非单纯减少元素,而是通过语义压缩、形式提纯与负空间策略构建高度凝练的视觉语言。其核心在于以最少的视觉单元…...
航空发电机综合测试系统设计【附代码】
✨ 长期致力于航空发电机、测试系统、控制方法、LabVIEW研究工作,擅长数据搜集与处理、建模仿真、程序编写、仿真设计。 ✅ 专业定制毕设、代码 ✅ 如需沟通交流,点击《获取方式》 (1)设计直流拖动调速系统的双闭环自适应模糊PID控…...
