单向链表——C语言实现

news2024/11/20 18:45:26

哈喽,大家好,今天我们学习的是数据结构里的链表,这里主要讲的是不带哨兵卫头节点的单向链表,下篇将会继续带大家学习双向链表。

目录

1.链表的概念

2.单向链表接口的实现

2.1动态申请一个节点

2.2单链表打印

2.3单链表尾插

2.4单链表头插

2.5单链表尾删

2.6单链表头删

2.7在pos位置插入x

2.7.1在pos位置前插入x

2.7.2在pos位置后插入x

2.8删除pos位置值

2.9 查找x的地址

2.10销毁链表

3.完整代码


1.链表的概念

在上篇文章,我们已经学习了顺序表,不知大家有没有发现顺序表在一定程度上是存在缺陷的,比如说:

  1. 空间不够了的时候需要扩容,扩容需要付出代价(特别是异地扩空间)
  2. 为了避免频繁扩容,我们满了基本都是扩2倍,可能会导致一定的空间浪费
  3. 顺序表要求数据从开始位置连续存储,那么我们在头部或者中间位置插入删除数据就需要挪动数据,效率不高

针对顺序表的缺陷,就有了链表来存储数据

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

 链表的定义:

 这里的data就是要存放的数据

2.单向链表接口的实现

下面是要介绍的常用到的链表接口函数以及实现方法:

//打印
void SListPrint(SLTNode* phead);
//创建新节点
SLTNode* BuyListNode(SLTDateType x);
//尾插
void SListPushBack(SLTNode** pphead, SLTDateType x);
//头插
void SListPushFront(SLTNode** pphead, SLTDateType x);
//头删
void SListPopBack(SLTNode** pphead);
//尾删
void SListPopFront(SLTNode** pphead);
//查找
SLTNode* SListFind(SLTNode* phead, SLTDateType x);
//插入
void SListInsert(SLTNode** pphead, SLTNode* pos, SLTDateType x);
void SListInsertAfter(SLTNode** pphead, SLTNode* pos, SLTDateType x);
//删除
void SListErase(SLTNode** pphead, SLTNode* pos);
//销毁
void SListDestroy(SLTNode** pphead);

2.1动态申请一个节点

由于我们每次给链表插入数据时,都需要动态开辟空间来申请节点,所以我们把这个过程封装成一个函数,方便后续操作。

动态申请一个节点的步骤是先向计算机内存申请一块空间,这里我们将申请的空间用指针变量newnode来存储,然后将newnode中的data赋值,因为这是新开辟的节点,所以暂时将newnode中的next指向空。

注意:为了提高程序的可靠性,我们在动态内存申请后记得检查是否申请失败了,如果申请失败了输出提示信息,并退出程序。

SLTNode* BuyListNode(SLTDateType x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	if (newnode == NULL)//如果动态内存申请失败就退出程序
	{
		printf("malloc fail\n");
		exit(-1);
	}
	newnode->data = x;
	newnode->next = NULL;
	return newnode;
}

2.2单链表打印

打印链表就是一个遍历链表的过程,我们首先定义一个指针(cur)指向链表的头节点,然后输出该节点的值,然后将指针指向下一个节点(cur=cur->next),依次进行,直到cur为空指针时停止

void SListPrint(SLTNode* phead)
{
	SLTNode* cur = phead;
	while (cur != NULL)
	{
		printf("%d->", cur->data);
		cur = cur->next;//将cur指向下一个节点
	}
	printf("NULL\n");
}

2.3单链表尾插

尾插,就是先找到链表中最后一个节点,然后将数据插入到最后。

但是,我们要先判断链表是否为空,如果链表为空,我们直接直接将链表的头指针赋予要插入的数据。

由于尾插要改变链表,所以传参要用二级指针,包括下面的尾插,尾删,头删等都要用二级指针传参

void SListPushBack(SLTNode** pphead, SLTDateType x)
{
	SLTNode* newnode = BuyListNode(x);
	if (*pphead == NULL)
	{
		*pphead = newnode;
	}
	else
	{
		SLTNode* tail = *pphead;
		while (tail->next != NULL)
		{
			tail = tail->next;
		}
		tail->next = newnode;
	}
}

2.4单链表头插

头插是比较简单的一种操作,只需要申请新节点,将新节点的next指向链表的头,再让新节点成为链表的头即可。

void SListPushFront(SLTNode** pphead, SLTDateType x)
{
	SLTNode* newnode = BuyListNode(x);
	newnode->next = *pphead;
	*pphead = newnode;
}

2.5单链表尾删

尾删:每次找到链表的最后一个节点和倒数第二个节点,然后释放最后一个节点所占的看空间并将最后一个节点置空,同时将倒数第二个节点的next指向NULL;如果链表只剩下一个节点,直接释放并置空该节点(这一步需要单独考虑)

注意:为了避免链表为空但有调用尾删的情况,我们需要断言一下,当传过来的链表是空链表的时候,程序就会报错

void SListPopBack(SLTNode** pphead)
{
	//保证链表不是空链表
	assert(*pphead != NULL);
	if ((*pphead)->next == NULL)
	{
		//当链表中只有一个节点
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		SLTNode* tail = *pphead;
		SLTNode* prev = NULL;
		while (tail->next != NULL)
		{
			prev = tail;
			tail = tail->next;
		}
		prev->next = NULL;
		free(tail);
		tail = NULL;
	}
}

2.6单链表头删

头删是将第一个节点释放然后指向第二个节点,在此之前需要定义一个指针next来保存第二个节点的地址。

void SListPopFront(SLTNode** pphead)
{
	//保证链表不是空链表
	assert(*pphead != NULL);
	SLTNode* next = (*pphead)->next;
	free(*pphead);
	*pphead = next;
}

2.7在pos位置插入x

在pos位置插入x分为两种情况,一种是在pos前位置插入x ,另一种是在pos后位置插入x,下面将分别为大家介绍:

2.7.1在pos位置前插入x

在pos位置前插入x,只需要找到pos的前一个位置,我们把pos的前一个位置命名为posPrev,然后创建一个新节点newnode,将posPrev的下一个节点指向newnodenewnode的下一个节点指向pos即可,如下图:

 

void SListInsert(SLTNode** pphead, SLTNode* pos, SLTDateType x)
{
	assert(pphead != NULL);
	assert(pos != NULL);

	SLTNode* newnode = BuyListNode(x);
	if (*pphead == pos)
	{
		newnode->next = *pphead;
		*pphead = newnode;
	}
	else
	{
		//找到pos的前一个位置
		SLTNode* posPrev = *pphead;
		while (posPrev->next != pos)
		{
			posPrev = posPrev->next;
		}
		posPrev->next = newnode;
		newnode->next = pos;
	}
}

2.7.2在pos位置后插入x

在pos位置后插入x比在pos位置前插入x要简单,不需要遍历链表即可完成

void SListInsertAfter(SLTNode** pphead, SLTNode* pos, SLTDateType x)
{
    assert(pos != NULL);
    SLTNode* newnode = BuyListNode(x);
    newnode->next = pos->next;
    pos->next = newnode;
}

2.8删除pos位置值

删除pos位置值也需要先找到pos的前一个节点,因此也要考虑pos是链表的头节点的情况

void SListErase(SLTNode** pphead, SLTNode* pos)
{
	assert(*pphead != NULL);
	assert(pos != NULL);
	if (*pphead == pos)
	{
		*pphead = pos->next;
		free(pos);
	}
	else
	{
		SLTNode* posPrev = *pphead;
		while (posPrev->next != pos)
		{
			posPrev = posPrev->next;
		}
		posPrev->next = pos->next;
		free(pos);
	}
}

2.9 查找x的地址

查找x的地址,如果查找到了x,则返回该节点的地址,否则返回空指针。这个步骤也要遍历链表。

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

2.10销毁链表

销毁链表需要将所有节点所占的内存全部释放,再将链表的头置为空即可。

void SListDestroy(SLTNode** pphead)
{
	assert(*pphead != NULL);
	SLTNode* cur = *pphead;
	while (cur != NULL)
	{
		SLTNode* next = cur->next;
		free(cur);
		cur = next;
	}
	*pphead = NULL;
}

3.完整代码

SList.h文件:

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

typedef int SLTDateType;
typedef struct SListNode
{
	SLTDateType data;
	struct SListNode* next;//存放下一个链表的地址
}SLTNode;

//打印
void SListPrint(SLTNode* phead);
//创建新节点
SLTNode* BuyListNode(SLTDateType x);
//尾插
void SListPushBack(SLTNode** pphead, SLTDateType x);
//头插
void SListPushFront(SLTNode** pphead, SLTDateType x);
//头删
void SListPopBack(SLTNode** pphead);
//尾删
void SListPopFront(SLTNode** pphead);
//查找
SLTNode* SListFind(SLTNode* phead, SLTDateType x);
//插入
void SListInsert(SLTNode** pphead, SLTNode* pos, SLTDateType x);
void SListInsertAfter(SLTNode** pphead, SLTNode* pos, SLTDateType x);
//删除
void SListErase(SLTNode** pphead, SLTNode* pos);
//销毁
void SListDestroy(SLTNode** pphead);

SList.c文件:

#include"SList.h"

void SListPrint(SLTNode* phead)
{
	SLTNode* cur = phead;
	while (cur != NULL)
	{
		printf("%d->", cur->data);
		cur = cur->next;//将cur指向下一个节点
	}
	printf("NULL\n");
}

SLTNode* BuyListNode(SLTDateType x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	if (newnode == NULL)//如果动态内存申请失败就退出程序
	{
		printf("malloc fail\n");
		exit(-1);
	}
	newnode->data = x;
	newnode->next = NULL;
	return newnode;
}

void SListPushBack(SLTNode** pphead, SLTDateType x)
{
	SLTNode* newnode = BuyListNode(x);
	if (*pphead == NULL)
	{
		*pphead = newnode;
	}
	else
	{
		SLTNode* tail = *pphead;
		while (tail->next != NULL)
		{
			tail = tail->next;
		}
		tail->next = newnode;
	}
}

void SListPushFront(SLTNode** pphead, SLTDateType x)
{
	SLTNode* newnode = BuyListNode(x);
	newnode->next = *pphead;
	*pphead = newnode;
}

void SListPopBack(SLTNode** pphead)
{
	//保证链表不是空链表
	assert(*pphead != NULL);
	if ((*pphead)->next == NULL)
	{
		//当链表中只有一个节点
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		SLTNode* tail = *pphead;
		SLTNode* prev = NULL;
		while (tail->next != NULL)
		{
			prev = tail;
			tail = tail->next;
		}
		prev->next = NULL;
		free(tail);
		tail = NULL;
	}
}


void SListPopFront(SLTNode** pphead)
{
	//保证链表不是空链表
	assert(*pphead != NULL);
	SLTNode* next = (*pphead)->next;
	free(*pphead);
	*pphead = next;
}

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

void SListInsert(SLTNode** pphead, SLTNode* pos, SLTDateType x)
{
	assert(pphead != NULL);
	assert(pos != NULL);

	SLTNode* newnode = BuyListNode(x);
	if (*pphead == pos)
	{
		newnode->next = *pphead;
		*pphead = newnode;
	}
	else
	{
		//找到pos的前一个位置
		SLTNode* posPrev = *pphead;
		while (posPrev->next != pos)
		{
			posPrev = posPrev->next;
		}
		posPrev->next = newnode;
		newnode->next = pos;
	}
}

void SListInsertAfter(SLTNode** pphead, SLTNode* pos, SLTDateType x)
{
	assert(pos != NULL);
	SLTNode* newnode = BuyListNode(x);
	newnode->next = pos->next;
	pos->next = newnode;
}

void SListErase(SLTNode** pphead, SLTNode* pos)
{
	assert(*pphead != NULL);
	assert(pos != NULL);
	if (*pphead == pos)
	{
		*pphead = pos->next;
		free(pos);
	}
	else
	{
		SLTNode* posPrev = *pphead;
		while (posPrev->next != pos)
		{
			posPrev = posPrev->next;
		}
		posPrev->next = pos->next;
		free(pos);
	}
}

void SListDestroy(SLTNode** pphead)
{
	assert(*pphead != NULL);
	SLTNode* cur = *pphead;
	while (cur != NULL)
	{
		SLTNode* next = cur->next;
		free(cur);
		cur = next;
	}
	*pphead = NULL;
}

总结:这篇文章主要写的是单向链表,后续将继续带领大家学习双向链表。如果我写的有什么的不好之处,请在文章下方给出你宝贵的意见。如果觉得我写的好的话请点个赞赞和关注哦~😘

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

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

相关文章

强大的editplus 5.7

EditPlus是一款由韩国 Sangil Kim &#xff08;ES-Computing&#xff09;出品的小巧但是功能强大的可处理文本、HTML和程序语言的Windows编辑器&#xff0c;你甚至可以通过设置用户工具将其作为C,Java,Php等等语言的一个简单的IDE。 EditPlus&#xff08;文字编辑器&#xff0…

Java学习-MySQL-数据库的设计

Java学习-MySQL-数据库的设计 为什么需要设计数据库 当数据库比较复杂的时候&#xff0c;需要设计数据库。 糟糕的数据库设计&#xff1a; 数据冗余&#xff0c;浪费空间&#xff1b;数据库插入和删除很麻烦&#xff0c;可能导致异常&#xff08;屏蔽使用物理外键&#xff0…

机器学习实战教程(十三):集成学习

简介 集成学习是一种机器学习方法&#xff0c;它旨在通过将多个单独的学习器&#xff08;称为基分类器或基学习器&#xff09;的预测结果进行组合&#xff0c;来提高整体的预测准确率。 集成学习可以看作是一种“多个人一起合作做事”的方法。每个基分类器都是独立的学习器&a…

【恭喜宿主:你的神装Xpath到手】——07全栈开发——如桃花来

目录索引 什么是XML&#xff1a;文档演示&#xff1a; XML的节点关系&#xff1a;1.父节点&#xff1a;2. 子节点&#xff1a;3. 同胞节点&#xff1a;4. 先辈节点&#xff1a;5. 后代节点&#xff1a; Xpath&#xff1a;1. 相关语法&#xff1a;*最常用的路径表达式&#xff1…

Android Studio实现文艺阅读App

项目目录 一、系统概述二、系统特点三、开发环境四、运行演示五、源码获取 一、系统概述 本次带来的文艺阅读App可以提供高质量的原创文学作品。用户可以App中找到各种类型的文学作品&#xff0c;包括小说、散文、诗歌等&#xff0c;由来自不同领域的作家所创作。此外&#xf…

对不起,我们不招还在用Excel的人,和金山系新秀比起差太远了

相信点进来的朋友曾经也深受Excel荼毒。 的确&#xff0c;现如今在网上随便一搜&#xff0c;关于Excel的学习资料和答疑解惑的帖子不胜枚举&#xff0c;盖因为Excel有时太过热心&#xff0c;当然&#xff0c;是帮倒忙的那种热心。 自动把天数转换为日期&#xff0c;还替你把身…

Flume 从入门到精通

Flume Flume 是一种分布式、可靠且可用的服务 高效收集、聚合和移动大量日志 数据。 它具有基于流媒体的简单灵活的架构 数据流。它坚固耐用&#xff0c;容错&#xff0c;可靠性可调 机制以及许多故障转移和恢复机制。 它 使用允许在线分析的简单可扩展数据模型 应用。 系统要求…

Java程序设计入门教程---循环结构(while)

目录 思考 概念 语法 案例&#xff1a;求1到100的整数和&#xff1f; 案例分析 思考 1. 让你输出10000000000000000句“Hello,world!”&#xff0c;你怎么写代码&#xff1f; 2. 求1到100的整数和&#xff1f; 概念 循环结构程序多次循环执行相同或相近的任务。 while循环…

Nacos注册中心一些配置说明

未安装Nacos的可以参考以下的安装教程 Nacos 安装教程&#xff08;史上最详细保姆级教程&#xff09;_nacos安装_大三的土狗的博客-CSDN博客 注意: Nacos默认是集群部署,如果想单机启动需要在对应Nacos的bin目录执行下面的命令 因为以后不可能只有一个Nacos注册中心,所以就默认…

BERT+TextCNN实现医疗意图识别项目

BERTTextCNN实现医疗意图识别项目 一、说明 本项目采用医疗意图识别数据集CMID传送门 数据集示例&#xff1a; {"originalText": "间质性肺炎的症状?", "entities": [{"label_type": "疾病和诊断", "start_pos&quo…

Qt-数据库开发-用户登录、后台管理用户

Qt-数据库开发-用户登录、后台管理用户 [1] Qt-数据库开发-用户登录、后台管理用户1、概述2、实现效果 [2] Qt使用SqlLite实现权限管理初始化数据库创建数据表插入数据可使用结构体对数据信息进行封装数据库查询函数为数据库更新数据函数为删除数据函数为 [3] 测试效果 [1] Qt-…

最详细的静态路由的原理和配置

第四章&#xff1a;静态路由 转发数据包是路由器的最主要功能。路由器转发数据包时需要查找路由表&#xff0c;管理员可以通过手工的方法在路由器中直接配置路由表&#xff0c;这就是静态路由。虽然静态路由不适合于在大的网络中使用&#xff0c;但是由于静态路由简单、路由器…

亚马逊云科技让数十亿的数据清洗、转移和查询只需要10分钟

随着数字经济对经济社会的发展贡献愈渐增多&#xff0c;数字金融作为数字经济的有机组成部分和重要支撑&#xff0c;也正成为金融领域竞争与合作的制高点。但在数据要素推动数字金融高速发展的同时&#xff0c;逐渐复杂的互联网环境与日益增强的金融监管力度&#xff0c;对数字…

FL Studio21最新中文版本下载及详细安装教程

FL Studio21最新中文版本是一款专业的音乐制作软件&#xff0c;软件支持录音、音频剪辑、混音、编曲等众多实用功能&#xff0c;可以让你的电脑化身为专业的录音室&#xff0c;进行音乐的录制和剪辑工作&#xff0c;帮助用户轻松创作出各种优秀的音乐作品。 FL Studio21中文版…

亚马逊云科技携手普华永道,推出健康及生命科学行业出海合规指南

自2022年起&#xff0c;国内医疗健康行业的投融资热度略有降低。但医疗行业投资结构正面临转型&#xff0c;投资逐步呈现全球化布局的趋势&#xff0c;在跨境合作领域仍持续释放活力。同时&#xff0c;随着药品集采、医保谈判持续推进&#xff0c;越来越多的中国健康及生命科学…

基于RK3588+TensorFlow的人工智能跨模态行人重识别方法及应用

摘要&#xff1a; 跨模态行人重识别技术&#xff08;cm-ReID&#xff09;旨在可见光、红外等不同模态图像中识别出同一个人&#xff0c;其在人 机协同、万物互联、跨界融合、万物智能的智能系统与装备中有重要应用。提出一种数据增强的跨模态行人 重识别方法&#xff0c;在波长…

【Vue 基础】尚品汇项目-10-Search模块中商品分类与过渡动画

一、商品导航的显示与隐藏 打开“src/componetnts/TypeNav/index.vue”&#xff0c;让商品导航默认为显示 在TypeNav组件挂载完毕时&#xff0c;判断当前的路由是否是“/home”&#xff0c;如果不是“/home”&#xff0c;就将分类导航隐藏 当鼠标移入时 移入时让商品导航显示 …

如何在Windows上轻松安全的将数据从HDD迁移到SSD?

当你打算升级硬盘时&#xff0c;如何将数据从HDD迁移到SSD&#xff1f;你可以使用一款免费的软件将所有数据从一个硬盘克隆到另一个硬盘。 为什么要将数据从HDD迁移到SSD&#xff1f; HDD&#xff08;机械硬盘&#xff09;和SSD&#xff08;固态硬盘&#xff09;是目前常用…

java 学习日记

今天先搞题目 给你一个points 数组&#xff0c;表示 2D 平面上的一些点&#xff0c;其中 points[i] [xi, yi] 。 连接点 [xi, yi] 和点 [xj, yj] 的费用为它们之间的 曼哈顿距离 &#xff1a;|xi - xj| |yi - yj| &#xff0c;其中 |val| 表示 val 的绝对值。 请你返回将所…

DS1302芯片介绍

低功耗时钟芯片DS1302可以对年、月、日、时、分、秒进行计时&#xff0c;且具有闰年补偿等多种功能。 DS1302的性能特性&#xff1a; 实时时钟&#xff0c;可对秒、分、时、日、周、月以及带闰年补偿的年进行计数&#xff1b; 用于高速数据暂存的318位RAM&#xff1b; 最少引脚…