当前位置: 首页 > news >正文

数据结构与算法——单链表的实现及增、插、删、查、印、毁

文章目录

  • 一、前言
  • 二、单链表的基本概念
    • 2.1单链表的概念
    • 2.2单链表的结构
    • 2.3单链表的性质
    • 2.4结点实现代码
  • 三、单链表的增、插、删、查、印、毁
    • 3.1结点的创建
    • 3.2链表的尾插
    • 3.3链表的头插
    • 3.4链表指定位置之后插入数据
    • 3.5链表指定位置之前插入数据
    • 3.6链表的尾删
    • 3.7链表的头删
    • 3.8删除指定位置pos结点
    • 3.9删除指定位置pos之后的结点
    • 3.10链表元素的查找
    • 3.11链表的打印
    • 3.12链表的销毁
  • 四、单链表整体代码分析
    • 4.1头文件——SList.h及代码展示
    • 4.2主体代码文件——Slist.c及代码展示
    • 4.3测试文件——test.c及代码展示
      • 4.3.1新结点创建演示
      • 4.3.2尾插演示
      • 4.3.3头插演示
      • 4.3.4尾删演示
      • 4.3.5头删演示
      • 4.3.6元素查找演示
      • 4.3.7指定位置之后插入演示
      • 4.3.8指定位置之前插入演示
      • 4.3.9删除pos结点演示
      • 4.3.10删除pos之后结点演示
      • 4.3.11单链表销毁演示
  • 五、总结

一、前言

学习如逆水行舟,不进则退。今天的你还在坚持学习吗?最近up身边也是出现了一件很有意思的事情:我的高中的时候一个朋友,他询问我一些有关编程的知识,我一听到这不就兴奋起来了吗?于是我就把我一系列的学习方法推荐给了他,他当时也是说非常有用。可是过了几天他却就彻底躺平了,我问他:你最近怎么了啊?他回答我说:学习太累了,我想躺平了。我听完为之一震,后面即使过了好久,他也没继续学习。通过这件事情,up希望能引起大家的警醒,学习虽然很累,但是学完之后却非常幸福。
好了,一小碗鸡汤,希望与大家共勉。前面呢我已经给大家介绍完了顺序表这种数据结构,今天我给大家再介绍一种新的数据结构——单链表,请大家拭目以待。

二、单链表的基本概念

2.1单链表的概念

单链表是一种储存结构,它在物理储存结构上是非连续且非顺序的,数据元素的逻辑顺序是通过链表中的指针衔接次序实现的

2.2单链表的结构

在这里插入图片描述
地铁相信大家都见过吧,它是由一节一节的车厢组成的,每节车厢作为一个个体单独存在,在最前面由一个火车头引领。在每节车厢的末尾,还有通往下一节车厢的大门,方便乘客们随时走动。其实啊,单链表也相当于是一个“火车”,那么单链表的结构又具体是什么样子的呢?如图:
在这里插入图片描述
由图可知:单链表也是由一节一节类似”车厢“的结点组成,除了头结点之外,每个结点都由两部分组成:分别是当前结点内存放的数据和另外一个结点的地址,而在这些单独结点的最开始,由头结点所引领。但是通过仔细观察,我们发现:结点与结点之间都不是连续存放的,通过存放在结点中另外一个结点的地址,就把这些单独的结点连接到了一起,而最后一个结点后面已经没有结点了,于是就置为空(NULL)。

2.3单链表的性质

1:单链表在逻辑结构上是连续的,在物理结构上是不连续的;
2:结点一般是从堆上申请的;
2:从堆上申请来的空间,可能是连续也可能是不连续的。

2.4结点实现代码

在这里插入图片描述

三、单链表的增、插、删、查、印、毁

3.1结点的创建

画图展示:
在这里插入图片描述
代码展示:
在这里插入图片描述

3.2链表的尾插

画图展示:
在这里插入图片描述
实质:如果链表为空,直接插入即可;如果链表不为空,把要插入的结点的地址放入原链表的最后一个结点所指向存放的地址中
代码展示:
在这里插入图片描述

3.3链表的头插

画图展示:
在这里插入图片描述
实质:把原链表第一个结点的地址放入新结点所指向存放的地址中。
代码展示:
在这里插入图片描述

3.4链表指定位置之后插入数据

画图展示:
在这里插入图片描述
实质:先把原链表中指定位置之后的结点的地址放入所指向存放的地址中,再把新结点的地址放入pos位置结点所指向存放的地址中。
代码展示:
在这里插入图片描述

3.5链表指定位置之前插入数据

画图展示:
在这里插入图片描述
实质:如果是第一个结点之前,则是头插;如果不是第一个结点之前,先把新结点的地址放入pos位置前一个结点所指向存放的地址中,再把pos结点的地址放入新结点所指向存放的地址中。
代码展示:
在这里插入图片描述

3.6链表的尾删

画图展示:
在这里插入图片描述
实质:如果只有一个结点,把这个结点删除并且置为NULL即可;如果链表不止一个结点,把要删除的最后一个结点的前一个结点指向所存放的地址改为NULL,再把最后一个结点释放即可。
代码展示:
在这里插入图片描述

3.7链表的头删

画图展示:
在这里插入图片描述
实质:直接把头结点删除使第二个结点成为头结点即可。
代码展示;
在这里插入图片描述

3.8删除指定位置pos结点

画图展示:
在这里插入图片描述

实质:把原链表中pos位置之后的结点的地址放入原链表中pos位置之前的结点所指向存放的地址中即可,最后再把pos结点释放掉。
代码展示:
在这里插入图片描述

3.9删除指定位置pos之后的结点

画图展示:
在这里插入图片描述
实质:把原链表中pos位置之后的结点的地址放入原链表中pos位置之前的结点所指向存放的地址中即可
代码展示:在这里插入图片描述

3.10链表元素的查找

实质:遍历链表,依次查找
代码展示:
在这里插入图片描述

3.11链表的打印

实质;遍历链表,依次打印
代码展示:
在这里插入图片描述

3.12链表的销毁

实质:遍历链表,依次销毁
代码展示:
在这里插入图片描述

四、单链表整体代码分析

OK啊,上面up就把单链表的各个功能模块的实现给大家讲解完毕,为了避免大家在单独看每个小模块的时候有疑惑,我还是给大家整体分析一波。单链表和顺序表一样,也可以由三个文件组成,分别是头文件——SList.h,主体代码文件——SList.c,测试文件——test.c。关于为什么要用3个文件而不是一个文件和各个文件的功能,up在上上一篇顺序表的实现中已经讲解地非常透彻了,有不知道的宝子们可以前往翻阅。

4.1头文件——SList.h及代码展示

在这里插入图片描述
代码粘贴:

//头文件SList.h
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef int SLTDataType;
typedef struct SLTNode
{
	SLTDataType data;
	struct SLTNode* next;
}SLTNode;
SLTNode* SLTbuyNode(SLTDataType x);//新结点的创建
void SLTPushBack(SLTNode** pphead, SLTDataType x);//尾插
void SLTPushFront(SLTNode** pphead, SLTDataType x);//头插
void SLTPopBack(SLTNode** pphead);//尾删
void SLTPopFront(SLTNode** pphead);//头删
void SLTInsertAfter(SLTNode* pos, SLTDataType x);//指定位置之后插入结点
void SLTInsertFront(SLTNode** pphead, SLTNode* pos, SLTDataType x);//指定位置之前插入结点
void SLTErase(SLTNode** pphead, SLTNode* pos);//删除指定位置结点
void SLTEraseAfter(SLTNode* pos);//删除指定位置之后结点
SLTNode* SLTFind(SLTNode* phead,  SLTDataType x);//链表元素的查找
void SLTPrint(SLTNode* phead);//链表元素的打印
void SLTDestroy(SLTNode** pphead);//链表的销毁

4.2主体代码文件——Slist.c及代码展示

在这里插入图片描述
代码粘贴:

//主体代码文件SList.c
#include "SList.h"
SLTNode* SLTbuyNode(SLTDataType x)//新结点的创建
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));//申请空间
	if (newnode == NULL)//判断是否申请成功
	{
		printf("malloc fail!");
		exit(1);
	}
	newnode->data = x;
	newnode->next = NULL;
	return newnode;
}
void SLTPushBack(SLTNode** pphead, SLTDataType x)//尾插
{
	assert(pphead);
	SLTNode* newnode = SLTbuyNode(x);//创建新结点
	if (*pphead == NULL)//链表为空
	{
		*pphead = newnode;
	}
	else//链表不为空
	{
		SLTNode* ptail = *pphead;
		while (ptail->next)
		{
			ptail = ptail->next;
		}
		ptail->next = newnode;
	}
}
void SLTPushFront(SLTNode** pphead, SLTDataType x)//头插
{
	assert(pphead);
	SLTNode* newnode = SLTbuyNode(x);//创建新结点
	newnode->next = *pphead;
	*pphead = newnode;
}
void SLTPopBack(SLTNode** pphead)//尾删
{
	assert(pphead);
	if ((*pphead)->next == NULL)//如果只有一个结点
	{
		free(*pphead);
		*pphead = NULL;
	}
	else//如果不止一个结点
	{
		SLTNode* prev = NULL;
		SLTNode* ptail = *pphead;
		while (ptail->next)
		{
			prev = ptail;
			ptail = ptail->next;
		}
		prev->next = NULL;
		free(ptail);
		ptail = NULL;
	}
}
void SLTPopFront(SLTNode** pphead)//头删
{
	assert(pphead);
	SLTNode* next = (*pphead)->next;
	free(*pphead);
	*pphead = next;

}
void SLTInsertAfter(SLTNode* pos, SLTDataType x)//指定位置之后插入结点
{
	assert(pos);
	SLTNode* newnode = SLTbuyNode(x);
	newnode->next = pos->next;
	pos->next = newnode;
}
void SLTInsertFront(SLTNode** pphead, SLTNode* pos, SLTDataType x)//指定位置之前插入结点
{
	assert(pphead && pos);
	if (pos == *pphead)//判断是否为第一个结点之前
	{
		SLTPushFront(pphead, x);
	}
	else//不是第一个结点之前
	{
		SLTNode* newnode = SLTbuyNode(x);
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		prev->next = newnode;
		newnode->next = pos;
	}
}
void SLTErase(SLTNode** pphead, SLTNode* pos)//删除指定位置结点
{
	assert(pphead && pos);//判断是否为头结点
	if (pos == *pphead)
	{
		SLTPopFront(pphead);
	}
	else//不是头结点
	{
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		prev->next = pos->next;
		free(pos);
		pos = NULL;
	}
}
void SLTEraseAfter(SLTNode* pos)//删除指定位置之后结点
{
	assert(pos && pos->next);
	SLTNode* del = pos->next;
	pos->next = del->next;
	free(del);
	del = NULL;
}
SLTNode* SLTFind(SLTNode* phead,  SLTDataType x)//链表元素的查找
{
	SLTNode* pcur = phead;
	while (pcur)
	{
		if (pcur->data == x)
		{
			return pcur;
		}
		pcur = pcur->next;
	}
	return NULL;
}
void SLTPrint(SLTNode* phead)//链表元素的打印
{
	SLTNode* pcur = phead;
	while (pcur)
	{
		printf("%d -> ", pcur->data);
		pcur = pcur->next;
	}
	printf("NULL\n");
}
void SLTDestroy(SLTNode** pphead)//链表的销毁
{
	SLTNode* pcur = *pphead;
	while (pcur)
	{
		SLTNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	*pphead = NULL;
}

4.3测试文件——test.c及代码展示

4.3.1新结点创建演示

在这里插入图片描述
在这里插入图片描述

4.3.2尾插演示

在这里插入图片描述
在这里插入图片描述

4.3.3头插演示

在这里插入图片描述

在这里插入图片描述

4.3.4尾删演示

在这里插入图片描述
在这里插入图片描述

4.3.5头删演示

在这里插入图片描述
在这里插入图片描述

4.3.6元素查找演示

在这里插入图片描述
在这里插入图片描述

4.3.7指定位置之后插入演示

在这里插入图片描述
在这里插入图片描述

4.3.8指定位置之前插入演示

在这里插入图片描述

在这里插入图片描述

4.3.9删除pos结点演示

在这里插入图片描述

在这里插入图片描述

4.3.10删除pos之后结点演示

在这里插入图片描述
在这里插入图片描述

4.3.11单链表销毁演示

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

#include "SList.h"
test01()
{
	//创建4个新结点
	SLTNode* node1 = (SLTNode*)malloc(sizeof(SLTNode));
	SLTNode* node2 = (SLTNode*)malloc(sizeof(SLTNode));
	SLTNode* node3 = (SLTNode*)malloc(sizeof(SLTNode));
	SLTNode* node4 = (SLTNode*)malloc(sizeof(SLTNode));

	node1->data = 1;
	node2->data = 2;
	node3->data = 3;
	node4->data = 4;

	node1->next = node2;
	node2->next = node3;
	node3->next = node4;
	node4->next = NULL;

	SLTNode* plist = node1;
	//SLTPrint(plist);


}
test02()
{
	SLTNode* plist = NULL;
	//SLTPushBack(&plist, 1);
	//SLTPushBack(&plist, 2);
	//SLTPushBack(&plist, 3);
	//SLTPushBack(&plist, 4);
	//SLTPrint(plist);
	SLTPushFront(&plist, 1);
	SLTPushFront(&plist, 2);
	SLTPushFront(&plist, 3);
	SLTPushFront(&plist, 4);
	//SLTPrint(plist);
	//SLTPopBack(&plist);
	//SLTPrint(plist);
	//SLTPopFront(&plist);
	//SLTPrint(plist);
	//SLTNode* find = SLTFind(plist, 4);
	SLTDestroy(plist);
	SLTPrint(plist);
	//SLTErase(&plist, find);
	//SLTPrint(plist);
	//SLTNode* find1 = SLTFind(plist, 3);
	//SLTEraseAfter(find1);
	//SLTPrint(plist);
	//if (find)
	//{
	//	printf("找到了!\n");
	//}
	//else
	//{
	//	printf("未找到\n");
	//}
	//SLTInsertAfter( find, 100);
	//SLTPrint(plist);
	//SLTInsertFront(&plist, find, 100);
	//SLTPrint(plist);
	//SLTErase(&plist, find);
	//SLTEraseAfter(find1);
	//SLTDestroy(plist);
	//SLTPrint(plist);
}
int main()
{
	//test01();
	test02();
	return 0;
}

五、总结

关于数据结构——单链表的所有知识点,到现在已经全部over了,不知道屏幕前的你消化了多少呢?up个人觉得,单链表这里确实有一点抽象,建议大家反复观看加以理解。最后也希望大家在学习的路上再上一层楼。

学习之路如掌纹, 错综复杂却由你掌控
在这里插入图片描述

http://www.dtcms.com/a/109180.html

相关文章:

  • 【c++深入系列】:类与对象详解(中)
  • Golang定时任务管理(中文日志+防重复执行)
  • React 项目使用 pdf.js 及 Elasticpdf 教程
  • lvgl避坑记录
  • lodash库介绍(一个现代JavaScript实用工具库,提供模块化、性能优化和额外功能)JavaScript库(防抖、节流、函数柯里化)JS库
  • 每日一题洛谷P8664 [蓝桥杯 2018 省 A] 付账问题c++
  • 【C++】nlohmann::json 配置加载技术实践:从基础到高级应用
  • OpenLayers:如何控制Overlay的层级?
  • 如何为Linux/Android Kernel 5.4和5.15添加 fuse passthrough透传功能 ?
  • 如何通过管理系统提升团队协作效率
  • 大模型学习二:DeepSeek R1+蒸馏模型组本地部署与调用
  • css动态设置div宽高,calc函数
  • Unity中的动态合批使用整理
  • Vue中虚拟DOM创建到挂载的过程
  • Milvus 向量数据库详解:从核心概念到 Docker 部署实践
  • redis 安装
  • windows批处理命令,执行一个python文件,将命令保存为xxxx.bat文件
  • angular+form实现2048小游戏
  • 41. 评论日记
  • 2025高频面试算法总结篇【二叉树】
  • GLTF(GL Transmission Format)详细介绍
  • 多线程基础
  • 多线程交替打印ABC方法整理
  • 前端入门之CSS
  • rknn_convert的使用方法
  • WebRTC源码解析:Android如何渲染画面
  • Redis是什么?架构是怎么样的?
  • MySQL客户端工具-图形化工具-DataGrip 安装与使用
  • 《AI大模型应知应会100篇》加餐篇:LlamaIndex 与 LangChain 的无缝集成
  • 3.9/Q2,Charls最新文章解读