【数据结构】手撕单链表
目录
一,链表的概念及结构
二,接口实现
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,隐式全局变量 四,作用域与块级作…...
51c自动驾驶~合集58
我自己的原文哦~ https://blog.51cto.com/whaosoft/13967107 #CCA-Attention 全局池化局部保留,CCA-Attention为LLM长文本建模带来突破性进展 琶洲实验室、华南理工大学联合推出关键上下文感知注意力机制(CCA-Attention),…...
工业安全零事故的智能守护者:一体化AI智能安防平台
前言: 通过AI视觉技术,为船厂提供全面的安全监控解决方案,涵盖交通违规检测、起重机轨道安全、非法入侵检测、盗窃防范、安全规范执行监控等多个方面,能够实现对应负责人反馈机制,并最终实现数据的统计报表。提升船厂…...
跨链模式:多链互操作架构与性能扩展方案
跨链模式:多链互操作架构与性能扩展方案 ——构建下一代区块链互联网的技术基石 一、跨链架构的核心范式演进 1. 分层协议栈:模块化解耦设计 现代跨链系统采用分层协议栈实现灵活扩展(H2Cross架构): 适配层…...
linux 下常用变更-8
1、删除普通用户 查询用户初始UID和GIDls -l /home/ ###家目录中查看UID cat /etc/group ###此文件查看GID删除用户1.编辑文件 /etc/passwd 找到对应的行,YW343:x:0:0::/home/YW343:/bin/bash 2.将标红的位置修改为用户对应初始UID和GID: YW3…...
Android15默认授权浮窗权限
我们经常有那种需求,客户需要定制的apk集成在ROM中,并且默认授予其【显示在其他应用的上层】权限,也就是我们常说的浮窗权限,那么我们就可以通过以下方法在wms、ams等系统服务的systemReady()方法中调用即可实现预置应用默认授权浮…...
使用Matplotlib创建炫酷的3D散点图:数据可视化的新维度
文章目录 基础实现代码代码解析进阶技巧1. 自定义点的大小和颜色2. 添加图例和样式美化3. 真实数据应用示例实用技巧与注意事项完整示例(带样式)应用场景在数据科学和可视化领域,三维图形能为我们提供更丰富的数据洞察。本文将手把手教你如何使用Python的Matplotlib库创建引…...
用机器学习破解新能源领域的“弃风”难题
音乐发烧友深有体会,玩音乐的本质就是玩电网。火电声音偏暖,水电偏冷,风电偏空旷。至于太阳能发的电,则略显朦胧和单薄。 不知你是否有感觉,近两年家里的音响声音越来越冷,听起来越来越单薄? —…...
安宝特方案丨船舶智造的“AR+AI+作业标准化管理解决方案”(装配)
船舶制造装配管理现状:装配工作依赖人工经验,装配工人凭借长期实践积累的操作技巧完成零部件组装。企业通常制定了装配作业指导书,但在实际执行中,工人对指导书的理解和遵循程度参差不齐。 船舶装配过程中的挑战与需求 挑战 (1…...
【Go语言基础【13】】函数、闭包、方法
文章目录 零、概述一、函数基础1、函数基础概念2、参数传递机制3、返回值特性3.1. 多返回值3.2. 命名返回值3.3. 错误处理 二、函数类型与高阶函数1. 函数类型定义2. 高阶函数(函数作为参数、返回值) 三、匿名函数与闭包1. 匿名函数(Lambda函…...
Go 语言并发编程基础:无缓冲与有缓冲通道
在上一章节中,我们了解了 Channel 的基本用法。本章将重点分析 Go 中通道的两种类型 —— 无缓冲通道与有缓冲通道,它们在并发编程中各具特点和应用场景。 一、通道的基本分类 类型定义形式特点无缓冲通道make(chan T)发送和接收都必须准备好࿰…...
