C语言--单链表的创建及使用详解

news2024/9/20 10:43:38

C语言--单链表的创建及使用详解

  • 1. 单链表定义
    • 1.1 工作原理
    • 1.2 优点
  • 2. 单链表的创建
    • 2.1 文件创建
    • 2.2 节点创建
    • 2.3 链表显示
  • 3. 链表操作
    • 3.1 尾插
    • 3.2 头插
    • 3.3 尾删
    • 3.4 头删
    • 3.5 指定数据寻找
    • 3.6 指定位置前插入
    • 3.7 指定位置删除
  • 4. 单链表总内容
    • 4.1 test.c文件
    • 4.2 SList.h文件
    • 4.3 SList.c文件

1. 单链表定义

单链表是一种常见的数据结构,它由一系列节点组成,每个节点包含数据和指向下一个节点的指针。
每个节点只能访问其后继节点,而无法访问前驱节点。链表的头节点是第一个节点,尾节点是最后一个节点,尾节点的指针指向空值。

1.1 工作原理

其工作原理如下图,它每个模块有两个部分组成,前面存放数据,最后存放下一个数据的地址。当我们想要访问下一个节点中的数据的时候,只需要通过前一个数据存放的下一个数据的地址就可以直接进行访问数据。
在数单链表的最前端有一个 plist 的指针,它会一直记录着链表第一个数据的地址,当我们想要访问链表中的数据的时候,只要通过 plist 就可以成功进入到链表当中。而刚开始创建链表的时候,头数据不存在,因而创建的 plist 要初始化为NULL,形式如下:
SLTNode* plist = NULL; //plist为头数据的地址
在这里插入图片描述

1.2 优点

与顺序表相比,单链表内存空间利用率更高,单向链表的每个节点只需要存储数据和指向下一个节点的指针,不需要预先分配固定大小的内存空间,因此可以灵活地利用内存空间,避免了顺序表需要预留固定大小的问题。

2. 单链表的创建

2.1 文件创建

为方便代码管理,将文件内容分为三个文件来共同组成单链表,如下:

test.c //用于⽂件中单链表的逻辑测试  
SList.c //⽂件中写单链表中函数的实现等 
SList.h //⽂件中写单链表需要的数据类型和函数声明等 

为了方便后期对单链表中数据及数据类型的修正,可以将数据类型等信息进行宏定义,如下所示:

typedef int SLTDataType;//定义数据类型
struct  SListNode//创建单链表
{
	SLTDataType data;//保存数据
	struct SListNode* next;//指向下一个数据的位置
};

typedef struct SListNode SLTNode;//定义结构体名称

SLTNode* plist = NULL;//plist为头数据的地址

在数据放入创建的链表之前,单链表的所有内容为空,在物理意义上是不存在的,其首地址的数据地址是不存在的,只有当放入数据之后,才会存在地址。因而,头数据的地址的创建如下所示:

SLTNode* plist = NULL;//plist为头数据的地址

2.2 节点创建

如果要插入数据,要么首先要开辟一块新的链表数据块,也就是创建一个结构体类型的数据,用一个 newnode 的指针保存其起始位置的地址,其指向下一个数据的指针置空,此时只是创建,并无下一个数据的链接。

在这里插入图片描述
代码实现如下:

SLTNode* BuySListNode(SLTDataType x)
{

	SLTNode* newcode = (SLTNode*)malloc(sizeof(SLTNode));
	newcode->data = x;
	newcode->next = NULL;
	return newcode;
}

先用 malloc 开辟一个结构体类型的空间,用 newnode 来接收其起始位置,之后将需要保存的数据放到newnode 的 data 当中,next 置空,然后再返回 newnode 的地址,这样,一个链表块就创建完毕。

需要注意的是,因为我们要返回的是 newnode 的地址,因而创建的开辟函数的数据类型是 SLTNode* 类型,即结构体指针类型。

2.3 链表显示

用形参 phead 来接收整个链表的头地址 plist 的值,通过 phead 来访问显示链表中的数据。
代码如下:

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

注意以下几点:

  1. 创建临时变量 cur 接收 phead 的地址(也可以直接通过 phead 进行操作)
  2. 因为最后一个数据的指针是置空的,因而结束的标志是访问的数据块里面 next 指向的地址为NULL。
    在这里插入图片描述
  3. cur 通过将自己的地址改为 cur 里面 next 里面保存的地址来达到指向下一个数据的目的。
  4. 最后最好打印一个 NULL,避免链表为空时,什么都不执行,不好判断是否出错。

3. 链表操作

3.1 尾插

在链表尾部插入一个数据块时,要先找到最后一个数据块,然后将最后一个数据块的 next 由置空改为指向新创建的数据块的首地址,如图所示。
在这里插入图片描述
代码如下:

void SListPushBack(SLTNode** pphead, SLTDataType x)
{
	SLTNode* newcode=BuySListNode(x);

	if (*pphead == NULL)
	{
		*pphead = newcode;
	}
	else
	{
		//找尾节点的指针
		SLTNode* tail = *pphead;
		while (tail->next != NULL)
		{
			tail = tail->next;
		}
		//尾节点,链接新节点
		tail->next = newcode;
	}
}

注意以下几点:

  1. 函数的变量一定要将链表的首地址的地址传递过去,也就是SLTNode** pphead是二级指针,不再是直接使用 phead 这个一级指针。因为当链表为空的时候,往链表里面放数据之后,链表首地址 plist 应该改为指向新数据的首地址,而不再应该是被置空。而函数中是对形参 phead 进行操作,出函数之后,phead 的内容将被清除,实参 pliast 将依然指向NULL。如果不考虑数据为空的情况,那依然可以只传递 phead 过去,只对其所指的内容进行修改。以上情况适用于后面的其他操作。
  2. 创建临时变量 newnode 来接收新开辟的数据块的起始地址.
  3. 当链表为空的时候,此时链表的头位置也为空,此时只需要直接将 newnode 的地址传递给整个链表的起始地址 plist 即可,即传递给 *pphead.
  4. 通过和链表显示一样的操作找到尾节点,将尾节点的 next 指向 newnode 便可以完成链接。

3.2 头插

头插只需要将整个链表的头地址改为新的数据块的地址,再将数据块的 next 指向之前的头地址,便可以完成链接,需要注意与尾插的注意一相同的问题即可,如下同。
在这里插入图片描述
代码如下:

void SListPushFont(SLTNode** pphead, SLTDataType x)
{
	SLTNode* newcode = BuySListNode(x);
	newcode->next = *pphead;
	*pphead = newcode;
}

3.3 尾删

删除尾部数据块 tail 时,需要先找到倒数第二的数据块的地址 prev ,将倒数第二的数据块 prev 的 next 置空,然后将最后一个数据块 tail 释放掉,过程如下图:

void SListPophBack(SLTNode** pphead)
{
	//1、空的时候
	if (*pphead == NULL)
	{
		return;
	}
	//2、只有一个节点
	else if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	//3、一个以上的节点
	else
	{
		SLTNode* prev = NULL;
		SLTNode* tail = *pphead;
		while (tail->next != NULL)
		{
			prev = tail;
			tail = tail->next;
		}
		free(tail);
		prev->next = NULL;
	}
}

注意以下几点:

  1. 当链表为的时候,直接返回,不做任何操作。
  2. 当判断链表只有一个节点的时候,直接释放链表数据,然后置空。
  3. 当链表有多个数据的时候,找到倒数第二个数据,改变链接,释放最后一个数据。
  4. 注意与头插注意一相同的问题。

3.4 头删

头部删除只需要将整个链表首地址改为第一块数据块指向的 next 即可,仍旧需要主要传值问题。
代码如下:
在这里插入图片描述

void SListPopFront(SLTNode** pphead)
{
	SLTNode* next = (*pphead)->next;
	free(*pphead);
	*pphead = next;
}

3.5 指定数据寻找

寻找指定的数据时,我们只是对数据进行查阅,不会更改首地址 plist 的地址,因而只需要使用phead 进行操作。从第一个数据开始寻找,如果找到了,就返回数据的地址,如果到尾了仍旧没有,则直接返回NULL,表示所寻找的数据不存在。
代码如下:

SLTNode* SListFind(SLTNode* phead, SLTDataType x)
{
	SLTNode* cur = phead;
	while (cur)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}

3.6 指定位置前插入

在指定位置 pos 前面插入一个数据,需要把指定数据的位置地址传进去。考虑到链表可能为空的情况,因而要使用二级指针 pphead 。用 prev 来寻找指定位置 pos 的前一个节点,找到之后,将 prev 的 next 指向新开辟的 newnode ,将 newnode 的 next 指向 pos ,这样就i完成了链接,如下图。
在这里插入图片描述

void SListInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
	if (pos ==* pphead)
	{
		SListPushFont(pphead,x);
	}
	else
	{
		SLTNode* newcode = BuySListNode(x);
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		prev->next = newcode;
		newcode->next = pos;
	}
}

使用案例:

	SLTNode* pos = SListFind(plist, 3);
	if (pos)
	{
		SListInsert(&plist, pos, 30);

	}
	SListPrint(plist);

3.7 指定位置删除

在指定位置 pos 前面插入一个数据,需要把指定数据的位置地址传进去。考虑到链表可能为空的情况,因而要使用二级指针 pphead 。如果第一个数据就是指定数据,则直接使用头删,如果不是,仍旧需要找到指定数据 pos 的前一个数据,将pos 前一个数据和 pos 的后一个数据链接起来,过程与指定位置插入相似。。

void SListErase(SLTNode** pphead, SLTNode* pos)
{
	if (pos == *pphead)
	{
		SListPopFront(pphead);
	}
	else
	{
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		prev->next = pos->next;
	}
}

4. 单链表总内容

4.1 test.c文件

测试文件,可根据自己需要添加内容。

//test.c文件
#define  _CRT_SECURE_NO_WARNINGS

#include "SList.h"

void TestSList1()
{
	SLTNode* plist = NULL;
	SListPushBack(&plist, 1);
	SListPushBack(&plist, 2);
	SListPushBack(&plist, 3);
	SListPushBack(&plist, 4);
	SListPophBack(&plist);
	SListPopFront(&plist);
	SListPushFont(&plist, 0);
	SListPrint(plist);
}

void TestSList2()
{
	SLTNode* plist = NULL;
	SListPushBack(&plist, 1);
	SListPushBack(&plist, 2);
	SListPushBack(&plist, 3);
	SListPushBack(&plist, 4);
	SListPrint(plist);

	SLTNode* pos = SListFind(plist, 3);
	if (pos)
	{
		SListInsert(&plist, pos, 30);

	}
	SListPrint(plist);
}


int main()
{
	TestSList2();

	return 0;
}

当前 test.c 文件演示效果:
在这里插入图片描述

4.2 SList.h文件

函数声明文件,用于结构体和宏定义。

#define  _CRT_SECURE_NO_WARNINGS
#pragma once
#include <stdio.h>
#include <string.h>
#include <stdlib.h>


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

typedef struct SListNode SLTNode;

//不会改变链表的头指针,传一级指针
SLTNode* BuySListNode(SLTDataType x);

void SListPrint(SLTNode* phead);

//会改变链表的头指针,传二级指针
void SListPushBack(SLTNode** pphead, SLTDataType x);
void SListPushFont(SLTNode** pphead, SLTDataType x);
void SListPopFront(SLTNode** pphead);
void SListPophBack(SLTNode** pphead);

SLTNode* SListFind(SLTNode* phead, SLTDataType x);
//在pos的前面插入x
void SListInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);
//删除pos位置的值
void SListErase(SLTNode** pphead, SLTNode* pos);


4.3 SList.c文件

函数定义文件,用于定义函数。

#define  _CRT_SECURE_NO_WARNINGS
#include "SList.h" 

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

SLTNode* BuySListNode(SLTDataType x)
{

	SLTNode* newcode = (SLTNode*)malloc(sizeof(SLTNode));
	newcode->data = x;
	newcode->next = NULL;
	return newcode;
}

void SListPushBack(SLTNode** pphead, SLTDataType x)
{
	SLTNode* newcode=BuySListNode(x);

	if (*pphead == NULL)
	{
		*pphead = newcode;
	}
	else
	{
		//找尾节点的指针
		SLTNode* tail = *pphead;
		while (tail->next != NULL)
		{
			tail = tail->next;
		}
		//尾节点,链接新节点
		tail->next = newcode;
	}
}

void SListPushFont(SLTNode** pphead, SLTDataType x)
{
	SLTNode* newcode = BuySListNode(x);
	newcode->next = *pphead;
	*pphead = newcode;
}

void SListPopFront(SLTNode** pphead)
{
	SLTNode* next = (*pphead)->next;
	free(*pphead);
	*pphead = next;
}
void SListPophBack(SLTNode** pphead)
{
	//1、空的时候
	if (*pphead == NULL)
	{
		return;
	}
	//2、只有一个节点
	else if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	//3、一个以上的节点
	else
	{
		SLTNode* prev = NULL;
		SLTNode* tail = *pphead;
		while (tail->next != NULL)
		{
			prev = tail;
			tail = tail->next;
		}
		free(tail);
		prev->next = NULL;
	}
}


SLTNode* SListFind(SLTNode* phead, SLTDataType x)
{
	SLTNode* cur = phead;
	while (cur)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}
//在pos的前面插入x
void SListInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
	if (pos ==* pphead)
	{
		SListPushFont(pphead,x);
	}
	else
	{
		SLTNode* newcode = BuySListNode(x);
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		prev->next = newcode;
		newcode->next = pos;
	}
}
//删除pos位置的值
void SListErase(SLTNode** pphead, SLTNode* pos)
{
	if (pos == *pphead)
	{
		SListPopFront(pphead);
	}
	else
	{
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		prev->next = pos->next;
	}
}

好了,今天的分享就到这里了,欢迎大家留言评论。

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

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

相关文章

强化学习应用(一):基于Q-learning的物流配送路径规划研究(提供Python代码)

一、Q-learning算法简介 Q-learning是一种强化学习算法&#xff0c;用于解决基于马尔可夫决策过程&#xff08;MDP&#xff09;的问题。它通过学习一个值函数来指导智能体在环境中做出决策&#xff0c;以最大化累积奖励。 Q-learning算法的核心思想是使用一个Q值函数来估计每…

ubuntu连接xshell怎么连接

在网上找了好多办法都不行 例如 太久没打开Ubuntu可能输入命令查不到IP地址&#xff0c;解决办法也比较简单&#xff0c;首先第一步 确定自己能不能进入管理员root权限&#xff08;输入命令su&#xff09;&#xff0c;如果没有的话得重新配置&#xff0c;如下图 这是因为当前Ub…

DC-DC变换集成电路芯片B34063——工作电压范围宽,静态电流小

B34063为一单片DC-DC变换集成电路&#xff0c;内含温度补偿的参考电压源(1.25V)、比较器、能有效限制电流及控制工作周期的振荡器,驱动器及大电流输出开关管等&#xff0c;外配少量元件&#xff0c;就能组成升压、降压及电压反转型DC-DC变换器。 主要特点&#xff1a; ● 工作…

文本翻译GUI程序,实现简单的英汉互译

项目地址&#xff1a;mendianyu/txtTranslate: 文本翻译GUI程序&#xff0c;实现简单的英汉互译 (github.com) 文本翻译GUI程序&#xff0c;实现简单的英汉互译 项目结构 三个java文件加一个pom文件 项目运行效果 语言可选择en(英语) zh(汉语) auto(自动识别&#xff0c;仅源语…

推荐算法常见的评估指标

推荐算法评估指标比较复杂&#xff0c;可以分为离线和在线两部分。召回、粗排、精排和重排由于定位区别&#xff0c;其评估指标也会有一定区别&#xff0c;下面详细讲解。 1 召回评价体系 召回结果并不是最终推荐结果&#xff0c;其本质是为后续排序层服务的&#xff0c;故核…

【深度学习目标检测】十五、基于深度学习的口罩检测系统-含GUI和源码(python,yolov8)

YOLOv8是一种物体检测算法&#xff0c;是YOLO系列算法的最新版本。 YOLO&#xff08;You Only Look Once&#xff09;是一种实时物体检测算法&#xff0c;其优势在于快速且准确的检测结果。YOLOv8在之前的版本基础上进行了一系列改进和优化&#xff0c;提高了检测速度和准确性。…

通过IP地址识别风险用户

随着互联网的迅猛发展&#xff0c;网络安全成为企业和个人关注的焦点之一。识别和防范潜在的风险用户是维护网络安全的关键环节之一。IP数据云将探讨通过IP地址识别风险用户的方法和意义。 IP地址的基本概念&#xff1a;IP地址是互联网上设备的独特标识符&#xff0c;它分为IP…

前端工程化相关

工具方法&#xff1a; 知道软件包名&#xff0c;拿到源码或者路径的方法 在浏览器输入以下内容&#xff0c;就可以找到你想要的。。。 unpkg.com/输入包名 一、模块化 ESM特性清单&#xff1a; 自动采取严格模式&#xff0c;忽略“use strict”每个ESM模块都是单独的私有作用…

“华为杯“第四届中国研究生数学建模竞赛-D题:邮路规划与邮车调度

目录 摘 要&#xff1a; 1.问题的重述 2.模型的假设与符号说明 2.1 针对本问题&#xff0c;本文做出如下假设 2.2 符号说明 3.问题的数学模型 4.问题的求解 4.1 问题一的求解 4.1.1 最少邮车数的求法 4.1.2 邮路规划及路径选择 4.1.3 问题的求解结果 4.2 问题二的求…

FPGA之初探

FPGA的构成 基本逻辑单元CLB CLB是FPGA的基本逻辑单元&#xff0c; 一个 CLB 包括了 2 个 Slices&#xff0c;所以知道Slices的数量就可以知道FPGA的“大概”逻辑资源容量了。一个 Slice 等于 4 个6输入LUT8个触发器(flip-flop)算数运算逻辑&#xff0c;每个 Slice 的 4 个触发…

在线的货币兑换平台源码下载

在线的货币兑换平台&#xff0c;可帮助全球各地的个人和企业将货币从一种货币兑换为另一种货币。该货币兑换平台是 Codecanyon 中最先进的脚本。 源码下载&#xff1a;https://download.csdn.net/download/m0_66047725/88728084

VMware 安装及创建一个 CentOS Stream 的详细指南

文章目录 1. 简介2. 下载和安装1&#xff09;通过官网安装2&#xff09;通过电脑管家安装 3. 下载操作系统镜像包4. 创建虚拟机结语 1. 简介 在过去&#xff0c;服务器通常是运行单一操作系统和应用程序的物理设备。这就导致了硬件资源浪费和管理复杂性的增加。为了解决这些问…

【C++刷题】位运算

【C刷题】位运算 一、二进制中最右侧的11、位1的个数&#xff08;1&#xff09;题目链接&#xff08;2&#xff09;解析&#xff08;3&#xff09;代码 2、比特位计数&#xff08;1&#xff09;题目链接&#xff08;2&#xff09;解析&#xff08;3&#xff09;代码 3、汉明距离…

LTESniffer:一款功能强大的LTE上下行链路安全监控工具

关于LTESniffer LTESniffer是一款功能强大的LTE上下行链路安全监控工具&#xff0c;该工具是一款针对LTE的安全开源工具。 该工具首先可以解码物理下行控制信道&#xff08;PDCCH&#xff09;并获取所有活动用户的下行链路控制信息&#xff08;DCI&#xff09;和无线网络临时…

重温经典struts1之自定义全局异常处理类处理异常以及<exeception>标签的配置

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 前言 前面的文章&#xff0c;我们学习了&#xff0c;Action类中调用Service&#xff0c;通过try…catch代码块&#xff0c;catch自定义异常类&#xff0c;通过ActionMessage…

cesium粒子效果——船舰水花效果

效果&#xff1a; 实现思路&#xff08;最后有完整代码&#xff09;&#xff1a; 与上篇文章思路一样 无非就是更换了模型与粒子图片以及粒子的配置&#xff0c;上一篇文章链接&#xff1a;https://blog.csdn.net/m0_63701303/article/details/135551667?spm1001.2014.3001.5…

neo4j 图数据库 py2neo 操作 示例代码

文章目录 摘要前置NodeMatcher & RelationshipMatcher创建节点查询获取节点节点有则查询&#xff0c;无则创建创建关系查询关系关系有则查询&#xff0c;无则创建 Cypher语句创建节点 摘要 利用py2neo包&#xff0c;实现把excel表里面的数据&#xff0c;插入到neo4j 图数据…

React Native 环境安装

Notion – The all-in-one workspace for your notes, tasks, wikis, and databases. 搭建开发环境 React Native 中文网 Homebrew&#xff08;包管理器&#xff09; → rvm&#xff08;ruby版本管理&#xff09; → ruby → cocoapods 安装 Homebrew Homebrew /bin/ba…

java每日一题——ATM系统编写(答案及编程思路)

前言&#xff1a; 基础语句学完&#xff0c;也可以编写一些像样的程序了&#xff0c;现在要做的是多加练习&#xff0c;巩固下知识点&#xff0c;打好基础&#xff0c;daydayup! 题目&#xff1a;模仿银行ATM系统&#xff0c;可以创建用户&#xff0c;存钱&#xff0c;转账&…

分布式搜索引擎--认识

elasticsearch的作用 elasticsearch是一款非常强大的开源搜索引擎&#xff0c;具备非常多强大功能&#xff0c;可以帮助我们从海量数据中快速找到需要的内容 。 elasticsearch结合kibana、Logstash、Beats&#xff0c;也就是elastic stack&#xff08;ELK&#xff09;。被广泛…