【数据结构】单链表(线性表)的实现

news2024/11/15 11:16:05

目录

      一、什么是链表

      二、单链表的实现

            1、动态申请一个结点

            2、单链表打印

            3、单链表尾插

            4、单链表的尾删

            5、单链表的头插

            6、单链表头删

            7、单链表查找

            8、单链表在pos位置之后插入x

            9、单链表删除pos位置之后的值

            10、单链表在pos位置之前插入x

            11、单链表删除pos位置的值

            12、销毁链表

      三、源代码

            1、SList.h

            2、SList.c

            3、test.c


一、什么是链表

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

单链表的声明如下:

//声明单链表
typedef struct SListNode
{
	SLTDataType data;
	struct SListNode* next;
}SLTNode;

链表的逻辑结构:

链表的物理结构:

注意:

1、从上图可看出,链式结构在逻辑上是连续的,但是在物理上不一定连续。

2、现实中的结点一般都是从堆上申请出来的。

3、从堆上申请空间,是按照一定的策略来分配的,两次申请的空间可能连续,也可能不连续。

假设在32位系统上,结点中值域为int类型,则一个节点的大小为8个字节,则也可能有下述链表:

每个链表都是由一节节的链结组成的,我们称之为节点。其中,每一个节点都是由两部分组成的,存储数据的部分叫做数据域,存储地址的部分叫做指针域。指向第一个节点的指针称之为头指针

二、单链表的实现

  1、动态申请一个结点 

在链表中插入一个数据,首先需要先动态申请一个结点,并将该结点的数值域与指针域进行赋值,指针域都设置为NULL。

//动态申请一个节点
SLTNode* BuySLTNode(SLTDataType x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	newnode->data = x;
	newnode->next = NULL;
	return newnode;
}

  2、单链表打印

 在这里我的思路是用while循环将链表遍历一遍,从首个结点开始移动,直到NULL结束。

//打印链表
void SLTPprint(SLTNode* phead)
{
	SLTNode* cur = phead;
	while (cur != NULL)
	{
		printf("%d->", cur->data);
		cur = cur->next;
	}
	printf("NULL\n");
}

  3、单链表尾插

尾插就需要我们先动态开辟一个新的节点 。然后让前面的指针指向新的节点。但是要分两种情况如下图所示:

因为链表为NULL时,plist指针指向的是空,此时不能进行解引用操作,所以如果为NULL时,直接进行赋值就行。当链表不为空时,直接在后面进行尾插。 

注意:

在这里使用的是二级指针,因为需要改变结构体里面的数值,而头指针是一个结构体类型的指针,而指针也是一种变量。假设我们用的是一级指针,当链表为空,我们进行尾插的时候,我们会将头指针内的数据改为新节点的地址。我们可以找到,一级指针的传递方式不会引起实参的变化。因此,当此函数结束后,我们的头指针依旧是空。因此,我们这里需要传入的是二级指针,从而实现地址传递。

//尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
	SLTNode* newnode = BuySLTNode(x);
	if (*pphead == NULL)
	{
		*pphead = newnode;
	}
	else
	{
		SLTNode* tail = *pphead;
		//找尾
		while (tail->next)
		{
			tail = tail->next;
		}
		tail->next = newnode;
	}
}

  4、单链表的尾删

在这里首先要要声明一下,保证链表不为空,然后再分两种情况:(1)如果链表长度大于1时,只需要将倒是第二个节点的指针域设置为空指针,并且将最后一个节点释放掉。(2)如果链表长度为1时,只需将头指针置空,第一个节点释放掉就行。

//单链表尾删
void SLTPopBack(SLTNode** pphead)
{
	//暴力的检查
	assert(*pphead);
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		SLTNode* tail = *pphead;
		while (tail->next->next)
		{
			tail = tail->next;
		}
		free(tail->next);
		tail->next = NULL;
	}	
}

  5、单链表的头插

头插也是先要开辟一个新的节点,然后直接让新节点的next链接到头节点上,最后重新更新一下头节点。

//头插
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
	SLTNode* newnode = BuySLTNode(x);
	newnode->next = *pphead;
	*pphead = newnode;
}

  6、单链表头删

头删首先还是要声明一下,保证链表不为空,然后保存头指针指向指针域中所对的地址空间,最后释放头节点即可。

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

  7、单链表查找

 直接用while循环将链表遍历一遍。注意返回值返回的是空指针或者所查元素的地址。

//查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{
	SLTNode* cur = phead;
	while (cur)
	{
		if (cur->data == x)
		{
			return cur;
		}
		else
			cur = cur->next;
	}
	return NULL;
}

  8、单链表在pos位置之后插入x

在pos位置之后插入x时,pos位置所对的节点的指针域中就记录了后面的节点位置。因此可以直接插入x所对应的节点。

//在pos之后插入x
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{
	assert(pos);
	SLTNode* newnode = BuySLTNode(x);
	newnode->next = pos->next;
	pos->next = newnode;
}

  9、单链表删除pos位置之后的值

在删除pos位置之后的节点时,如果pos->next为空时,说明pos位置就是最后一个节点,它的后面已经没有节点了,所以直接返回即可,如果pos->next不为空时,要保存住这个位置,然后重新链接pos->next = pos->next->next(就相当于下面代码中pos->next = nextNode->next),最后释放pos->next。

//删除pos之后的值
void SLTEraseAfter(SLTNode* pos)
{
	assert(pos);
	if (pos->next == NULL)
	{
		return;
	}
	else
	{
		SLTNode* nextNode = pos->next;
		pos->next = nextNode->next;
		free(nextNode);
	}
}

  10、单链表在pos位置之前插入x

在pos位置前面插入新的节点,分两种情况:(1)如果pos的位置就是头节点时,就相当于头插。(2)如果pos位置不是头节点时,要找到pos位置原链表的前方的节点。然后让该节点指向所插入的节点,然后让所插入的节点指向pos所对的节点。

//在pos之前插入x
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
	assert(pos);
	if (*pphead == pos)
	{
		SLTPushFront(pphead, x);
	}
	else
	{
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		SLTNode* newnode = BuySLTNode(x);
		prev->next = newnode;
		newnode->next = pos;
	}
}

  11、单链表删除pos位置的值

删除pos位置的节点,分两种情况:(1)如果pos的位置就是头节点时,就相当于头删。(2)如果pos的位置不是头节点时,先保存pos位置后面的节点,然后让该节点链接pos位置之前的节点,最后再释放掉pos位置的节点。

//删除pos位置的数据
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
	assert(pos);
	if (pos == *pphead)
	{
		SLTPopFront(pphead);
	}
	else
	{
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		prev->next = pos->next;
		free(pos);
	}
}

  12、销毁链表

销毁链表的时候,我们不能简单只释放头指针。这样是没有将空间完全释放掉的,这只是释放了头节点。所以可以设置一个指针cur,让它用while循环将链表从头到尾都释放一遍,最后将头指针设置为空。一定要将头指针设置为空指针!否则会出现野指针的问题!

//销毁
void SLTDestroy(SLTNode** pphead)
{
	SLTNode* cur = *pphead;
	while (cur)
	{
		SLTNode* next = cur->next;
		free(cur);
		cur = next;
	}
	*pphead = NULL;
}

三、源代码

  1、SList.h

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


typedef int SLTDataType;

//声明单链表
typedef struct SListNode
{
	SLTDataType data;
	struct SListNode* next;
}SLTNode;

//动态申请一个节点
SLTNode* BuySLTNode(SLTDataType x);
//创建循环节点
SLTNode* CreateSList(int n);
//打印链表
void SLTPprint(SLTNode* phead);
//查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x);

//尾插尾删
void SLTPushBack(SLTNode** pphead, SLTDataType x);
void SLTPopBack(SLTNode** pphead);
//头插头删
void SLTPushFront(SLTNode** pphead, SLTDataType x);
void SLTPopFront(SLTNode** pphead);


//在pos之后插入x
void SLTInsertAfter(SLTNode* pos, SLTDataType x);
//删除pos之后的值
void SLTEraseAfter(SLTNode* pos);

//在pos之前插入x
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);

//删除pos位置的数据
void SLTErase(SLTNode** pphead, SLTNode* pos);

//销毁
void SLTDestroy(SLTNode** pphead);

  2、SList.c

#include "SList.h"

//动态申请一个节点
SLTNode* BuySLTNode(SLTDataType x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	newnode->data = x;
	newnode->next = NULL;
	return newnode;
}

//创建循环节点
SLTNode* CreateSList(int n)
{
	SLTNode* phead = NULL, * ptail = NULL;
	for (int i = 0; i < n; i++)
	{
		SLTNode* newnode = BuySLTNode(i);
		if (phead == NULL)
		{
			ptail = phead = newnode;
		}
		else
		{
			ptail->next = newnode;
			ptail = newnode;
		}
	}
	return phead;
}

//打印链表
void SLTPprint(SLTNode* phead)
{
	SLTNode* cur = phead;
	while (cur != NULL)
	{
		//printf("[%d | %p]->", cur->data, cur->next);
		printf("%d->", cur->data);
		cur = cur->next;
	}
	printf("NULL\n");
}
//尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
	SLTNode* newnode = BuySLTNode(x);
	if (*pphead == NULL)
	{
		*pphead = newnode;
	}
	else
	{
		SLTNode* tail = *pphead;
		//找尾
		while (tail->next)
		{
			tail = tail->next;
		}
		tail->next = newnode;
	}
}
void SLTPopBack(SLTNode** pphead)
{
	//暴力的检查
	assert(*pphead);
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		SLTNode* tail = *pphead;
		while (tail->next->next)
		{
			tail = tail->next;
		}
		free(tail->next);
		tail->next = NULL;
	}	
}
//头插
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
	SLTNode* newnode = BuySLTNode(x);
	newnode->next = *pphead;
	*pphead = newnode;
}
//头删
void SLTPopFront(SLTNode** pphead)
{
	assert(*pphead);
	SLTNode* next = (*pphead)->next;
	free(*pphead);
	*pphead = next;
}

//查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{
	SLTNode* cur = phead;
	while (cur)
	{
		if (cur->data == x)
		{
			return cur;
		}
		else
			cur = cur->next;
	}
	return NULL;
}

//在pos之后插入x
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{
	assert(pos);
	SLTNode* newnode = BuySLTNode(x);
	newnode->next = pos->next;
	pos->next = newnode;
}

//在pos之前插入x
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
	assert(pos);
	if (*pphead == pos)
	{
		SLTPushFront(pphead, x);
	}
	else
	{
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		SLTNode* newnode = BuySLTNode(x);
		prev->next = newnode;
		newnode->next = pos;
	}
}

//删除pos之后的值
void SLTEraseAfter(SLTNode* pos)
{
	assert(pos);
	if (pos->next == NULL)
	{
		return;
	}
	else
	{
		SLTNode* nextNode = pos->next;
		pos->next = nextNode->next;
		free(nextNode);
	}
}

//删除pos位置的数据
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
	assert(pos);
	if (pos == *pphead)
	{
		SLTPopFront(pphead);
	}
	else
	{
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		prev->next = pos->next;
		free(pos);
	}
}

//销毁
void SLTDestroy(SLTNode** pphead)
{
	SLTNode* cur = *pphead;
	while (cur)
	{
		SLTNode* next = cur->next;
		free(cur);
		cur = next;
	}
	*pphead = NULL;
}

  3、test.c

TestSList1()
{
	SLTNode* plist = NULL;
	SLTPushBack(&plist, 1);
	SLTPushBack(&plist, 2);
	SLTPushBack(&plist, 3);
	SLTPushBack(&plist, 4);
	SLTPushBack(&plist, 5);
	SLTPprint(plist);

	SLTPopBack(&plist);
	SLTPprint(plist);
	
	SLTPushFront(&plist, 100);
	SLTPushFront(&plist, 200);
	SLTPprint(plist);

	SLTPopFront(&plist);
	SLTPprint(plist);

	SLTNode* p = SLTFind(plist, 2);
	SLTInsertAfter(p, 30);
	SLTPprint(plist);

	p = SLTFind(plist, 1);
	SLTEraseAfter(p);
	SLTPprint(plist);
	SLTDestroy(&plist);

}
int main()
{
    TestSList1();
	return 0;
 }


本文要是有不足的地方,欢迎大家在下面评论,我会在第一时间更正。

  老铁们,记着点赞加关注哦!!!

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

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

相关文章

前端期末考试试题及参考答案(04)

版权声明 本文原创作者&#xff1a;谷哥的小弟作者博客地址&#xff1a;http://blog.csdn.net/lfdfhl 一、 填空题 在页面中&#xff0c; ______标签用于创建一个表单。< form>中的______属性用于指定接收并处理表单数据的服务器url地址。< form>中的______表示以…

基于springboot+Vue的疫情防控系统(程序+数据库)

大家好✌&#xff01;我是CZ淡陌。一名专注以理论为基础实战为主的技术博主&#xff0c;将再这里为大家分享优质的实战项目&#xff0c;本人在Java毕业设计领域有多年的经验&#xff0c;陆续会更新更多优质的Java实战项目&#xff0c;希望你能有所收获&#xff0c;少走一些弯路…

Linux 资源限制 setrlimit

有的时候为了避免程序毫无意义的占用CPU&#xff08;如死循环&#xff09;、过度占用内存&#xff08;如内存泄漏&#xff09;&#xff0c;我们可以限制程序使用的资源。 下面主要从两个角度限制资源&#xff1a; 限制程序累计运行时长限制可以使用的内存大小 限制资源使用到…

微信小程序 | 小程序系统API调用

&#x1f5a5;️ 微信小程序专栏&#xff1a;小程序系统API调用 &#x1f9d1;‍&#x1f4bc; 个人简介&#xff1a;一个不甘平庸的平凡人&#x1f36c; ✨ 个人主页&#xff1a;CoderHing的个人主页 &#x1f340; 格言: ☀️ 路漫漫其修远兮,吾将上下而求索☀️ &#x1f44…

Go语言进阶

一、并发 VS 并行 1: 多线程程序在一个核的CPU上运行 2: 多线程程序在多个核的CPU上运行 Go可以充分发挥多核优势&#xff0c;高效运行。 线程&#xff1a;用户态&#xff0c;轻量级线程&#xff0c;栈MB级别。 协程&#xff1a;内核态&#xff0c;线程内跑多个协程&#xff…

最短路径的java代码实现

1.最短路径定义及性质 有了加权有向图之后&#xff0c;我们立刻就能联想到实际生活中的使用场景&#xff0c;例如在一副地图中&#xff0c;找到顶点a与地点b之间的路径&#xff0c;这条路径可以是距离最短&#xff0c;也可以是时间最短&#xff0c;也可以是费用最小等&#xff…

爬虫的奇技淫巧之ajax-hook

声明 本文仅供学习参考&#xff0c;如有侵权可私信本人删除&#xff0c;请勿用于其他途径&#xff0c;违者后果自负&#xff01; 前言 随着反爬力度的不断升级&#xff0c;现在的爬虫越来越难搞了。诸如加密参数sign、signature、token。面对这种情况传统的方式可以使用自动…

Redis集群系列九 —— 集群伸缩之扩容

集群伸缩 Redis 集群提供了灵活的节点扩容和收缩方案&#xff0c;当有新节点加入时&#xff0c;需要把一部分数据迁移到新节点来达到集群的负载均衡&#xff1b;当旧节点退出时&#xff0c;需要把其上的数据迁移到其他节点上&#xff0c;确保该节点上的数据能够被正常访问。从…

ext4 extent详解3之内核源码流程讲解

本文在前两篇《ext4 extent详解1之示意图演示》和《ext4 extent详解2之内核源码详解》讲解ext4 extent 文章的基础上&#xff0c;本文从内核源码、实例演示等角度详细介绍ext4 extent B树的形成过程&#xff0c;希望看过本文的读者能理解ext4 extent B树的工作原理。 1 &#…

6.3 返回类型和return语句

文章目录无返回值函数有返回值函数值是如何被返回的不要返回局部对象的引用或指针引用返回左值列表初始化返回值主函数main的返回值递归返回数组指针声明一个返回数组指针的函数使用尾置返回类型使用decltypereturn语句终止当前正在执行的函数并将控制权返回到调用该函数的地方…

2022年终总结:点滴积累让我不再迷茫

今年是开始写作的第二年&#xff0c;如果说第一年是起步的话&#xff0c;今年就是开始有了一些小收获了&#xff0c;通过点滴积累让我知道积累的充实感&#xff0c;通过一点一点粉丝或阅读量的积累&#xff0c;增加写作的自信。 今年的收获 首先看一下今年的阅读量和粉丝量: …

cheunghonghui的【22年度总结】

cheunghonghui的【22年度总结】 好久好久没写博客了&#xff0c;看了下后台&#xff0c;上一次发表博客已经是一年半之前&#xff0c;趁着年底&#xff0c;抓紧时间写&#xff08;水&#xff09;一篇不然就要断更了。 【年度工作总结】 1、迭代了未知bug 2、修复了已知bug …

迎接2023,他真的想说“新年快乐”

&#x1f60a;你好&#xff0c;我是小航&#xff0c;一个正在变秃、变强的文艺倾年。 &#x1f514;2023年快要到来啦&#xff0c;再此祝大家诸事顺遂&#xff0c;所见所盼皆如愿。 &#x1f514;本文讲解如何使用Java演奏一首歌曲&#xff0c;一起卷起来叭&#xff01; 众所周…

Faster RCNN网络源码解读(Ⅶ) --- RPN网络代码解析(中)RegionProposalNetwork类解析

目录 一、代码作用&#xff08;rpn_function.py&#xff09; 二、代码解析 2.1 RegionProposalNetwork类 2.1.1 初始化函数__init__ 2.1.2 正向传播过程forward 2.1.3 concat_box_prediction_layers函数 2.1.4 permute_and_flatten 2.1.5 filter_proposals 2.1.6 _…

2022 许些遗憾 年终总结

目录回首过去展望未来验收 2022年任务清单 ---------------------------》 2023年 flag2023 展望回首过去 此刻&#xff0c;想想这一年&#xff0c;口罩&#xff0c;38.5℃&#xff0c;艰难时刻&#xff0c;终究在2022最后十天被确诊了“阳”&#xff0c;没有备任何药&#xff…

Linux系列——Linux操作系统安装及服务控制(1)

作者简介&#xff1a;一名在校云计算网络运维学生、每天分享网络运维的学习经验、和学习笔记。 座右铭&#xff1a;低头赶路&#xff0c;敬事如仪 个人主页&#xff1a;网络豆的主页​​​​​​ 目录 前言 一.Linux介绍 1.Linux是什么&#xff1f; 2.Linux系统的优点 …

ArcGIS基础实验操作100例--实验31纠正栅格坐标

本实验专栏参考自汤国安教授《地理信息系统基础实验操作100例》一书 实验平台&#xff1a;ArcGIS 10.6 实验数据&#xff1a;请访问实验1&#xff08;传送门&#xff09; 高级编辑篇--实验31 纠正栅格坐标 目录 一、实验背景 二、实验数据 三、实验步骤 &#xff08;1&…

2023新年祝福代码[css动画特效]

目录 前言 一、jQuery之家 二、2023新年祝福页面 2.1 我的博客和祝福语 2.2 我的博客头像和动态烟花 ​编辑 2.3 背景为动图 三、完整效果图 总结 前言 心态还需努力呀在这里祝大家新的一年里愿望都能实现。2022年已经过去&#xff0c;2022年的遗憾、不开心&#xff…

Spring学习笔记(1)

Spring系统架构 Spring Framework是Spring生态圈中最基础的项目&#xff0c;是其他项目的根基。 Spring Framework系统架构 学习线路 核心概念 IoC( Inversion of Control )控制反转 使用对象时&#xff0c;由主动new产生对象转换为由外部提供对象&#xff0c;此过程中对象…

DoIP协议从入门到精通—Alive check

惯例,为避免自己成为高知识低文化的汉子,分享一段喜欢的文字: 一、Socket 概念 在DoIP(Diagnostic on IP)概念中,通信的核心是Socket(套接字,实际通信的载体),是车载以太网在诊断范畴进行通信的句柄,Socket是支持TCP/IP协议的网络通信的基本操作单元。对于Socket: …