单链表的实现(单链表的增删查改)

news2024/11/15 7:12:48

在顺序表中实现数据的增删的操作时,都要把操作位置之后的数据全部移动一遍,操作效率低下。其次是容量固定(静态顺序表),虽然在动态顺序表中容量可变,但也会造成空间上的浪费。

单链表就完美解决了上述缺点。

这里要说明一点,单链表与顺序表没有哪个比哪个更好,只有哪个更适应场合。

介绍完上述后,现在开始单链表的实现,实现单链表需要用到结构体指针。

链表中的节点都是动态开辟的空间,是在堆上的,并不会出了作用域就销毁,如果在链表中找到值为1的节点,那就把这个节点的地址返回(将体现在指定位置插入删除的操作,解决了为什么没有传址而传值的疑惑)

创建结构体:

typedef int SLTDataType;
typedef struct SListNode SLTNode;
typedef struct SListNode
{
    SLTDataType data;
    SLTNode* next;
}SLTNode;

结构体指针SLTNode*储存下一个节点,可以理解为一条链条或套娃。直到最后一个节点的next指向空(NULL)时

需要注意的是这里SLTNode我进行了前置声明因此可以直接用转换后的语句。

如果没有前置声明,STLNode不会被识别出来,这里需要说一下,操作系统在找SLTNode时只会向上找不会向向下找,这也就是为什么有许多代码中会有前置声明的函数或者像当先操作的转换一样。

能直观的看到前置声明的重要性。

创建一个头文件SListNode.h用来放需要实现的函数(声明)

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef int SLTDataType;
typedef struct SListNode SLTNode;
typedef struct SListNode
{
	SLTDataType data;
	SLTNode* next;
}SLTNode;


void SLTPrint(SLTNode* phead);

//头部插入删除/尾部插入删除
void SLTPushBack(SLTNode** pphead, SLTDataType x);
void SLTPushFront(SLTNode** pphead, SLTDataType x);
void SLTPopBack(SLTNode** pphead);
void SLTPopFront(SLTNode** pphead);

//查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x);
//在指定位置之前插入数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);
//删除pos节点
void SLTErase(SLTNode** pphead, SLTNode* pos);
//在指定位置之后插入数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x);
//删除pos之后的节点
void SLTEraseAfter(SLTNode* pos);
//销毁链表
void SListDesTroy(SLTNode** pphead);

创建SListNode.c文件用来实现头文件的函数

首先是尾插

//尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
    SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
    newnode->data = x;
    newnode->next = NULL;
    //不能直接这样用*pphead->next = NULL;加括号(*pphead)->next,  优先级问题
    //链表为空
    if (*pphead == NULL)
    {
        *pphead = newnode;
    }
    //链表不为空,找尾节点
    SLTNode* node = *pphead;
    while (node->next)
    {
        node = node->next;
    }
    node->next = newnode;
}

使用SLTNode** pphead接收,传递的phead是SLTNode*类型,而我们需要改变phead的内容,因此要传址而非传值,故用二级指针接收。

在这里我想用*pphead->next可是会报错,这是为什么呢,因为优先级,->的优先级比*的优先级高,pphead先与->结合,因此报错。

首先创建 节点(结构体指针newnode)保存需要插入的值,然后要判断两种情况,一种是*pphead为空时,直接让*pphead等于newnode。第二种*pphead不为空,创建一个node等于*pphead,循环向后找,找到node->next为空时,node->next = newnode。

头插的实现

//头插
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
    SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
    newnode->data = x;
    if (*pphead == NULL)
    {
        *pphead = newnode;
        newnode->next = NULL;
    }
    else
    {
        SLTNode* ret = *pphead;
        newnode->next = ret;
        *pphead = newnode;
    }
}

头插跟尾插一样需要判断两种情况,当*pphead为空和不为空的两种情况。为空直接等于,不为空创建临时的ret保存*pphead,然后让*pphead(头节点)等于newnode。

尾删的实现

//尾删
void SLTPopBack(SLTNode** pphead)
{
    assert(*pphead);
    assert(pphead);
    if ((*pphead)->next == NULL)
    {
        free(*pphead);
        *pphead = NULL;
    }
    else
    {
        SLTNode* node = *pphead;
        SLTNode* ret = node->next;

        while (ret->next)
        {
            node = node->next;
            ret = ret->next;
        }
        free(ret);
        ret = NULL;
        node->next = NULL;
    }
}

删除涉及到空间的释放。因此要判断*ppead和ppead是否为空,不能传递一个空值来删除释放吧,所以要用assert断言。

也是要判断两种情况,一种是只有一个节点时,另一种是多个节点时。当为一个节点时,直接释放后置空即可,当为多个节点时需要遍历到最后一个节点后进行删除。

头删的实现

//头删
void SLTPopFront(SLTNode** pphead)
{
    assert(*pphead);
    assert(pphead);
    if ((*pphead)->next == NULL)
    {
        free(*pphead);
        *pphead = NULL;
    }
    else
    {
        SLTNode* node = (*pphead)->next;
        free(*pphead);
        *pphead = node;
    }
}

头删和尾删还是差不多,需要判断两种情况,是否是一个节点或多个节点。一个节点直接删除即可,多个节点,将头节点指向第二个节点。

查找的实现

//查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{
    assert(phead);
    SLTNode* node = phead;
    while (node)
    {
        if (node->data == x)
        {
            return node;
        }
        node = node->next;
    }
    return NULL;
}

void SLTfind(SLTNode* phead, SLTDataType x)
{
    SLTNode* ret = SLTFind(phead, x);
    if (ret)
    {
        printf("找到了%d\n", ret->data);
    }
    else
    {
        printf("没找到%d\n", x);
    }
}

先创建SLTFind函数返回需要查找的节点(在指定位置操作时需要用到,因此将查找节点的操作分割出来),后用SLTfind判断。

在指定位置之前插入的实现

//指定位置之前插入数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
    assert(*pphead);
    assert(pphead);
    assert(pos);
    SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
    newnode->data = x;
    newnode->next = NULL;
    if (pos == *pphead)
    {
        SLTNode* node = (*pphead);
        *pphead = newnode;
        (*pphead)->next = node;
        return;
    }

    SLTNode* prev = *pphead;
    while (prev->next != pos)
    {
        prev = prev->next;
    }
    prev->next = newnode;
    newnode->next = pos;
}

链表中的节点都是动态开辟的空间,是在堆上的,并不会出了作用域就销毁,如果在链表中找到值为1的节点,那就把这个节点的地址返回

pos节点用SLTFind函数寻找,需要断言 pos是否为空,要判断*ppead和ppead是否为空。

判断两种情况,当插入节点为头节点时和不为头节点时。当插入节点为头节点时将头节点指向newnode。当不为头节点时创建prev使用while循环,当 prev->next=pos时退出,让prev的下个节点指向newnode,newnode的下个节点指向pos。

pos节点删除的实现

//删除pos节点
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
    assert(*pphead);
    assert(pphead);
    assert(pos);

    if (pos == *pphead)
    {
        SLTNode* ret = *pphead;
        *pphead = (*pphead)->next;
        free(ret);
        ret = NULL;
        return;
    }

    SLTNode* prev = *pphead;
    while (prev->next != pos)
    {
        prev = prev->next;
    }
    SLTNode* node = pos->next;
    prev->next = node;
    free(pos);
    pos = NULL;
}

SLTFind函数寻找pos节点,判断两种情况,pos是否为头节点,然后进行删除操作。

在指定位置之后插入的实现

//在指定位置之后插入数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{
    assert(pos);
    SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
    newnode->data = x;
    newnode->next = NULL;

    SLTNode* prve = pos->next;
    pos->next = newnode;
    newnode->next = prve;
}

这个更简单。

删除pos之后的节点的实现

//删除pos之后的节点
void SLTEraseAfter(SLTNode* pos)
{

    assert(pos);
    SLTNode* prve = pos->next;
    SLTNode* node = prve;
    while (node)
    {
        node = prve->next;
        free(prve);
        prve = node;
    }
    pos->next = NULL;
}

找到pos后循环释放。

销毁链表的实现

void SListDesTroy(SLTNode** pphead)
{
    assert(*pphead);
    assert(pphead);
    SLTNode* prve = (*pphead);
    while(prve)
    {
        prve = prve->next;
        free(*pphead);
        *pphead = NULL;
        *pphead = prve;
    }
}

一样的循环释放。

下面是SListNode.c代码

#include "SListNode.h"

//打印
void SLTPrint(SLTNode* phead)
{
	while (phead)
	{
		printf("%d->", phead->data);
		phead = phead->next;
	}
	printf("NULL\n");
}

//尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	newnode->data = x;
	newnode->next = NULL;
	//不能直接这样用*pphead->next = NULL;加括号(*pphead)->next,  优先级问题
	//链表为空
	if (*pphead == NULL)
	{
		*pphead = newnode;
	}
	//链表不为空,找尾节点
	SLTNode* node = *pphead;
	while (node->next)
	{
		node = node->next;
	}
	node->next = newnode;
}


//头插
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	newnode->data = x;
	if (*pphead == NULL)
	{
		*pphead = newnode;
		newnode->next = NULL;
	}
	else
	{
		SLTNode* ret = *pphead;
		newnode->next = ret;
		*pphead = newnode;
	}
}


//尾删
void SLTPopBack(SLTNode** pphead)
{
	assert(*pphead);
	assert(pphead);
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		SLTNode* node = *pphead;
		SLTNode* ret = node->next;

		while (ret->next)
		{
			node = node->next;
			ret = ret->next;
		}
		free(ret);
		ret = NULL;
		node->next = NULL;
	}
}


//头删
void SLTPopFront(SLTNode** pphead)
{
	assert(*pphead);
	assert(pphead);
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		SLTNode* node = (*pphead)->next;
		free(*pphead);
		*pphead = node;
	}
}



//查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{
	assert(phead);
	SLTNode* node = phead;
	while (node)
	{
		if (node->data == x)
		{
			return node;
		}
		node = node->next;
	}
	return NULL;
}

void SLTfind(SLTNode* phead, SLTDataType x)
{
	SLTNode* ret = SLTFind(phead, x);
	if (ret)
	{
		printf("找到了%d\n", ret->data);
	}
	else
	{
		printf("没找到%d\n", x);
	}
}


//指定位置之前插入数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
	assert(*pphead);
	assert(pphead);
	assert(pos);
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	newnode->data = x;
	newnode->next = NULL;
	if (pos == *pphead)
	{
		SLTNode* node = (*pphead);
		*pphead = newnode;
		(*pphead)->next = node;
		return;
	}

	SLTNode* prev = *pphead;
	while (prev->next != pos)
	{
		prev = prev->next;
	}
	prev->next = newnode;
	newnode->next = pos;
}


//删除pos节点
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
	assert(*pphead);
	assert(pphead);
	assert(pos);

	if (pos == *pphead)
	{
		SLTNode* ret = *pphead;
		*pphead = (*pphead)->next;
		free(ret);
		ret = NULL;
		return;
	}

	SLTNode* prev = *pphead;
	while (prev->next != pos)
	{
		prev = prev->next;
	}
	SLTNode* node = pos->next;
	prev->next = node;
	free(pos);
	pos = NULL;
}


//在指定位置之后插入数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{
	assert(pos);
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	newnode->data = x;
	newnode->next = NULL;

	SLTNode* prve = pos->next;
	pos->next = newnode;
	newnode->next = prve;
}


//删除pos之后的节点
void SLTEraseAfter(SLTNode* pos)
{

	assert(pos);
	SLTNode* prve = pos->next;
	SLTNode* node = prve;
	while (node)
	{
		node = prve->next;
		free(prve);
		prve = node;
	}
	pos->next = NULL;
}

//销毁链表
void SListDesTroy(SLTNode** pphead)
{
	assert(*pphead);
	assert(pphead);
	SLTNode* prve = (*pphead);
	while(prve)
	{
		prve = prve->next;
		free(*pphead);
		*pphead = NULL;
		*pphead = prve;
	}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1611719.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

.net8系列-02图文并茂手把手教你编写增删改查接口

前情提要 接上篇文章&#xff0c;我们的应用已经创建完毕了&#xff0c;接下来我们编写几个自己的接口 快速开始 新增Controller 复制一份WeatherForecastController.cs,改名为CommonInfoController 设置Class名 将CommonInfoController中的复制过来的class名改成新名 …

对称二叉树 - LeetCode 热题 39

大家好&#xff01;我是曾续缘&#x1f90e; 今天是《LeetCode 热题 100》系列 发车第 39 天 二叉树第 4 题 ❤️点赞 &#x1f44d; 收藏 ⭐再看&#xff0c;养成习惯 对称二叉树 给你一个二叉树的根节点 root &#xff0c; 检查它是否轴对称。 示例 1&#xff1a; 输入&#…

vue3图片展示实战

首先得有一个vue3项目 教程&#xff1a; vue3项目搭建 我测试存在两种方式可以将本地图片进行展示到页面 public文件夹下可以直接导入src下的asset文件夹下得图片需要在 script 标签内声明再导入网页图片不可导入&#xff0c;应该是存在一种限制 1&#xff0c;创建文件demo1I…

ICLR 2024 | FTS-Diffusion: 用于合成具有不规则和尺度不变模式的金融时间序列的生成框架

ICLR 2024 | FTS-Diffusion: 用于合成具有不规则和尺度不变模式的金融时间序列的生成框架 原创 QuantML QuantML 2024-04-17 09:53 上海 Content 本文提出了一个名为FTS-Diffusion的新颖生成框架&#xff0c;用于模拟金融时间序列中的不规则和尺度不变模式。这些模式由于其独…

桌面软件使用到的开源库

想了解一下桌面软件开发中可能使用到的dll库 联想锁屏 libcef-常用概念-框架特点-CSDN博客 libcurl库使用详情、libcurl库的制作-CSDN博客 使用Cef和Qt做一个跨平台的多标签多窗口浏览器_cef3 多个标签-CSDN博客 cef 依赖的文件 libcef - Bigben - 博客园 (cnblogs.com) Q…

Cadence virtuoso 原理图创建边框

给原理图创建如下图的边框Creating a Sheet Border and Title edit-sheet size添加画布 选择画布尺寸&#xff0c;border size里面的A/B/C/D对应不同的画布尺寸。 添加好画布后&#xff0c;在edit-sheet tilte里面可以选择输入相关信息&#xff0c;如电路名称&#xff0c;日期&…

STM32 F103C8T6经验笔记15:国产芯片下载受限问题

今日使用DAP仿真器下载时的小问题&#xff1a; Not a genuine sT Device! Abort connection 错误&#xff1a;不是真正的ST设备&#xff01;中止连接。 问题解释&#xff1a; 这是因为下载时软件给你检测出这不是他们的正版芯片了&#xff0c;然后我查看了一下之前的购买记…

项目管理-项目范围管理

目录 一、概述 二、范围计划的编制 2.1 项目中包含的范围 2.1.1 产品范围 2.1.2 工作范围 2.1.3 总结 2.2 范围计划编制的成果 2.2.1 范围管理计划 2.2.1.1 概述 2.2.1.2 内容 三、创建工作分解结构 3.1 概述 3.2 WBS目的和用途 3.3 WBS分层结构 3.3.1 分层结构图…

python爬虫-----深入了解 requests 库下篇(第二十五天)

&#x1f388;&#x1f388;作者主页&#xff1a; 喔的嘛呀&#x1f388;&#x1f388; &#x1f388;&#x1f388;所属专栏&#xff1a;python爬虫学习&#x1f388;&#x1f388; ✨✨谢谢大家捧场&#xff0c;祝屏幕前的小伙伴们每天都有好运相伴左右&#xff0c;一定要天天…

[卷积神经网络]YoloV8

一、YoloV8 1.网络详解 ①backbone部分&#xff1a;第一次卷积的卷积核缩小(由3变为6)&#xff1b;CSP模块的预处理卷积从3次变为2次&#xff1b;借鉴了YoloV7的多分支堆叠结构&#xff08;Multi_Concat_Block&#xff09;。 所小第一次卷积的卷积核尺寸会损失部分感受野&#…

1.7jdk安装rpm的安装ssh加固

jk安装 [rootlocalhost ~]# java -version openjdk version "1.8.0_161"[rootlocalhost ~]# rpm -qa | grep java tzdata-java-2018c-1.el7.noarch python-javapackages-3.4.1-11.el7.noarch java-1.7.0-openjdk-1.7.0.171-2.6.13.2.el7.x86_64 java-1.8.0-openjdk-1…

会议文字记录工具【钉钉闪记】

当开会时&#xff0c;需要文字记录会议内容&#xff0c;但是打字又慢&#xff0c;可以使用钉钉闪记。 钉钉工作台直接搜索-钉钉闪记

【Pytorch】PytorchCPU版或GPU报错异常处理(10X~4090D)

Pytorch为CPU版或GPU使用报错异常处理 文章目录 Pytorch为CPU版或GPU使用报错异常处理0.检查阶段1. 在conda虚拟环境中安装了torch2.卸载cpuonly3.从tsinghua清华源安装不完善误为cpu版本4.用tsinghua清华源安装成cpu错误版本5.conda中torch/vision/cudatoolkit版本与本机cuda版…

openai whisper 语音转文字尝鲜

最近大模型很火&#xff0c;也试试搭一下&#xff0c;这个是openai 开源的whisper&#xff0c;用来语音转文字。 安装 按照此文档安装&#xff0c;个人习惯先使用第一个pip命令安装&#xff0c;然后再用第二个安装剩下的依赖&#xff08;主要是tiktoken&#xff09; https:/…

paho-mqtt 库揭秘

文章目录 **paho-mqtt 库揭秘**第一部分&#xff1a;背景介绍第二部分&#xff1a;paho-mqtt 是什么&#xff1f;第三部分&#xff1a;如何安装这个库&#xff1f;第四部分&#xff1a;库函数使用方法第五部分&#xff1a;场景应用第六部分&#xff1a;常见Bug及解决方案第七部…

【海思Hi3516CV610】是面向新一代视频编解码标准、网络安全和隐私保护、人工智能行业应用方面的IPC SoC

海思Hi3516CV610是面向新一代视频编解码标准、网络安全和隐私保护、人工智能行业应用方面的IPC SoC&#xff0c;除了开发普通摄像机&#xff0c;还可以打造极具竞争力的枪球一体机、双目长短焦摄像机产品&#xff1b; 处理器内核: 支持ARM Cortex-A7 MP2 时钟速率950MHz 支持…

[Linux][进程信号][二][信号如何被保存][信号处理][可重入函数]详细解读

目录 1.信号如何被保存&#xff1f;1.信号其他相关常见概念2.信号在内核中的表示3.sigset_t -- 本质是个位图4.信号集操作函数sigset_t&#xff1a;sigprocmask()sigpending() 5.思考6.使用 2.信号处理0.内核态和用户态1.内核空间和用户空间2.信号何时被处理&#xff1f;3.信号…

Python | Leetcode Python题解之第42题接雨水

题目&#xff1a; 题解&#xff1a; class Solution:def trap(self, height: List[int]) -> int:if not height:return 0n len(height)leftMax [height[0]] [0] * (n - 1)for i in range(1, n):leftMax[i] max(leftMax[i - 1], height[i])rightMax [0] * (n - 1) [he…

5.Vue项目目录结构

Vue项目目录结构 我们通过Vue命令行工具 npm init vuelatest 创建项目&#xff0c;会出现很多文件及文件夹 .vscode --- VSCode工具的配置文件夹 node_modules --- Vue项目的运行依赖文件夹 public --- 资源文件夹&#xff08;浏览器图标&#xff09; src ---…

关于某次授权的大型内网渗透测试(1)

前期渗透&#xff1a; 打点&#xff1a;&#xff08;任意文件上传&#xff09; 直接发现头像处任意文件上传&#xff0c;这里直接上传冰蝎即可。 tasklist查看杀软 System Idle Process 0 N/A System …