C语言之旅:探索单链表

news2025/4/6 11:31:54

目录

一、前言

二、实现链表的功能:

打印

创建节点

尾插

尾删

头插

头删

查找

在指定位置之前插入数据

指定位置删除

在指定位置之后插入数据

打印

销毁

三、全部源码: 

四、结语


一、前言

链表是一个强大且基础的数据结构。对于很多初学者来说,链表可能是一个令人望而生畏的主题,因为它涉及到了动态内存分配和指针操作等较为高级的概念。但是,一旦你掌握了链表的基本概念和操作,你会发现它在很多实际应用中都有着广泛的应用。

今天,我们就从C语言的角度,来探索一下单链表的基本概念和实现方法。在接下来的篇幅中,我将带你逐步构建一个单链表的简单实现,并解释其中的关键概念和代码逻辑。 

首先,我们需要明确什么是单链表。单链表是一种线性数据结构,它的元素(在C语言中通常称为节点)按照线性的顺序排列,但每个节点并不是在内存中连续存储的。相反,每个节点都包含两个部分:一个是数据域(用于存储实际的数据),另一个是指针域(用于指向下一个节点)。通过这种方式,我们可以将多个节点连接起来,形成一个链状的数据结构。

单链表的定义:
 

typedef int SLTDataType;
typedef struct SListNode
{
	SLTDataType data;
	struct SListNode* 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);

打印

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

创建节点

SLTNode* SLTBuyNode(SLTDataType x) 
{
	SLTNode* newNode = (SLTNode*)malloc(sizeof(SLTNode));
	if (newNode == NULL) 
	{
		perror("malloc fail!");
		exit(1);
	}
	newNode->data = x;
	newNode->next = NULL;
	return newNode;
}

尾插

void SLTPushBack(SLTNode** pphead, SLTDataType x) 
{
	SLTNode* newNode = SLTBuyNode(x);

	// 如果链表为空,直接让头指针指向新节点  
	if (*pphead == NULL) {
		*pphead = newNode;
		return;
	}

	// 否则,找到链表尾部并插入新节点  
	SLTNode* current = *pphead;
	while (current->next != NULL)
	{
		current = current->next;
	}
	current->next = newNode;
}

尾删

//尾删
void SLTPopBack(SLTNode** pphead)
{
	assert(pphead && *pphead);
	SLTNode* prev = NULL;
	SLTNode* current = *pphead;
	while (current->next != NULL)
	{
		prev = current;
		current = current->next;
	}
	free(current);
	if (prev == NULL)
	{
		*pphead = NULL;
	}
	else
	{
		prev->next = NULL;
	}
}

1.  首先,检查传入的二级指针和其指向的头指针是否为空,以确保链表存在。
2.  接着,定义了两个指针prev和current,用于遍历链表并找到尾节点。
在循环中,prev总是指向current的前一个节点,而current则遍历链表直到找到尾节点(即current->next为NULL的节点)。
3.  找到尾节点后,释放其内存。
4.  最后,根据prev是否为空来判断链表是否只有一个节点。如果是,则将头指针置为NULL;否则,将prev的next指针置为NULL,完成尾节点的删除。 

头插

//头插
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);
	SLTNode* newNode = SLTBuyNode(x);
	  
	newNode->next = *pphead;
	
	*pphead = newNode;

}

我们首先通过断言确保传入的二级指针pphead不为空。接着,我们调用SLTBuyNode函数来根据给定的数据x创建一个新的节点newNode。然后,我们将新节点的next指针设置为当前的头节点*pphead,这样新节点就连接到了链表的开始位置。最后,我们更新头指针*pphead,使其指向新节点,从而完成了在链表头部插入新节点的操作。 

头删

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

定义一个临时指针tmp,让它指向当前头节点的下一个节点。接着,我们释放当前头节点的内存。最后,我们更新头指针*pphead,使其指向tmp, 从而完成了头节点的删除操作。如果链表原本只有一个节点,那么删除后头指针*pphead将被设置为NULL,表示链表为空。

查找

SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{
	SLTNode* pcur = phead;
	while (pcur != NULL)
	{
		if (pcur->data == x)
		{
			return pcur;

		}
		pcur = pcur->next;

	}
	return NULL;
}

使用一个指针pcur来遍历链表,从头节点开始,逐个检查每个节点的数据是否与要查找的数据x相等。如果找到了匹配的节点,就返回该节点的指针;如果遍历完整个链表都没有找到匹配的节点,则返回NULL表示未找到。

在指定位置之前插入数据

void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
	assert(pphead && *pphead);
	assert(pos);
	SLTNode* newnode = SLTBuyNode(x);
	if (pos == *pphead)
	{
		SLTPushFront(pphead, x);
	}
	else
	{
		// 找到pos节点的前一个节点  
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}

		// 在pos之前插入新节点  
		newnode->next = pos;
		prev->next = newnode;
	}
}

指定位置删除

void SLTErase(SLTNode** pphead, SLTNode* pos)
{
	assert(pphead && *pphead);
	assert(pos);
	if (pos == *pphead)
	{
		*pphead = pos->next;
	}
	else
	{
		SLTNode* prev = *pphead;
		while (prev->next != pos)// 遍历链表,找到要删除节点的前一个节点
		{
			prev = prev->next;
		}
		prev->next = pos->next;

	}

}

在指定位置之后插入数据

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

}

设置新节点的next指针指向pos的下一个节点。最后,它将pos的next指针指向新节点,从而将新节点插入到pos之后。 

打印

//打印
void SLPrint(SL* ps)
{
	for (int i = 0; i < ps->size; i++) 
	{
		printf("%d ", ps->arr[i]);
	}
	printf("\n");
}

销毁

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

三、全部源码: 

// 打印链表
void SLTPrint(SLTNode* phead)
{
	SLTNode* pucr = phead;
	while (pucr != NULL)
	{
		printf("%d->", pucr->data);
		pucr = pucr->next;
	}
	printf("NULL\n");
}
//初始化
SLTNode* SLTBuyNode(SLTDataType x) 
{
	SLTNode* newNode = (SLTNode*)malloc(sizeof(SLTNode));
	if (newNode == NULL) 
	{
		perror("malloc fail!");
		exit(1);
	}
	newNode->data = x;
	newNode->next = NULL;
	return newNode;
}
// 尾部插入  
void SLTPushBack(SLTNode** pphead, SLTDataType x) 
{
	SLTNode* newNode = SLTBuyNode(x);

	// 如果链表为空,直接让头指针指向新节点  
	if (*pphead == NULL) {
		*pphead = newNode;
		return;
	}

	// 否则,找到链表尾部并插入新节点  
	SLTNode* current = *pphead;
	while (current->next != NULL)
	{
		current = current->next;
	}
	current->next = newNode;
}
//尾删
void SLTPopBack(SLTNode** pphead)
{
	assert(pphead && *pphead);
	SLTNode* prev = NULL;
	SLTNode* current = *pphead;
	while (current->next != NULL)
	{
		prev = current;
		current = current->next;
	}
	free(current);
	if (prev == NULL)
	{
		*pphead = NULL;
	}
	else
	{
		prev->next = NULL;
	}
}

//头插
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);
	SLTNode* newNode = SLTBuyNode(x);
	  
	newNode->next = *pphead;
	
	*pphead = newNode;

}
//头删
void SLTPopFront(SLTNode** pphead)
{
	assert(pphead && *pphead);
	SLTNode* tmp = (*pphead)->next;
	free(*pphead);
	*pphead=tmp;
}
//查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{
	SLTNode* pcur = phead;
	while (pcur != NULL)
	{
		if (pcur->data == x)
		{
			return pcur;

		}
		pcur = pcur->next;

	}
	return NULL;
}
//在指定位置之前插入数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
	assert(pphead && *pphead);
	assert(pos);
	SLTNode* newnode = SLTBuyNode(x);
	if (pos == *pphead)
	{
		SLTPushFront(pphead, x);
	}
	else
	{
		// 找到pos节点的前一个节点  
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}

		// 在pos之前插入新节点  
		newnode->next = pos;
		prev->next = newnode;
	}
}
//删除pos节点
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
	assert(pphead && *pphead);
	assert(pos);
	if (pos == *pphead)
	{
		*pphead = pos->next;
	}
	else
	{
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		prev->next = pos->next;

	}

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

}

//删除pos之后的节点
void SLTEraseAfter(SLTNode* pos)
{
	assert(pos&&pos->next);
	SLTNode* del = pos->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;
}

四、结语

总之,C语言中的单链表之旅是一次富有成效的学习经历。它不仅让我们掌握了链表的基本操作,还提高了我们的编程技能和问题解决能力。希望这个旅程能为你在数据结构和算法领域的进一步探索打下坚实的基础。

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

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

相关文章

uniapp中父子组件的传值

1. uniapp中父子组件的传值 1.1. 父子组件的传值 通过props来实现, 子组件通过props来接收父组件传过来的值 1.1.1. 父组件 <!-- 父组件 --> <template><view><my-son :title"title" sendData"getSonData"></my-son><…

项目实战系列——WebSocket——websock简介

最近项目中需要用到mes和本地客户端进行实时通讯&#xff0c;本来想用webapi进行交互的&#xff0c;但是考虑到高效和实时性&#xff0c;就采用这一项技术。 以往采用的方式——长轮询 客户端主动向服务器发送一个请求&#xff0c;如果服务器没有更新的数据&#xff0c;客户端…

numpy的基本使用

一、NumPy 的主要特性和功能&#xff1a; 多维数组对象&#xff08;ndarray&#xff09;&#xff1a; NumPy 的核心是 ndarray 对象&#xff0c;它是一个多维数组&#xff0c;可以存储相同类型的元素。ndarray 对象具有固定大小&#xff0c;支持矢量化运算和广播功能&#xff0…

Vue3的ref创建一个全局变量,非常好用!

1. 前言 Vue3的ref对象我们都知道其用法,通过ref可以创建一个响应式对象使用,同时可以用compute,watch等Vue3的API对其进行操作 不同于Vue2的是,Vue3使用的是组合式API,这也就意味着,我可以在外部单独创建一个ref对象,然后保存,通过导出的方式,给其他的页面使用 理论存在,开始…

Python 机器学习 基础 之 算法链与管道 【通用的管道接口/网格搜索预处理步骤与模型参数/网格搜索选择使用哪个模型】的简单说明

Python 机器学习 基础 之 算法链与管道 【通用的管道接口/网格搜索预处理步骤与模型参数/网格搜索选择使用哪个模型】的简单说明 目录 Python 机器学习 基础 之 算法链与管道 【通用的管道接口/网格搜索预处理步骤与模型参数/网格搜索选择使用哪个模型】的简单说明 一、简单介…

【网络安全的神秘世界】Kali安装中文输入法

&#x1f31d;博客主页&#xff1a;泥菩萨 &#x1f496;专栏&#xff1a;Linux探索之旅 | 网络安全的神秘世界 | 专接本 今天就手把手教你如何在kali中安装和配置输入法 首先&#xff0c;打开终端&#xff0c;输入下面这行代码&#xff1a; # sudo apt install ibus ibus-pi…

Python第二语言(四、Python数据容器)

目录 一、 数据容器&#xff08;list、tuple、str、map、dict&#xff09; 1. 数据容器概念 2. 列表list&#xff08; [] &#xff09; 2.1 定义方式 2.2 嵌套列表 2.3 list通过获取下标索引获取值 2.4 下标使用概念 2.5 list列表的使用&#xff08;列表的方法&#xff…

【研发日记】Matlab/Simulink软件优化(二)——通信负载柔性均衡算法

文章目录 前言 背景介绍 初始代码 优化代码 分析和应用 总结 前言 见《【研发日记】Matlab/Simulink软件优化(一)——动态内存负荷压缩》 背景介绍 在一个嵌入式软件开发项目中&#xff0c;需要设计一个ECU节点的CAN网路数据发送&#xff0c;需求是在500k的通信波特率上&a…

特征工程技巧—Bert

前段时间在参加比赛&#xff0c;发现有一些比赛上公开的代码&#xff0c;其中的数据预处理步骤值得我们参考。 平常我们见到的都是数据预处理&#xff0c;现在我们来讲一下特征工程跟数据预处理的区别。 数据预处理是指对原始数据进行清洗、转换、缩放等操作&#xff0c;以便为…

制造执行MES系统在光伏行业的应用

全球对可再生能源的需求不断增长&#xff0c;光伏能源作为一种清洁、可持续的能源形式&#xff0c;已经在广泛应用中受到了广泛关注。为满足工业领域的光伏能源需求&#xff0c;光伏制造执行系统(MES)作为一种集成化的技术解决方案&#xff0c;提供了更高效、更可靠的解决方案。…

海外仓系统推荐:中小型海外仓和家庭海外仓如何低投入高营收

随着海外仓行业整体竞争的加剧&#xff0c;海外仓的管理和经营成本可以说也是水涨船高。这对一些集团性质的大型海外仓影响不大&#xff0c;因为他们可以通过规模效应来摊薄成本。 但是对中小型海外仓和一些家庭海外仓来说&#xff0c;影响将是巨大的。一方面&#xff0c;海外…

GLM-4开源版本终于发布!!性能超越Llama3,多模态媲美GPT-4V,MaaS平台全面升级

今天上午&#xff0c;在 AI 开放日上&#xff0c;备受关注的大模型公司智谱 AI 公布了一系列行业落地数据&#xff1a; 根据最新统计&#xff0c;智谱 AI 大模型开放平台目前已拥有 30 万注册用户&#xff0c;日均调用量达到 4000 亿 Tokens。GPT-4o深夜发布&#xff01;Plus免…

【重学C语言】十八、SDL2 图形编程介绍和环境配置

【重学C语言】十八、SDL2 图形编程介绍和环境配置 **SDL2介绍**SDL 2用途SDL 在哪些平台上运行&#xff1f;下载和安装 SDL2安装 SDL2 clion 配置 SDL2 SDL2介绍 SDL2&#xff08;Simple DirectMedia Layer 2&#xff09;是一个开源的跨平台多媒体开发库&#xff0c;主要用于游…

Cortex系列详解

Cortex系列属于ARMv7架构(ARM公司在经典处理器ARM11以后的产品改用Cortex命名) 一、Cortex-A系列 “A”系列面向尖端的基于虚拟内存的操作系统和用户应用。 A 系列处理器适用于具有高计算要求、运行丰富操作系统以及提供交互媒体和图形体验的应用领域。 具体案例如:智能手…

2024百度之星 跑步

原题链接&#xff1a;码题集OJ-跑步 题目大意&#xff1a;一个n个人在绕圈跑&#xff0c;第i个人跑一圈的时间是i分钟&#xff0c;每二个人位置相同就会打一次招呼&#xff0c;如果同时来到终点&#xff0c;他们就会停下来&#xff0c;请问会打多少次招呼&#xff1f; 思路&a…

文件加密软件排行榜前五名|好用的五款文件加密软件分享

你的公司是否存在这些问题&#xff1a; 数据泄露事件常有发生&#xff0c;数据安全的重要性日益凸显&#xff0c;而文件加密软件则是保护数据安全的重要工具。 市场上存在众多文件加密软件&#xff0c;每款都有其独特的特点和优势。 本文将为您分享五款好用的文件加密软件&…

java通过ftp上传文件到服务器

依赖 <!-- https://mvnrepository.com/artifact/com.jcraft/jsch --><dependency><groupId>com.jcraft</groupId><artifactId>jsch</artifactId><version>0.1.55</version></dependency> 代码 package com.cyz; impor…

Docker高级篇之Dockerfile解析

文章目录 1. DockerFile简介2. DockerFile的构建过程3. DockerFile的常用保留字4. 使用案例5. 虚悬镜像 1. DockerFile简介 DockerFile是用来构建Docker镜像的文本文件&#xff0c;是由一条条构建镜像的指令和参数构成的脚本。 2. DockerFile的构建过程 DockerFile内容的基…

2.3 OpenCV随手简记(四)

阈值处理是很多高级算法底层处理的预方法之一。 自己求图像平均阈值&#xff1a; # -*- codingGBK -*- import cv2 as cv import numpy as np #求出图像均值作为阈值来二值化 def custom_image(image): gray cv.cvtColor(image, cv.COLOR_BGR2GRAY) cv.imshow("原来&qu…

精选网络安全书单:打造数字世界的钢铁长城!

目录 1.前言 2.书单推荐 2.1. 《内网渗透实战攻略》 2.2. 《Kali Linux高级渗透测试&#xff08;原书第4版&#xff09;》 2.3. 《CTF那些事儿》 2.4. 《权限提升技术&#xff1a;攻防实战与技巧》 2.5. 《数字政府网络安全合规性建设指南&#xff1a;密码应用与数据安全…