数据结构——单链表的实现(c语言版)

news2025/1/13 13:29:35

前言 

        单链表作为顺序表的一种,了解并且熟悉它的结构对于我们学习更加复杂的数据结构是有一定意义的。虽然单链表有一定的缺陷,但是单链表也有它存在的价值, 它也是作为其他数据结构的一部分出现的,比如在图,哈希表中。

目录

1.链表节点的结构

2.头插头删

3.尾插尾删

4.任意位置的插入和删除

5.查找链表的值和修改链表节点的值

6.销毁链表

7.测试代码

8.全部代码

9.总结 


1.链表节点的结构

        单链表有节点的值和节点的next指针组成,如图:

 

typedef int SListDatatype;
typedef struct SListNode
{
	SListDatatype _data;//存储节点的数据
	struct SListNode* _next;
}SListNode;

2.头插头删

        头插分为两种情况,第一种是没有节点的情况,第二种是 有节点的情况。如图:

                头删也分为两种情况,如果只有一个节点的时候,直接删除就行了,然后将头结点置空。如果有多个节点,需要先记录头结点,然后再进行删除就可以了。

void SListPushFront(SListNode** ppHead, SListDatatype data)//头插
{
	SListNode* newNode = SlistBuyNode(data);//申请一个新的节点
	if (*ppHead == NULL)
	{
		//链表为空
		*ppHead = newNode;
		return;
	}
	newNode->_next = (*ppHead);
	*ppHead = newNode;//对头结点进行链接
}
void SListPopFront(SListNode** ppHead)//头删
{
	assert(*ppHead);//确保指针的有效性
	if ((*ppHead)->_next == NULL)
	{
		//链表只有一个节点
		free(*ppHead);
		*ppHead = NULL;
		return;
	}
	//删除头结点,然后更新头结点
	SListNode* newHead = (*ppHead)->_next;
	free(*ppHead);
	*ppHead = newHead;
	return;
}

3.尾插尾删

        尾插也分为链表为空和指针不为空的情况,如果链表为空,申请节点,让链表的头结点指向申请的节点,然后将这个节点的_next置空,如果链表不为空,首先需要找到尾结点,然后将尾结点与这个节点链接起来,再将这个新申请的节点的_next置空。如图:

 

        尾删也分为两种情况:1只有一个节点和2存在多个节点

如果只有一个节点,删除以后需要将头结点置空,防止出现野指针的问题。

如果有多个节点,删除尾结点以后需要将新的尾结点置空。

如图: 

void SListPushBack(SListNode** ppHead, SListDatatype data)//尾插
{
	SListNode*newNode =  SlistBuyNode(data);//申请一个新的节点
	
	if (*ppHead == NULL)//链表为空
	{
		*ppHead = newNode;
		return;
	}
	if ((*ppHead)->_next == NULL)//链表只存在一个节点
	{
		(*ppHead)->_next = newNode;
		return;
	}
	SListNode* cur = *ppHead;
	while (cur->_next)//找到尾节点
	{
		cur = cur->_next;
	}
	cur->_next = newNode;//进行链接
	return;
}
void SListPopBack(SListNode** ppHead)//尾删
{
	assert(*ppHead);
	if (*ppHead == NULL)//链表为空不需要删除
	{
		return;
	}
	if ((*ppHead)->_next == NULL)
	{
		free(*ppHead);//链表只有一个节点
		(*ppHead) = NULL;
		return;
	}
	SListNode* cur = *ppHead;
	SListNode* prev = NULL;

	while (cur->_next)//找到尾结点
	{
		prev = cur;//保存上一个节点
		cur = cur->_next;
	}
	free(cur);//释放尾结点所在的空间
	prev->_next = NULL;//将上一个节点的_next置空
	return;

4.任意位置的插入和删除

        由于单链表结构的限制,这里只实现了在pos位置之后的插入和删除,如果删除pos的后一个节点就需要确保pos的后一个节点是存在的,否则就会出现问题。

void SListInsertAfter(SListNode*pos, SListDatatype data)//任意位置的插入,在pos之后插入
{
	assert(pos);//确保指针不为空
	SListNode* newNode = SlistBuyNode(data);
	SListNode* next = pos->_next;
	pos->_next = newNode;
	newNode->_next = next;
}
void SListErase(SListNode*pos)//任意位置的删除,pox位置之后的删除
{
	assert(pos);//确保节点的有效性
	//如果只有一个节点
	if (pos->_next )//pos节点的下一个节点存在
	{
		SListNode* next = pos->_next;
		SListNode* nextNext = next->_next;
		free(next);//删除节点,重新链接
		pos->_next = nextNext;
	}
}

5.查找链表的值和修改链表节点的值

        遍历链表就可以对链表中的数据进行查找,找到查找的值,就可以对节点的值进行修改。 

SListNode* SListFind(SListNode* pHead, SListDatatype data)//查找
{
	SListNode* cur = pHead;
	while (cur)
	{
		if (cur->_data == data)
			return cur;
		cur = cur->_next;//迭代向后走
	}
	return NULL;//找不到
}

 

void testSList()
{
	//查找和修改的测试
	SListNode* pHead = NULL;
	SListPushFront(&pHead, 1);
	SListPushFront(&pHead, 2);
	SListPushFront(&pHead, 3);
	SListPushFront(&pHead, 4);
	SListPushFront(&pHead, 5);
	SListPushFront(&pHead, 6);
	SListPrint(pHead);
	SListNode* node = SListFind(pHead, 5);//查找
	if (node)
	{
		//节点的数据
		node->_data = 50;
	}
	SListPrint(pHead);
}

6.销毁链表

void SListDestory(SListNode** ppHead)//销毁
{
	assert(*ppHead);
	//确保指针有效性
	SListNode* cur = *ppHead;
	while (cur)
	{
		SListNode* freeNode = cur;
		cur = cur->_next;
		free(freeNode);

	}
	*ppHead = NULL;
}

 

7.测试代码

void testSListBack()
{
	//尾插尾删的测试代码
	SListNode* pHead = NULL;
	SListPushBack(&pHead, 1);
	SListPushBack(&pHead, 2);
	SListPushBack(&pHead, 3);
	SListPushBack(&pHead, 4);
	SListPushBack(&pHead, 5);
	SListPushBack(&pHead, 6);
	SListPrint(pHead);
	SListPopBack(&pHead);
	SListPopBack(&pHead);
	SListPopBack(&pHead);
	SListPopBack(&pHead);
	SListPopBack(&pHead);
	SListPopBack(&pHead);


}
void testSListFront()
{
	//头插头删的测试代码
	SListNode* pHead = NULL;
	SListPushFront(&pHead, 1);
	SListPushFront(&pHead, 2);
	SListPushFront(&pHead, 3);
	SListPushFront(&pHead, 4);
	SListPushFront(&pHead, 5);
	SListPushFront(&pHead, 6);
	SListPrint(pHead);
	SListPopFront(&pHead);
	SListPopFront(&pHead);
	SListPopFront(&pHead);
	SListPopFront(&pHead);
	SListPopFront(&pHead);
	SListPopFront(&pHead);
}
void testSList()
{
	//查找和修改的测试
	SListNode* pHead = NULL;
	SListPushFront(&pHead, 1);
	SListPushFront(&pHead, 2);
	SListPushFront(&pHead, 3);
	SListPushFront(&pHead, 4);
	SListPushFront(&pHead, 5);
	SListPushFront(&pHead, 6);
	SListPrint(pHead);
	SListNode* node = SListFind(pHead, 5);//查找
	if (node)
	{
		//节点的数据
		node->_data = 50;
	}
	SListPrint(pHead);
}
void TestSList1()
{
	//对在pos节点之后进行插入和删除的测试
	SListNode* pHead = NULL;
	SListPushFront(&pHead, 1);
	SListPushFront(&pHead, 2);
	SListPushFront(&pHead, 3);
	SListPushFront(&pHead, 4);
	SListPushFront(&pHead, 5);
	SListPushFront(&pHead, 6);
	SListPrint(pHead);
	SListNode* node = SListFind(pHead, 5);//查找
	if (node)
	{
		
		//插入节点
		SListInsertAfter(node, -2);
		SListPrint(pHead);

		SListErase(node);
		SListPrint(pHead);

	}
	SListDestory(&pHead);
}

8.全部代码

//SList.h

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<assert.h>
typedef int SListDatatype;
typedef struct SListNode
{
	SListDatatype _data;//存储节点的数据
	struct SListNode* _next;
}SListNode;
SListNode* SlistBuyNode(SListDatatype data);


void SListDestory(SListNode** ppHead);//销毁
void SListPushBack(SListNode**ppHead,SListDatatype data);//尾插
void SListPopBack(SListNode** ppHead );//尾删

void SListPushFront(SListNode** ppHead, SListDatatype data);//头插
void SListPopFront(SListNode** ppHead);//头删

void SListInsertAfter(SListNode* pos, SListDatatype data);//任意位置的插入

void SListErase(SListNode*pos);//任意位置的删除

SListNode* SListFind(SListNode*pHead, SListDatatype data);//查找
void SListPrint(SListNode* pHead);//显示链表数据
//void SListDestory(SListNode** ppHead);//删除链表

 //SList.c

#include"SList.h"

SListNode* SlistBuyNode(SListDatatype data)
{
	 SListNode*newNode = (SListNode*)malloc(sizeof(SListNode));
	 if (newNode == NULL)
	 {
		 //申请节点失败
		 printf("申请节点失败\n");
		 exit(-1);//暴力返回
	 }
	 newNode->_data = data;//给节点赋值
	 newNode->_next = NULL;

	 return newNode;
}

void SListDestory(SListNode** ppHead)//销毁
{
	assert(*ppHead);
	//确保指针有效性
	SListNode* cur = *ppHead;
	while (cur)
	{
		SListNode* freeNode = cur;
		cur = cur->_next;
		free(freeNode);

	}
	*ppHead = NULL;
}
void SListPushBack(SListNode** ppHead, SListDatatype data)//尾插
{
	SListNode*newNode =  SlistBuyNode(data);//申请一个新的节点
	
	if (*ppHead == NULL)//链表为空
	{
		*ppHead = newNode;
		return;
	}
	if ((*ppHead)->_next == NULL)//链表只存在一个节点
	{
		(*ppHead)->_next = newNode;
		return;
	}
	SListNode* cur = *ppHead;
	while (cur->_next)//找到尾节点
	{
		cur = cur->_next;
	}
	cur->_next = newNode;//进行链接
	return;
}
void SListPopBack(SListNode** ppHead)//尾删
{
	assert(*ppHead);
	if (*ppHead == NULL)//链表为空不需要删除
	{
		return;
	}
	if ((*ppHead)->_next == NULL)
	{
		free(*ppHead);//链表只有一个节点
		(*ppHead) = NULL;
		return;
	}
	SListNode* cur = *ppHead;
	SListNode* prev = NULL;

	while (cur->_next)//找到尾结点
	{
		prev = cur;//保存上一个节点
		cur = cur->_next;
	}
	free(cur);//释放尾结点所在的空间
	prev->_next = NULL;//将上一个节点的_next置空
	return;
}
void SListPushFront(SListNode** ppHead, SListDatatype data)//头插
{
	SListNode* newNode = SlistBuyNode(data);//申请一个新的节点
	if (*ppHead == NULL)
	{
		//链表为空
		*ppHead = newNode;
		return;
	}
	newNode->_next = (*ppHead);
	*ppHead = newNode;//对头结点进行链接
}
void SListPopFront(SListNode** ppHead)//头删
{
	assert(*ppHead);//确保指针的有效性
	if ((*ppHead)->_next == NULL)
	{
		//链表只有一个节点
		free(*ppHead);
		*ppHead = NULL;
		return;
	}
	//删除头结点,然后更新头结点
	SListNode* newHead = (*ppHead)->_next;
	free(*ppHead);
	*ppHead = newHead;
	return;
}
void SListInsertAfter(SListNode*pos, SListDatatype data)//任意位置的插入,在pos之后插入
{
	assert(pos);//确保指针不为空
	SListNode* newNode = SlistBuyNode(data);
	SListNode* next = pos->_next;
	pos->_next = newNode;
	newNode->_next = next;
}
void SListErase(SListNode*pos)//任意位置的删除,pox位置之后的删除
{
	assert(pos);//确保节点的有效性
	//如果只有一个节点
	if (pos->_next )//pos节点的下一个节点存在
	{
		SListNode* next = pos->_next;
		SListNode* nextNext = next->_next;
		free(next);//删除节点,重新链接
		pos->_next = nextNext;
	}
}

SListNode* SListFind(SListNode* pHead, SListDatatype data)//查找
{
	SListNode* cur = pHead;
	while (cur)
	{
		if (cur->_data == data)
			return cur;
		cur = cur->_next;//迭代向后走
	}
	return NULL;//找不到
}
void SListPrint(SListNode* pHead)//显示链表数据
{
	assert(pHead);//确保指针的有效性
	SListNode* cur = pHead;
	while (cur)
	{
		printf("%d ", cur->_data);
		printf("->");
		cur = cur->_next;
	}
	printf("NULL\n");
}

//test.c

#include"SList.h"
void testSListBack()
{
	//尾插尾删的测试代码
	SListNode* pHead = NULL;
	SListPushBack(&pHead, 1);
	SListPushBack(&pHead, 2);
	SListPushBack(&pHead, 3);
	SListPushBack(&pHead, 4);
	SListPushBack(&pHead, 5);
	SListPushBack(&pHead, 6);
	SListPrint(pHead);
	SListPopBack(&pHead);
	SListPopBack(&pHead);
	SListPopBack(&pHead);
	SListPopBack(&pHead);
	SListPopBack(&pHead);
	SListPopBack(&pHead);


}
void testSListFront()
{
	//头插头删的测试代码
	SListNode* pHead = NULL;
	SListPushFront(&pHead, 1);
	SListPushFront(&pHead, 2);
	SListPushFront(&pHead, 3);
	SListPushFront(&pHead, 4);
	SListPushFront(&pHead, 5);
	SListPushFront(&pHead, 6);
	SListPrint(pHead);
	SListPopFront(&pHead);
	SListPopFront(&pHead);
	SListPopFront(&pHead);
	SListPopFront(&pHead);
	SListPopFront(&pHead);
	SListPopFront(&pHead);
}
void testSList()
{
	//查找和修改的测试
	SListNode* pHead = NULL;
	SListPushFront(&pHead, 1);
	SListPushFront(&pHead, 2);
	SListPushFront(&pHead, 3);
	SListPushFront(&pHead, 4);
	SListPushFront(&pHead, 5);
	SListPushFront(&pHead, 6);
	SListPrint(pHead);
	SListNode* node = SListFind(pHead, 5);//查找
	if (node)
	{
		//节点的数据
		node->_data = 50;
	}
	SListPrint(pHead);
}
void TestSList1()
{
	//对在pos节点之后进行插入和删除的测试
	SListNode* pHead = NULL;
	SListPushFront(&pHead, 1);
	SListPushFront(&pHead, 2);
	SListPushFront(&pHead, 3);
	SListPushFront(&pHead, 4);
	SListPushFront(&pHead, 5);
	SListPushFront(&pHead, 6);
	SListPrint(pHead);
	SListNode* node = SListFind(pHead, 5);//查找
	if (node)
	{
		
		//插入节点
		SListInsertAfter(node, -2);
		SListPrint(pHead);

		SListErase(node);
		SListPrint(pHead);

	}
	SListDestory(&pHead);
}
int main()
{
	TestSList1();
	return 0;
}

 

9.总结 

        链表与顺序表区别和联系。顺序表是在数组的基础上实现增删查改的。并且插入时可以动态增长。顺序表的缺陷:可能存在空间的浪费,增容有一定的效率损失,中间或者头部数据的删除,时间复杂度是O(n),因为要挪动数据。这些问题都是由链表来解决的,但是链表也有自己的缺陷,不能随机访问,存在内存碎片等问题。 其实没有哪一种数据结构是完美的,它们都有各自的缺陷,实际中的使用都是相辅相成的。

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

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

相关文章

JZ32 从上往下打印二叉树(Java)

题目地址&#xff1a;从上往下打印二叉树_牛客题霸_牛客网 题目回顾&#xff1a; 不分行从上往下打印出二叉树的每个节点&#xff0c;同层节点从左至右打印。例如输入{8,6,10,#,#,2,1}&#xff0c;如以下图中的示例二叉树&#xff0c;则依次打印8,6,10,2,1(空节点不打印&…

keil下载程序具体过程2:硬件链路

引言 本篇博客将介绍keil下载程序的过程中&#xff0c;镜像文件将经过哪些硬件&#xff0c;以及简单的介绍他们之间的协议。 一、硬件连接 图1 硬件连接 将PC、jlink、芯片使用ubs线、swd线连接好之后&#xff0c;在PC上的keil软件中&#xff0c;我们选择对应的仿真器&#xf…

滤波器必须掌握的关键知识

一个理想滤波器应该是在通频带内具有均匀且稳定的增益&#xff0c;对信号的其余频带则具有无穷大的衰减。然后&#xff0c;各种实际的频率响应曲线从阻带或从通带到阻带总有一个逐渐过渡的过程&#xff0c;距离理想情况有一定距离&#xff0c;不像其那样跳跃变化&#xff0c;因…

大O表示法表示算法运行时间

大O表示法用来度量一个算法的运行时间。书写为O(n)&#xff0c;其中n为一个算法所执行的操作次数。当我们讨论算法的运行时间时&#xff0c;说的是一个算法在给定的输入列表增加的情况下算法执行操作数的增速&#xff0c;也就是运行时间的增速。 二分查找算法 下面介绍两种简…

Matlab的Filter Designer工具设计二阶低通滤波器

Matlab版本&#xff1a;2018b 本文要求&#xff1a;设计一个二阶巴特沃斯低通滤波器用于嵌入式软件滤波&#xff0c;传感器采样频率是20KHz&#xff0c;截止频率是333Hz&#xff0c;获取滤波系数&#xff0c;本文不包括二阶滤波推导和代码编写。 打开Matlab->APP->Filt…

多线程中常见的成员方法

1.常见的成员方法 如果没有给线程设置名字&#xff0c;线程也是有默认名字的&#xff0c;格式&#xff1a; Thread-X&#xff08;X序号&#xff0c;从0开始的&#xff09; 2.创建名字的线程构造 用构造方法给线程设置名字 当JVM虚拟机启动之后&#xff0c;会自动的启动多条…

shell脚本开发

shell脚本语言属于弱类型的语言&#xff0c;无需声明变量类型&#xff0c;直接定义使用 shell语言定义的变量&#xff0c;数据类型默认都是字符串类型 调用历史记录命令&#xff1a;&#xff01; 历史记录id

ipconfig显示的内容分析(二)子网掩码

在cmd中输入ipconfig&#xff0c;经常会看到子网掩码。如下&#xff1a; 子网掩码225.225.225.0的二进制数是[1111 1111].[1111 1111].[1111 1111].[0000 0000]。 子网掩码225.225.224.0的二进制数是[1111 1111].[1111 1111].[1111 1110].[0000 0000]。 子网掩码的作用是什么…

C语言 ——函数指针变量

1、概念&#xff1a; 数组指针 - 是指针-是指向数组的指针&#xff0c;是存放数组地址的指针 函数指针 - 是指针 是指向函数的指针 - 是存放函数地址的指针!! 如上所示&#xff0c;函数也是具有地址的&#xff0c;而存放函数地址的指针变量成为函数指针变量。 而有如上所示&a…

奇舞周刊第502期:CSS 的 will-change,为什么有时候能优化几十倍的性能?

记得点击文章末尾的“ 阅读原文 ”查看哟~ 下面先一起看下本期周刊 摘要 吧~ 奇舞推荐 ■ ■ ■ CSS 的 will-change&#xff0c;为什么有时候能优化几十倍的性能&#xff1f; will-change 一个既陌生又熟悉的属性&#xff0c;以前在使用这个属性的时候&#xff0c;单纯是因为要…

VS Code中C++程序的调试(Debug)功能

有一个.vscode文件&#xff0c;存放当前工作区相关配置文件的目录。 launch.json {"version": "0.2.0","configurations": [{"name": "gcc.exe - 生成和调试活动文件", // 该调试任务的名字&#xff0c;启动调试时会在待…

智能优化算法:猎豹优化算法-附代码

智能优化算法&#xff1a;猎豹优化算法 文章目录 智能优化算法&#xff1a;猎豹优化算法1.猎豹优化算法1.1 初始化1.2 搜索策略1.3坐等策略1.4攻击策略 2.实验结果3.参考文献4.Matlab5.python 摘要&#xff1a;CO算法是Mohammad AminAkbari等人于2022年受自然界猎豹狩猎启发而提…

linux学习(进程管理)[8]

创建进程 myproc.c #include <stdio.h> #include <unistd.h>int main() {printf("我是父进程\n");pid_t id fork();if(id < 0){printf("创建子进程失败\n");return 1;}else if(id 0){while(1){printf("我是子进程&#xff1a; pid…

7.利用matlab完成 符号方阵的特征值分解和 符号矩阵的奇异值分解 (matlab程序)

1.简述 &#xff08;1&#xff09;特征值分解&#xff1a;函数eig 格式&#xff1a;[V,D] eig(A) %计算A的特征值对角阵D和特征向量V&#xff0c;使AVVD成立。 注意&#xff1a;特征值分解时&#xff0c;使用eig&#xff0c;矩阵A必须是方阵。 A [0 1;1 1]; [V,D] ei…

JAVA初体验 (HelloWorld)

1.编写代码 文件名字HelloWorld.java public class HelloWorld{public static void main(String [] args){System.out.printf("hello world!");} } 2.编译&#xff08;cmd&#xff09; javac HelloWorld.java 运行完之后 会出现一个 HelloWorld.class 3.运行 j…

APS生产排产将排程计划可视化

什么是生产计划可视化&#xff1f; 在制定生产计划时&#xff0c;由于是多工序的生产&#xff0c;物料及设备的匹配&#xff0c;生产工艺路线的安排&#xff0c;产品结构的不同等因素&#xff0c;生产计划的排程有一定的难度。 那么如何将一个复杂的生产计划转化为一张清晰明了…

Python分享之 Spider

一、网络爬虫 网络爬虫又被称为网络蜘蛛&#xff0c;我们可以把互联网想象成一个蜘蛛网&#xff0c;每一个网站都是一个节点&#xff0c;我们可以使用一只蜘蛛去各个网页抓取我们想要的资源。举一个最简单的例子&#xff0c;你在百度和谷歌中输入‘Python&#xff0c;会有大量和…

Python(三)

诚信像一面镜子&#xff0c;一旦打破&#xff0c;你的人格就会出现裂痕。 存在短路的情景 谢谢观看 Python(三)

造个破谣而已,咋还用上AI了?

最近&#xff0c;央视等各大媒体纷纷曝光了全国多起用AI炮制网络谣言的案例&#xff0c;其中涉及灾害、安全事故、刑事案件等多类谣言内容&#xff0c;造谣的方式更是从文案到图片、视频不一而足。 看到这样的消息&#xff0c;总是会加重我们对AI的担忧&#xff0c;联想到此前的…

承接各种设计

小弟985研究生毕业&#xff0c;目前攻读读博士&#xff0c;可做各种设计&#xff0c;包括但不限于Matlab 电力电子/电气工程&#xff0c;matlab/simulink 电气专业仿真MATLAB 电气工程专业&#xff0c;matlab建模 电力电子&#xff0c;电气工程&#xff0c;电力系统&#xff0c…