单链表实现:从理论到代码

news2025/4/8 18:18:21

 ✨✨欢迎👍👍点赞☕️☕️收藏✍✍评论

个人主页:秋邱'博客

所属栏目:C语言,数据结构

 

前言

前面学习了顺序表,顺序表优点:

  • 可以随机访问元素,通过索引能快速定位到元素。
  • 存储密度高,不需要额外的指针空间。

但是它也有一些不足的地方:

  • 中间/头部位置的插入删除,需要挪动数据,效率低下
  • 动态顺序表,空间不够时需要扩容,扩容本身有消耗,空间浪费

这时候就有了链表。


链表的概念以及结构 

概念:链表是⼀种物理存储结构上⾮连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表 中的指针链接次序实现的 。

链表分类

链表的结构非常的多样化,一共有2 * 2 * 2种链表。

1、单向或者双向

2、带头或者不带头

3、循环或者不循环

虽然有这么多的链表的结构,但是我们实际中最常⽤还是两种结构: 单链表和双向带头循环链表 

1.⽆头单向⾮循环链表:结构简单,⼀般不会单独⽤来存数据。实际中更多是作为其他数据结 构的⼦结构,如哈希桶、图的邻接表等等。另外这种结构在笔试⾯试中出现很多。

2.带头双向循环链表:结构最复杂,⼀般⽤在单独存储数据。实际中使⽤的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使⽤代码实现以后会发现结构会带来很多优势,实现反⽽简单了,后⾯我们代码实现了就知道了。  

 定义结构体

单链表在内存中的存储(单向,不带头,不循环)

 结合前面所学的知识,可以定义结构体:

typedef int SLTypeData;//方便之后更改类型
typedef  struct SListNode
{
	SLTypeData data; //节点数据
	struct SListNode* next; //指针变量保存下个节点的地址
}SLTNode;

 单链表的功能

功能实现

对单链表的打印

插入数据:头插,尾插,删除某个数据

删除数据:头删,尾删,删除某个数据

查找数据

摧毁单链表

单链表的打印

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

创建一个指针来遍历链表,phead是链表的头节点,通过将phead 赋值给 pcur,就可以利用 pcur来逐个访问链表中的节点。这样能保持phead不变性,方便后续可能的其他操作,而影响phead的值,确保链表结构的完整性

热知识:while(pcur) 等同于 while(pcur != NULL)


注意

在实现之前,我们需要来了解传值传址的区别。

传值:

  • 是将实参的值复制一份传递给形参。
  • 在函数内部对形参的修改不会影响到外部的实参。

传址:

  • 传递的是实参的地址。
  • 函数内部可以通过该地址直接操作外部的实参。
  • 可以实现对外部数据的修改。

在插入,删除等操作时,需要传一级指针的地址,这就需要用二级指针来接收,若用一级指针来接收,则修改的数据不会影响外部的实参。

 插入数据

尾插

思路:首先创建一个newnode,然后进行插入。

情况(1).若头为空,插入的newnode则为头。

情况(2).若头不为空,需要将最后一个节点的下一个节点指向newnode即可。

//尾插
void SLTPushBack(SLTNode** pphead, SLTypeData 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;
	}
}

头插 

思路:创建一个newnode,将新节点的下一个节点指向头节点。

代码实现

//头插
void SLTPushFront(SLTNode** pphead, SLTypeData x)
{
	assert(*pphead);//不能对空指针进行解引用
	SLTNode* newnode = SLTBuyNode(x);
	newnode->next = *pphead;
	*pphead = newnode;
}

指定位置之前插入数据

思路:

创建一个newnode,对数据进行插入。

情况1.若头节点为空,pos成为头节点,进行头插。

情况2.若头节点为非空,这时需要创建变量prev来遍历链表,指向pos的前一个节点(prev),让前一个节点指向新节点(newnode),新节点指向pos。

//在指定位置之前插数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTypeData x)
{
	assert(pphead && *pphead);
	assert(pos);
	if (*pphead == pos)
	{
		SLTPushFront(pphead, x);
	}
	else
	{
		SLTNode* prev = *pphead;
		SLTNode* newnode = SLTBuyNode(x);
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		prev->next = newnode;
		newnode->next = pos;
		}
}

 指定位置之后插入数据

思路:

创建一个新节点(newnode),将pos newnode pos->next,三者连接起来,先将新节点的下一个节点指向pos的下一个节点(pos->next),然后让pos的下一个节点指向新节点。

//在指定位置之后插入数据
void SLTInsertAfter(SLTNode* pos, SLTypeData x)
{
	assert(pos);
	SLTNode* newnode = SLTBuyNode(x);
	/*	pos newnode pos->next;*/
    //代码1
	newnode->next = pos->next;
	pos->next = newnode;
    //代码2
    //pos->next = newnode;
    //newnode->next = pos->next;
}

代码2和代码1一样,只是顺序不一样,那么代码1是否能代替代码2呢?

答案是不能,如果先将pos的next指向新节点,那么pos->next,待会酒就会找不到,所以这个代码2是不对的。

删除数据

头删

思路:创建一个变量*phead来接收*pphead,然后让*pphead指向下一个节点((*pphead)->next),随后释放phead,将其置为NULL。

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

尾删 

思路:

情况1.单节点,即头节直接将头节点释放,置为空。

情况2.多节点,创建两个变量,prev和ptail,遍历链表找到尾节点ptail和尾节点的前一节点prev,将prev的指向的下一个节点置为空,释放ptail,置为空。

//尾删
void SLTPopBack(SLTNode** pphead)
{
	assert(*pphead && pphead);
	//单节点
	
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	//多节点
	else
	{
		SLTNode* ptail = *pphead;
		SLTNode* prev = *pphead;
		while (ptail->next)
		{
			prev = ptail;
			ptail = ptail->next;
		}
		prev->next = NULL;
		free(ptail);
		ptail = NULL;
	}

}

删除指定位置的数据

思路:

情况1.单节点,头节点与pos相等就是头删。

情况2.多节点,创建一个变量prev赋值为*pphead,遍历pos的前一个节点prev,将prev的下一个节点指向pos->next。

// 删除pos节点
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
	assert(pphead && *pphead);
	assert(pos);
	if (*pphead == pos)
	{
		SLTPopFront(pphead);
	}
	else
	{
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		//prev pos pos->next
		prev->next = pos->next;
		free(pos);
		pos = NULL;
	}
}

 删除指定位置之后的数据

思路:

创建一个变量del=pos->next,将pos,del,pos->next三者连接起来,pos下一个节点指向del的下一节点。最后释放del,置为空。

//删除pos之后的节点
void SLTEraseAfter(SLTNode* pos)
{
	assert(pos && pos->next);//对pos和pos->next断言,不存在办法删
	SLTNode* del = pos->next;
	//pos del del->next
	pos->next = del->next;
	free(del);
	del = NULL;
}

 摧毁单链表

思路:

创建一个变量pcur=*pphead,随后进行遍历,每次先将pcur->next,存在next里,释放pcur,pcur =next,最后头节点置为空。

//摧毁链表
void SListDesTroy(SLTNode** pphead)
{
	assert(pphead && *pphead);
	SLTNode* pcur = *pphead;
	while (pcur != NULL)
	{
		SLTNode*	next = pcur->next;
		free(pcur);
		pcur = next;
	}
	*pphead = NULL;
}

完整代码

Slist.h

#pragma once
#include<stdio.h>
#include<assert.h>
#include<string.h>
#include<stdlib.h>
typedef int SLTypeData;
typedef  struct SListNode
{
	SLTypeData data; //节点数据
	struct SListNode* next; //指针变量?保存下?个节点的地址
}SLTNode;

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

Slist.c 

#include"SList.h"

void SLTPrint(SLTNode* phead)
{
	SLTNode* pcur = phead;
	while(pcur)//pcur != NULL
	{
		printf("%d", pcur->data);
		printf("->");
		pcur = pcur->next;
	}
	printf("NULL\n");
}
//创建链表
SLTNode* SLTBuyNode(SLTypeData x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	if (newnode == NULL)
	{
		perror("malloc error");
		return NULL;
	}

	newnode->data = x;
	newnode->next = NULL;
	return newnode;
}
//尾插
void SLTPushBack(SLTNode** pphead, SLTypeData 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, SLTypeData x)
{
	assert(*pphead);
	SLTNode* newnode = SLTBuyNode(x);
	newnode->next = *pphead;
	*pphead = newnode;
}
//尾删
void SLTPopBack(SLTNode** pphead)
{
	assert(*pphead && pphead);
	//单节点
	
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	//多节点
	else
	{
		SLTNode* ptail = *pphead;
		SLTNode* prev = *pphead;
		while (ptail->next)
		{
			prev = ptail;
			ptail = ptail->next;
		}
		prev->next = NULL;
		free(ptail);
		ptail = NULL;
	}

}
//头删
void SLTPopFront(SLTNode** pphead)
{
	assert(pphead && *pphead);
	SLTNode* phead = *pphead;
	*pphead = (*pphead)->next;
	free(phead);
	phead = NULL;
}
// 查找
SLTNode* SLTFind(SLTNode* phead, SLTypeData x)
{
	SLTNode* pcur = phead;
	while (pcur)
	{
		if (pcur->data == x) 
		{
			return pcur;
		}
		pcur = pcur->next;
	}
	return NULL;
}
//在指定位置之前插?数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTypeData x)
{
	assert(pphead && *pphead);
	assert(pos);
	if (*pphead == pos)
	{
		SLTPushFront(pphead, x);
	}
	else
	{
		SLTNode* prev = *pphead;
		SLTNode* newnode = SLTBuyNode(x);
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		prev->next = newnode;
		newnode->next = pos;
		}
}
// 删除pos节点
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
	assert(pphead && *pphead);
	assert(pos);
	if (*pphead == pos)
	{
		SLTPopFront(pphead);
	}
	else
	{
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		//prev pos pos->next
		prev->next = pos->next;
		free(pos);
		pos = NULL;
	}
}
//在指定位置之后插入数据
void SLTInsertAfter(SLTNode* pos, SLTypeData x)
{
	assert(pos);
	SLTNode* newnode = SLTBuyNode(x);
	/*	pos newnode pos->next;*/
	newnode->next = pos->next;
	pos->next = newnode;
}
//删除pos之后的节点
void SLTEraseAfter(SLTNode* pos)
{
	assert(pos && pos->next);//对pos和pos->next断言,不存在办法删
	SLTNode* del = pos->next;
	//pos del del - next
	pos->next = del->next;
	free(del);
	del = NULL;
}
//摧毁链表
void SListDesTroy(SLTNode** pphead)
{
	assert(pphead && *pphead);
	SLTNode* pcur = *pphead;
	while (pcur != NULL)
	{
		SLTNode*	next = pcur->next;
		free(pcur);
		pcur = next;
	}
	*pphead = NULL;
}

 Test.c

#include"SList.h"

void test1()
{
	SLTNode* plist = NULL;
	SLTPushBack(&plist, 1);
	SLTPushBack(&plist, 2);
	SLTPushBack(&plist, 3);
	SLTPrint(plist); // 1->2->3->NULL
	
	SLTPushFront(&plist, 4);
	SLTPushFront(&plist, 5);
	SLTPushFront(&plist, 6);
	SLTPrint(plist);

	SLTPopBack(&plist);
	SLTPopBack(&plist);
	SLTPrint(plist);

	SLTPopFront(&plist);
	SLTPopFront(&plist);
	SLTPrint(plist);

	SLTNode* ret = SLTFind(plist, 1);
	if (ret == NULL)
	{
		printf("没找到");
	}
	else
	{
		printf("找到啦");
	}
	SLTInsert(&plist, ret, 5);
	SLTErase(&plist, ret);
	SLTInsertAfter(ret, 6);
	SLTEraseAfter(ret);
	SLTPrint(plist);
	SListDesTroy
}

int main()
{
	test1();
	return 0;
 }

感谢各位大佬的关注,点赞,评论 

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

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

相关文章

Python界面编辑器Tkinter布局助手 使用体验

一、发现 我今天在网上搜关于Python Tkinter方面的信息时&#xff0c;发现了Python界面编辑器 Tkinter布局助手 的使用说明。 https://blog.csdn.net/weixin_52777652/article/details/135291731?spm1001.2014.3001.5506 这个编辑器是个开源的项目&#xff0c;个人用户可以…

Python学习笔记7:入门知识(七)

前言 之前说过我更换了新的学习路线&#xff0c;现在是根据官方文档和书籍Python crash course来进行学习的&#xff0c;在目前的学习中&#xff0c;对于之前的知识有一些遗漏&#xff0c;这里进行补充。 学习资料有两个&#xff0c;书籍中文版PDF&#xff0c;关注我私信发送…

Lua实现自定义函数面向对象编程

本文目录 1、引言2、原理3、实例4、层析验证 文章对应视频教程&#xff1a; 暂无&#xff0c;可以关注我的B站账号等待更新。 点击图片或链接访问我的B站主页~~~ 1、引言 在现代软件开发中&#xff0c;面向对象编程&#xff08;OOP&#xff09;已经成为一种广泛使用的编程范式…

nodejs 某音douyin网页端搜索接口及x_bogus、a_bogus(包含完整源码)(2024-06-13)

前言 x_bogus或a_bogus算法大概是对数据、ua、时间戳、浏览器的几个指纹进行计算&#xff0c;拿到一个110位大数组&#xff0c;然后转字符&#xff0c;在头部再添加十二位随机字符&#xff0c;再进行魔改的base64加密。 问&#xff1a;抖音的x_bogus、a_bogus值有什么用&#x…

幸狐RV1106开发板烧录Ubuntu系统与配置SDK,RV1106 LuckFox Pico Max——最新的操作

资料&#xff1a;上手教程 | LUCKFOX WIKI 以及SDK内的文档资料 开发板型号&#xff1a;RV1106 LuckFox Pico Max 烧录系统&#xff1a; Ubuntu 虚拟机系统&#xff1a;Ubuntu 20.04&&Ubuntu22.04 PC系统&#xff1a;win11 占用空间&#xff1a;大概15G 本文主要记…

idea有这个类却报红,无法用快捷键找到

idea有这个类却报红&#xff0c;无法用快捷键找到&#xff0c;但是项目启动却没有任何问题&#xff0c;严重影响到了开发效率&#xff0c;关idea 重新打开没有用。 找了一圈&#xff0c;办法如下&#xff1a; 1、点击左上角的 File—>Invalidate Caches/Restar 2、点击 In…

【Linux】进程控制3——进程程序替换

一&#xff0c;前言 创建子进程的目的之一就是为了代劳父进程执行父进程的部分代码&#xff0c;也就是说本质上来说父子进程都是执行的同一个代码段的数据&#xff0c;在子进程修改数据的时候进行写时拷贝修改数据段的部分数据。 但是还有一个目的——将子进程在运行时指向一个…

Python私教张大鹏 Vue3整合AntDesignVue之DatePicker 日期选择框

案例&#xff1a;选择日期 <script setup> import {ref} from "vue";const date ref(null) </script> <template><div class"p-8 bg-indigo-50 text-center"><a-date-picker v-model:value"date"/><a-divide…

visio绘制直线

1、右键打开绘图工具 2、选择线条 3、画直线、画横线

【推荐算法的评估与调试】离线评估+在线A/B Test

文章目录 1、离线评估1.1、评估排序算法1.1.1、AUC和GAUC1.1.2、NDCG 1.2、评估召回算法1.2.1、Precision&Recall1.2.2、MAP1.2.3、Hit Rate1.2.4、持续评估 2、在线评估2.1、线上&#xff1a;流量划分2.1.1、根据User ID划分流量2.1.2、分层重叠划分流量2.1.3、A/A实验的重…

最新下载:Paragon NTFS for Mac 15【软件附加安装教程】

NTFS For Mac 15是首个支持Mac上读写NTFS外置存储设备解决方案 &#xff0c;解决mac不能读写外置让您更加简单直观的在Mac机上随意对NTFS文件修改、删除等操作。 安 装 包 获 取 地 址&#xff1a; Paragon Ntfs For Mac 15版&#xff1a;​​https://souurl.cn/mqM9C6​​ 软…

python:faces swap

# encoding: utf-8 # 版权所有 2024 ©涂聚文有限公司 # 许可信息查看&#xff1a; 两个头像图片之间换脸 # 描述&#xff1a; https://stackoverflow.com/questions/902761/saving-a-numpy-array-as-an-image?answertabvotes # Author : geovindu,Geovin Du 涂聚文. #…

(几何:六边形面积)编写程序,提示用户输入六边形的边长,然后显示它的面积。

(几何:六边形面积)编写程序&#xff0c;提示用户输入六边形的边长&#xff0c;然后显示它的面积。计 算六边形面积的公式是: 这里的s就是边长。下面是一个运行示例 package myjava; import java.math.*; import java.util.Scanner; public class cy {public static void main(S…

数据结构笔记-2、线性表

2.1、线性表的定义和基本操作 如有侵权请联系删除。 2.1.1、线性表的定义&#xff1a; ​ 线性表是具有相同数据类型的 n (n>0) 个数据元素的有限序列&#xff0c;其中 n 为表长&#xff0c;当 n 0 时线性表是一个空表。若用 L 命名线性表&#xff0c;则其一般表示为&am…

STM32理论 —— μCOS-Ⅲ(2/2):时间管理、消息队列、信号量、任务内嵌信号量/队列、事件标志、软件定时器

文章目录 9. 时间管理9.1 OSTimeDly()9.2 OSTimeDlyHMSM()9.3 OSTimeDlyResume()9.4 延时函数实验 10. 消息队列10.1 创建消息队列函数OSQCreate()10.2 发送消息到消息队列函数(写入队列)OSQPost()10.3 获取消息队列中的消息函数(读出队列)OSQPend()10.4 消息队列操作实验 11. …

【因果推断python】32_合成控制2

目录 合成控制作为线性回归的一种实现​编辑 合成控制作为线性回归的一种实现 为了估计综合控制的治疗效果&#xff0c;我们将尝试构建一个类似于干预期之前的治疗单元的“假单元”。然后&#xff0c;我们将看到这个“假单位”在干预后的表现。合成控制和它所模仿的单位之间的…

Apollo配置中心最佳实践

携程配置中心地址&#xff1a;GitCode - 全球开发者的开源社区,开源代码托管平台 1.1 Apollo配置中心介绍 Apollo&#xff08;阿波罗&#xff09;是开源配置管理中心&#xff0c;能够集中化管理应用不同环境、不同集群的配置&#xff0c;配置修改后能够实时推送到应用端…

又一个对标Sora的AI视频工具,Dream Machine,开始免费试用

新的AI视频工具又又来了-Dream Machine&#xff0c;开始免费试用。 地址&#xff1a;https://lumalabs.ai/dream-machine 该工具需要科学上网 先看一下官网的宣传视频 luma AI 发布 Dream Machine 我生成了几个视频&#xff0c;效果还可以 生成视频很简单 只需要输入描述就可…

高等数学笔记(一):映射与函数

一、映射 1.1 映射的概念 存在一个法则 f &#xff0c;使得对 X 中每个元素 x &#xff0c;在 Y 中有唯一确定的元素 y 与之对应&#xff08;X、Y 非空集&#xff09; 称 f 为从 X 到 Y 的映射&#xff0c;如图所示 其中 y 称为元素 x&#xff08;在映射 f 下&#xff09;的…