【数据结构】手撕单链表
目录
一,链表的概念及结构
二,接口实现
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,隐式全局变量 四,作用域与块级作…...
智慧医疗能源事业线深度画像分析(上)
引言 医疗行业作为现代社会的关键基础设施,其能源消耗与环境影响正日益受到关注。随着全球"双碳"目标的推进和可持续发展理念的深入,智慧医疗能源事业线应运而生,致力于通过创新技术与管理方案,重构医疗领域的能源使用模式。这一事业线融合了能源管理、可持续发…...
Leetcode 3576. Transform Array to All Equal Elements
Leetcode 3576. Transform Array to All Equal Elements 1. 解题思路2. 代码实现 题目链接:3576. Transform Array to All Equal Elements 1. 解题思路 这一题思路上就是分别考察一下是否能将其转化为全1或者全-1数组即可。 至于每一种情况是否可以达到…...
【JavaEE】-- HTTP
1. HTTP是什么? HTTP(全称为"超文本传输协议")是一种应用非常广泛的应用层协议,HTTP是基于TCP协议的一种应用层协议。 应用层协议:是计算机网络协议栈中最高层的协议,它定义了运行在不同主机上…...
Cesium1.95中高性能加载1500个点
一、基本方式: 图标使用.png比.svg性能要好 <template><div id"cesiumContainer"></div><div class"toolbar"><button id"resetButton">重新生成点</button><span id"countDisplay&qu…...
Java如何权衡是使用无序的数组还是有序的数组
在 Java 中,选择有序数组还是无序数组取决于具体场景的性能需求与操作特点。以下是关键权衡因素及决策指南: ⚖️ 核心权衡维度 维度有序数组无序数组查询性能二分查找 O(log n) ✅线性扫描 O(n) ❌插入/删除需移位维护顺序 O(n) ❌直接操作尾部 O(1) ✅内存开销与无序数组相…...
PPT|230页| 制造集团企业供应链端到端的数字化解决方案:从需求到结算的全链路业务闭环构建
制造业采购供应链管理是企业运营的核心环节,供应链协同管理在供应链上下游企业之间建立紧密的合作关系,通过信息共享、资源整合、业务协同等方式,实现供应链的全面管理和优化,提高供应链的效率和透明度,降低供应链的成…...
深入浅出:JavaScript 中的 `window.crypto.getRandomValues()` 方法
深入浅出:JavaScript 中的 window.crypto.getRandomValues() 方法 在现代 Web 开发中,随机数的生成看似简单,却隐藏着许多玄机。无论是生成密码、加密密钥,还是创建安全令牌,随机数的质量直接关系到系统的安全性。Jav…...
【Redis技术进阶之路】「原理分析系列开篇」分析客户端和服务端网络诵信交互实现(服务端执行命令请求的过程 - 初始化服务器)
服务端执行命令请求的过程 【专栏简介】【技术大纲】【专栏目标】【目标人群】1. Redis爱好者与社区成员2. 后端开发和系统架构师3. 计算机专业的本科生及研究生 初始化服务器1. 初始化服务器状态结构初始化RedisServer变量 2. 加载相关系统配置和用户配置参数定制化配置参数案…...
Java - Mysql数据类型对应
Mysql数据类型java数据类型备注整型INT/INTEGERint / java.lang.Integer–BIGINTlong/java.lang.Long–––浮点型FLOATfloat/java.lang.FloatDOUBLEdouble/java.lang.Double–DECIMAL/NUMERICjava.math.BigDecimal字符串型CHARjava.lang.String固定长度字符串VARCHARjava.lang…...
ServerTrust 并非唯一
NSURLAuthenticationMethodServerTrust 只是 authenticationMethod 的冰山一角 要理解 NSURLAuthenticationMethodServerTrust, 首先要明白它只是 authenticationMethod 的选项之一, 并非唯一 1 先厘清概念 点说明authenticationMethodURLAuthenticationChallenge.protectionS…...
