单链表的详解实现

news2024/11/7 21:13:12

单链表

结构

单链表结构中有两个数据,一个是存储数据的,还有一个指针指向下一个节点。

在这里插入图片描述
该图就是一个简单单链表的结构图。

接口实现

SLNode* CreateNode(SLNDataType x);//申请节点
void SLTprint(SLNode* head);//打印链表
void SLTPushBack(SLNode** pphead, SLNDataType x);//尾插
void SLTPushFront(SLNode** pphead, SLNDataType x);//头插
void SLTPopBack(SLNode** pphead);//尾删
void SLTPopFront(SLNode** pphead);//头删
SLNode* SLTFind(SLNode* phead, SLNDataType x);//查找
SLNode* SLTInsert(SLNode** pphead, SLNode* pos, SLNDataType x);//任意位置插入
void SLTErase(SLNode** pphead, SLNode* pos);//任意位置删除
void SLTDestroy(SLNode** pphead);//销毁链表

结构体的定义

typedef int SLNDataType;
typedef struct SListNode
{
	SLNDataType val;
	struct SList* next;
}SLNode;

首先,我们需要定义这样一个结构体,用来实现链表。本章禁用数字来实现,想用链表写一些学生信息的朋友们可以参考一下。

申请节点

SLNode* CreateNode(SLNDataType x)
{
	SLNode* newnode=(SLNode*)malloc(sizeof(SLNode));//动态申请内存SLNode大小即可,存放到SLNode* 的指针中去
	if (newnode == NULL)//如果申请失败,则提醒一下
	{
		perror("malloc fail");
		exit("-1");
	}
	newnode->val = x;//申请成功后,将该数据存放到该指针指向的val中去
	newnode->next = NULL;//将下一个节点设置为空指针
	return newnode;//返回值为该节点
}

首先,我们插入一个数据的时候,必须要开辟一个空间,也就是申请一个节点用来存放数据,所以单独封装这个函数来实现这个功能。

链表的打印

void SLTprint(SLNode* head)
{
	assert(head);//断言保证传入指针非空
	SLNode* cur = head;
	while (cur!=NULL)//打印循环
	{
		printf("%d->", cur->val);//打印当前指针指向的val值
		cur = cur->next;//将该指针指向的下一个指针的地址赋给该指针,实现链表的遍历
	}
	printf("NULL\n");
}

我们接受该指针,之后检查是否为空,把该指针重新赋给一个SLNode* 类型的cur进入打印循环,循环结束条件为尾指针指向的指针不为空指针即可。

尾插

我们插入删除数据时候,需要注意的一点是,我们需要遍历修改的时一级指针,那么我们就要用二级指针去接受这个参数了。

void SLTPushBack(SLNode** pphead, SLNDataType x)
{
	assert(pphead);//断言非空
	SLNode* newnode = CreateNode(x);//将新开辟的节点赋给newnode
	if (*pphead == NULL)//链表为空
	{
		*pphead = newnode;//直接将newnode赋给头节点
	}
	else
	{
		SLNode* tmp = *pphead;//将头节点给一个临时变量
		while (tmp->next != NULL)//遍历循环
		{
			tmp = tmp->next;//可实现链表移动从而遍历
		}
		tmp->next = newnode;//让最后一个节点指向newnode即可
	}
}

我们实现尾部插入,有两种情况,首先就是如果该链表为空,也就是头节点指向的是空指针,那么我们直接将新开辟的节点赋给头节点即可,其他的情况,首先需要遍历链表到最后一个节点,然后让最后一个节点指向新开辟的节点即可实现尾部插入。

头插

void SLTPushFront(SLNode** pphead, SLNDataType x)
{
	assert(pphead);//断言非空
	SLNode* newnode = CreateNode(x);//将新开辟的节点赋给newnode节点
	newnode->next = *pphead;//新开辟的节点指向的是pphead节点
	*pphead = newnode;//newnode成为新的头节点
}

实现头插是十分简单的,直接看代码注释即可

尾删

void SLTPopBack(SLNode** pphead)
{
	assert(*pphead);//保证头节点不为空指针
	if ((*pphead)->next == NULL)//仅有一个节点的情况
	{
		free(*pphead);//释放掉头节点
		*pphead = NULL;//并将其赋值为空指针即可
	}
	else//多个节点的情况
	{
		SLNode* prev = NULL;//双指针移动之实际指针
		SLNode* tail = *pphead;//双指针移动之遍历指针
		while (tail->next!= NULL)//遍历循环,结束条件为tail的下一个节点不为空指针
		{
			prev = tail;//将遍历指针赋值给实际指针
			tail = tail->next;//遍历指针向后移动
		}
//循环结束,此时实际指针指向倒数第二个节点,而遍历指针指向最后一个节点
		free(tail);//释放掉此时指向最后一个节点的遍历节点
		tail = NULL;//将释放后的遍历指针赋值为空指针
		prev->next = NULL;//实际指针变成最后一个指针,最后一个指针指向的是空指针
	}
}

尾删,我们需要分为两种情况,首先是该链表只有一个头节点,这种情况直接将头节点释放掉在赋值为空指针即可,另外的就是该链表有多个节点,那么此时我们选哟做到的就是,遍历到最后一个节点,之后将该节点释放掉即可,这里我们用双指针的移动来实现释放节点。

头删

void SLTPopFront(SLNode** pphead)
{
	assert(*pphead);//保证头节点不为空
	SLNode* tmp = *pphead;//将头节点赋给临时节点
	(*pphead) = tmp->next;//临时节点也就是头节点指向的下一个节点赋给头节点成为新的头节点
	free(tmp);//释放掉已经不是头节点的节点
	tmp = NULL;//将释放掉的节点赋值为空指针
}

头删十分简单,直接看代码注释即可。

查找

SLNode* SLTFind(SLNode* phead, SLNDataType x)
{
	if (phead == NULL)//头节点为空指针,也就是链表为空无需打印
	{
		printf("链表为空无需查找");
		return NULL;//返回空指针
	}
	else//链表不为空
	{
		SLNode* cur = phead;//将头节点赋给临时变量cur
		while (cur)//遍历循环
		{
			if (cur->val== x)//如果遍历的数据是所需要的数据
			{
				return cur;//返沪该指针
			}
			cur = cur->next;//如果不是,则移动临时变量cur实现链表的遍历
		}
	}
	return NULL;//找完没有,则返回空指针
}

查找的实现也很简单,如果链表为空,那么我们就无需查找,返回空指针即可,不为空,我们遍历找到需要数据存放的节点,之后返回这个节点就可以了,如果遍历完没有找到,就说明不存在该节点返回空指针即可。

任意位置的插入

SLNode* SLTInsert(SLNode** pphead, SLNode* pos, SLNDataType x)
{
	assert(pphead);//断言非空
	if (*pphead == pos)//头插情况
	{
		SLTPushFront(pphead,x);
	}
	else//非头部插入情况
	{
		SLNode* newnode = CreateNode(x);//新开辟的节点赋给newnode指针
		SLNode* cur = *pphead;//头节点给临时变量cur
		while (cur->next != pos)//遍历到pos前一个位置
		{
			cur = cur->next;//遍历移动
		}
		cur->next = newnode;//此时让pos位置前一个节点指向要插入的节点
		newnode->next = pos;//要插入的节点指向之后的节点,实现链表的完整
	}
}

首先,如果插入的位置是头节点,那么也就是头插了,如果是其他位置,那么首先需要遍历到该位置的前一个节点,让该节点指向新插入的节点,新插入的节点在指向原来该位置的节点就实现了该位置的数据插入。

任意位置的删除

void SLTErase(SLNode** pphead, SLNode* pos)
{
	assert(pphead);//断言非空
	if (*pphead == pos)//头删情况
	{
		SLTPopFront(pphead);
	}
	else//非头删情况
	{
		SLNode* cur = *pphead;//头节点赋给临时变量cur
		while (cur->next != pos)//遍历到pos位置的前一个节点循环
		{
			cur = cur->next;//临时变量移动,链表的遍历
		}
		cur->next = pos->next;//pos位置的前一个节点直线pos的下一个节点
		free(pos);//释放pos
		pos = NULL;//pos赋值为空指针
	}
}

首先,也是如果删除的节点是头节点,直接头删即可,不是则遍历到该位置的前一个位置,跳过该位置指向下一个节点,再将该位置节点释放掉即可。

链表的销毁

void SLTDestroy(SLNode** pphead)
{
	assert(pphead);
	SLNode* cur = *pphead;
	while (cur)
	{
		SLNode* pur = cur;
		cur = cur->next;
		free(pur);
	}
	printf("链表已经成功销毁!");
}

链表的销毁十分简单,遍历释放即可。

代码参考

鄙人不才,大家可以参考一下链表代码的实现

SList.h

#pragma once
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>

typedef int SLNDataType;
typedef struct SListNode
{
	SLNDataType val;
	struct SList* next;
}SLNode;
SLNode* CreateNode(SLNDataType x);//申请节点
void SLTprint(SLNode* head);//打印链表
void SLTPushBack(SLNode** pphead, SLNDataType x);//尾插
void SLTPushFront(SLNode** pphead, SLNDataType x);//头插
void SLTPopBack(SLNode** pphead);//尾删
void SLTPopFront(SLNode** pphead);//头删
SLNode* SLTFind(SLNode* phead, SLNDataType x);//查找
SLNode* SLTInsert(SLNode** pphead, SLNode* pos, SLNDataType x);//任意位置插入
void SLTErase(SLNode** pphead, SLNode* pos);//任意位置删除
void SLTDestroy(SLNode** pphead);//销毁链表

SList.c

#define  _CRT_SECURE_NO_WARNINGS
#include"SList.h"
SLNode* CreateNode(SLNDataType x)
{
	SLNode* newnode=(SLNode*)malloc(sizeof(SLNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		exit("-1");
	}
	newnode->val = x;
	newnode->next = NULL;
	return newnode;
}
void SLTprint(SLNode* head)
{
	assert(head);
	SLNode* cur = head;
	while (cur!=NULL)
	{
		printf("%d->", cur->val);
		cur = cur->next;
	}
	printf("NULL\n");
}
void SLTPushBack(SLNode** pphead, SLNDataType x)
{
	SLNode* newnode = CreateNode(x);
	if (*pphead == NULL)
	{
		*pphead = newnode;
	}
	else
	{
		SLNode* tmp = *pphead;
		while (tmp->next != NULL)
		{
			tmp = tmp->next;
		}
		tmp->next = newnode;
	}
}
void SLTPushFront(SLNode** pphead, SLNDataType x)
{
	SLNode* newnode = CreateNode(x);
	newnode->next = *pphead;
	*pphead = newnode;
}
void SLTPopBack(SLNode** pphead)
{
	assert(*pphead);
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		SLNode* prev = NULL;
		SLNode* tail = *pphead;
		while (tail->next!= NULL)
		{
			prev = tail;
			tail = tail->next;
		}

		free(tail);
		tail = NULL;
		prev->next = NULL;
	}
}
void SLTPopFront(SLNode** pphead)
{
	assert(*pphead);
	SLNode* tmp = *pphead;
	(*pphead) = tmp->next;
	free(tmp);
	tmp = NULL;
}
SLNode* SLTFind(SLNode* phead, SLNDataType x)
{
	if (phead == NULL)
	{
		printf("链表为空无需查找");
		return NULL;
	}
	else
	{
		SLNode* cur = phead;
		while (cur)
		{
			if (cur->val== x)
			{
				return cur;
			}
			cur = cur->next;
		}
	}
	return NULL;
}
SLNode* SLTInsert(SLNode** pphead, SLNode* pos, SLNDataType x)
{
	assert(pphead);
	if (*pphead == pos)
	{
		SLTPushFront(pphead,x);
	}
	else
	{
		SLNode* newnode = CreateNode(x);
		SLNode* cur = *pphead;
		while (cur->next != pos)
		{
			cur = cur->next;
		}
		cur->next = newnode;
		newnode->next = pos;
	}
}
void SLTErase(SLNode** pphead, SLNode* pos)
{
	assert(pphead);
	if (*pphead == pos)
	{
		SLTPopFront(pphead);
	}
	else
	{
		SLNode* cur = *pphead;
		while (cur->next != pos)
		{
			cur = cur->next;
		}
		cur->next = pos->next;
		free(pos);
		pos = NULL;
	}
}
void SLTDestroy(SLNode** pphead)
{
	assert(pphead);
	SLNode* cur = *pphead;
	while (cur)
	{
		SLNode* pur = cur;
		cur = cur->next;
		free(pur);
	}
	printf("链表已经成功销毁!");
}

test.c

这个文件主要是为了检测链表代码是否有误,用于调试链表。

#define  _CRT_SECURE_NO_WARNINGS
#include"SList.h"
void test1()
{
	SLNode* S1=NULL;
	SLTPushBack(&S1, 1);
	SLTPushBack(&S1, 2);
	SLTPushBack(&S1, 3);
	SLTPushBack(&S1, 4);
	SLTprint(S1);
}
void test2()
{
	SLNode* S1 = NULL;
	SLTPushBack(&S1, 1);
	SLTPushBack(&S1, 2);
	SLTPushBack(&S1, 3);
	SLTPushBack(&S1, 4);
	SLTPushFront(&S1, 4);
	SLTprint(S1);
}
void test3()
{
	SLNode* S1 = NULL;
	SLTPushBack(&S1, 1);
	SLTPushBack(&S1, 2);
	SLTPushBack(&S1, 3);
	SLTPushBack(&S1, 4);
	SLTPushFront(&S1, 4);
	SLTPopBack(&S1);
	SLTprint(S1);
}
void test4()
{
	SLNode* S1 = NULL;
	SLTPushBack(&S1, 1);
	SLTPushBack(&S1, 2);
	SLTPushBack(&S1, 3);
	SLTPushBack(&S1, 4);
	SLTPushFront(&S1, 4);
	SLTPopBack(&S1);
	SLTPopFront(&S1);
	SLTprint(S1);
}
void test5()
{
	SLNode* S1 = NULL;
	SLTPushBack(&S1, 1);
	SLTPushBack(&S1, 2);
	SLTPushBack(&S1, 3);
	SLTPushBack(&S1, 4);
	SLTPushFront(&S1, 5);
	SLNode* x =SLTFind(S1,3);
	SLTprint(S1);
	printf("%d", x->val);
	
}
void test6()
{
	SLNode* S1 = NULL;
	SLTPushBack(&S1, 1);
	SLTPushBack(&S1, 2);
	SLTPushBack(&S1, 3);
	SLTPushBack(&S1, 4);
	SLTPushFront(&S1, 4);
	SLTInsert(&S1, S1, 5);
	SLNode* S2 = S1->next;
	SLTInsert(&S1, S2, 8);
	SLTprint(S1);
}
void test7()
{
	SLNode* S1 = NULL;
	SLTPushBack(&S1, 1);
	SLTPushBack(&S1, 2);
	SLTPushBack(&S1, 3);
	SLTPushBack(&S1, 4);
	SLTPushFront(&S1, 4);
	SLTInsert(&S1, S1, 5);
	SLNode* S2 = S1->next;
	SLTInsert(&S1, S2, 8);
	SLTErase(&S1, S1);
	SLTErase(&S1, S2);
	SLTprint(S1);
}
void test8()
{
	SLNode* S1 = NULL;
	SLTPushBack(&S1, 1);
	SLTPushBack(&S1, 2);
	SLTPushBack(&S1, 3);
	SLTPushBack(&S1, 4);
	SLTPushFront(&S1, 4);
	SLTInsert(&S1, S1, 5);
	SLNode* S2 = S1->next;
	SLTInsert(&S1, S2, 8);
	SLTErase(&S1, S1);
	SLTErase(&S1, S2);
	SLTprint(S1);
	SLTDestroy(&S1);
}
int main()
{
	/*test1();*/
	/*test2();*/
	/*test3();*/
	/*test4();*/
	/*test5();*/
	/*test6();*/
	/*test7();*/
	test8();
	return 0;
}

最后谢谢大家观看,以后会奉上更多内容,大家共同进步!

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

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

相关文章

【Echarts】玫瑰饼图数据交互

在学习echarts玫瑰饼图的过程中&#xff0c;了解到三种数据交互的方法&#xff0c;如果对您也有帮助&#xff0c;不胜欣喜。 一、官网教程 https://echarts.apache.org/examples/zh/editor.html?cpie-roseType-simple &#xff08;该教程数据在代码中&#xff09; import *…

springboot-2.7.3+ES-7.10.0

跟着官网走&#xff0c;能干99。一年几次变&#xff0c;次次不一样。刚部署好ES-6.8&#xff0c;又买阿里云Es-7.10.0根本忙不完。 做为JDK1.8最后一个版本。今天就拿新技术部署一套。致辞&#xff1a;大家以后就用这套好了。别轻易触发springboot3.0了 学习无止境&#xff1…

【使用Python编写游戏辅助工具】第三篇:鼠标连击器的实现

前言 这里是【使用Python编写游戏辅助工具】的第三篇&#xff1a;鼠标连击器的实现。本文主要介绍使用Python来实现鼠标连击功能。 鼠标连击是指在很短的时间内多次点击鼠标按钮&#xff0c;通常是鼠标左键。当触发鼠标连击时&#xff0c;鼠标按钮会迅速按下和释放多次&#xf…

言情小说怎么推广?如何推广网络小说?

网络小说是一种文学形式&#xff0c;它的受众群体相当广泛&#xff0c;其实也面临着很强的竞争&#xff0c;因此&#xff0c;网络推广是小说宣传的一项重要工作&#xff0c;这里小马识途营销顾问就分享一下小说推广的渠道和方法。 1、软文推广 在推广小说的过程中&#xff0c;…

面试10000次依然会问的【synchronized】,你还不会?

引言 synchronized 关键字是实现线程同步的核心工具&#xff0c;它能够确保在任一时刻&#xff0c;只有一个线程能够访问被同步的方法或代码块。 这不仅保证了操作的原子性&#xff0c;即这些操作要么完全执行&#xff0c;要么完全不执行&#xff1b;同时也确保了操作的可见性…

高效操作,轻松打造企业百度百科,展现实力形象

百度百科已经成为企业提升形象的重要渠道&#xff0c;拥有自己的百科词条意味着企业在互联网上拥有更高的知名度和可信度。接下来&#xff0c;将为大家介绍企业百度百科的创建过程和一些技巧&#xff0c;帮助企业更好地在百度百科上展现自身实力。 首先&#xff0c;创建企业百度…

基于Tensorflow卷积神经网络玉米病害识别系统(UI界面)

欢迎大家点赞、收藏、关注、评论啦 &#xff0c;由于篇幅有限&#xff0c;只展示了部分核心代码。 文章目录 一项目简介 二、功能三、系统四. 总结 一项目简介 Tensorflow是一个流行的机器学习框架&#xff0c;可用于训练和部署各种人工智能模型。玉米病害识别系统基于Tensorf…

明明用的不是自己机器视觉软件,甚至是盗版,机器视觉公司为什么还要申请那么多专利?

我首先看下专利是什么&#xff1f; 专利分为发明、实用新型、外观设计三种类型。 发明是指对产品、方法或者其改进所提出的新的技术方案。 实用新型是指对产品的形状构造或者其结合所提出的适于实用的新的技术方案。一般对日用品、机械、电器等产品的简单改进比较适用于申请…

Mysql数据目录结构以及文件类型解析

目录 1. 数据目录 2. Data目录 3. 数据库目录 1&#xff09;db.opt 2&#xff09;.frm 3&#xff09;.MYD和.MYI 4&#xff09;.ibd 5&#xff09;.ibd和.ibdata 在 MySQL 中&#xff0c;物理文件存放在数据目录中。数据目录与安装目录不同&#xff0c;安装目录用来存储…

NLP之Bert介绍和简单示例

文章目录 1. Bert 介绍2. 代码示例 1. Bert 介绍 2. 代码示例

Express框架开发接口之轮播图API

1.获取所有轮播图、 // 处理轮播图 const handleDB require(../handleDB/index) // 获取所有轮播图 exports.allCarousel (req, res) > {(async function () {let results await handleDB(res, "book_carousel", "find", "查询数据出错&#xf…

Python 生成Android不同尺寸的图标

源代码 # -*- coding: utf-8 -*- import sys import os import shutil from PIL import Imagedef generateAndroidIcons():imageSource icon.pngicon Image.open(imageSource)sizes [(android/drawable,512),(android/drawable-hdpi,72),(android/drawable-ldpi,36),(andro…

C# 发送邮件

1.安装 NuGet 包 2.代码如下 SendMailUtil using MimeKit; using Srm.CMER.Application.Contracts.CmerInfo; namespace Srm.Mail { public class SendMailUtil { public async static Task<string> SendEmail(SendEmialDto sendEmialDto,List<strin…

11月2日星期四今日早报简报微语报早读

11月2日星期四&#xff0c;农历九月十九&#xff0c;早报微语早读分享。 1、茅台深夜提价&#xff1a;11月1日起飞天、五星出厂价格平均上调约20&#xff05;&#xff0c;贵州茅台&#xff1a;市场指导价不变&#xff1b; 2、杭州拟发文规范直播电商业&#xff1a;不得要求商…

2015年亚太杯APMCM数学建模大赛C题识别网络中的错误连接求解全过程文档及程序

2015年亚太杯APMCM数学建模大赛 C题 识别网络中的错误连接 原题再现 网络是描述真实系统结构的强大工具——社交网络描述人与人之间的关系&#xff0c;万维网描述网页之间的超链接关系。随着现代技术的发展&#xff0c;我们积累了越来越多的网络数据&#xff0c;但这些数据部…

vs2013/2015/2019扩展-联机提示“未能建立到服务器的连接“/“基础连接已经关闭: 发送时发生错误“/“远程主机强迫关闭了一个现有的连接“

VS2013\VS2015 输入命令 [Net.ServicePointManager]::SecurityProtocol[Net.ServicePointManager]::SecurityProtocol-bOR [Net.SecurityProtocolType]::Tls12 采用上述方法偶尔可以有效&#xff0c;重新启动VS就没用了 VS2019 怎么样都不行 最终解决办法&#xff1a;换一…

CRM系统如何帮助企业实现管理信息化?

21世纪的今天&#xff0c;企业不重视CRM信息化会导致什么后果&#xff1f;我们先来看这个例子—— 假设有一家中小型电子商务公司&#xff0c;他们销售各种电子产品&#xff0c;如手机、平板、电脑和配件等。在开始使用CRM系统之前&#xff0c;他们的客户数据分散在各个部门的…

自己动手实现一个深度学习算法——三、神经网络的学习

文章目录 1.从数据中学习1&#xff09;数据驱动2&#xff09;训练数据和测试数据 2.损失函数1)均方误差2)交叉熵误差3)mini-batch学习 3.数值微分1&#xff09;概念2&#xff09;数值微分实现 4.梯度1&#xff09;实现2&#xff09;梯度法3&#xff09;梯度法实现4&#xff09;…

kvm--存储挂载

创建存储卷 然后后面分别挂载到虚拟机不同目录下 查看磁盘 格式化&#xff08;需要挂载的分区或磁盘&#xff09; 获得UUID 挂载磁盘或分区 开机自动挂载 vim /etc/fstab mount -a 不报错就可以了

linux驱动学习加强版-7(平台虚拟总线的引入)

文章目录 一、为什么要引入平台虚拟总线二、平台虚拟总线架构三、使用platform框架去写一个驱动 一、为什么要引入平台虚拟总线 Linux platform driver机制和传统的device_driver机制相比&#xff0c;一个十分明显的优势在于platform机制将本身的资源注册进内核&#xff0c;由…