链表(数据结构)

目录

链表

链表的分类

1、单向或者双向

2、带头或者不带头

3、循环或者非循环

总结:

单链表

创建链式结构

创建新节点

尾插

尾删

头插

头删

查找节点

在pos位置后插入

删除pos位置后的节点

销毁

总代码


链表

概念:

链表是一种物理结构上非连续的、非顺序的存储结构,数据元素的逻辑顺序是通过链表结构中的指针链接起来的!前一个元素中存放着后一个元素的地址!

结构:

结构中存放着链表下一个元素的地址!用一个指针来存储!

一般情况下一个链表结构中存放的是元素的值以及下一个位置的指针!或者多个链接元素的指针!

链表的分类

1、单向或者双向

单向链表:

链表结构中只有一个指针,该指针是存放的是下一个元素的地址的指针变量!


看图理解:


双向链表:

结构中有两个指针,分别记录它的前一个元素和后一个元素的地址!


看图理解:


2、带头或者不带头

1、带哨兵位的头节点的单向链表:


2、带哨兵位头节点的双向链表:


3、循环或者非循环

1、单向循环链表


2、双向循环链表 


3、单向带头循环链表 


4、带头双向循环链表 


总结:

1、图形

以上图都是链表的逻辑结构,物理上也就是底层并非是这样指向的,而是每个指针里面都存储它所对应节点的地址!


2、种类

链表类型总共有八种,分别是按照以上的三大类来组合:

第一类:单向或双向

第二类:带头或不带头

第三类:循环或不循环


八种链表:

1、单向链表

2、双向链表

3、单向带头链表

4、双向带头链表

5、单向循环链表

6、双向循环链表

7、单向带头循环链表

8、双向带头循环链表


本篇博主只带大家写一种链表:单链表,因为只要会了一种来链表,其它链表都是大同小异的!


单链表

概念:

单链表中的节点,后一个节点中存的是前一个节点的地址,最后一个节点的中的地址为空!


逻辑结构:


物理结构:

 


总结:

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

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

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


下面我们用代码实现单链表!


创建链式结构

思路:

我们用一个结构体来封装链表节点的值以及该类型结构的地址,值是链表节点中要保存的值,结构体的的地址是用来存储前一个结构节点的地址,若该节点是最后一个则地址里面的值是NULL


代码:

//用typedef重命名,后续可以继续使用
typedef int SLDateType;

//创建链表结构
typedef struct SListNode
{
	SLDateType val;//节点值
	struct SListNode* next;//下一个节点的位置

}SLNode;

创建新节点

思路:

创建新节点,要将链表链起来,我们就得要创造新的节点进行链接,才能形成链接功能。该功能可供我们用于头插,尾插,等功能中!


代码:

//创建新节点
SLNode* CreateNode(SLDateType x)
{
	SLNode* newnode = (SLNode*)malloc(sizeof(SLNode));
	//判断是否开辟成功
	if (newnode == NULL)
	{
		perror("malloc fali:");
		return NULL;
	}
	//开始赋值
	newnode->val = x;
	newnode->next = NULL;

	//返回
	return newnode;
}

尾插

思路:

单链表进行尾插,先要找到单链表的尾节点,随后在创建一个要插入的节点,让找到的尾节点的指针指向要插入的新节点,也就是尾节点中的结构体指针变量中保存要插入的新节点的地址!


代码:

//尾插
void SListPushBank(SLNode** pphead, SLDateType x)
{
	//创建新节点
	SLNode* newnode = CreateNode(x);

	//判断是否是空链表,空链表直接进行尾插
	if (*pphead == NULL)
	{
		*pphead = newnode;
	}
	else
	{
		//找尾
		SLNode* cur = *pphead;
		while (cur->next != NULL)
		{
			cur = cur->next;
		}
		
		//进行链接
		cur->next = newnode;
	}
}

尾删

思路:

单链表进行尾删,先进行循环找尾节点,同时用一个指针来记录尾节点的前一个节点的地址。找到尾节点之后,将尾节点的前一个节点中的指针置为NULL。再将尾节点的空间进行free释放!最后将尾节点结构中的指针置为NULL


代码:

//尾删
void SListPopBank(SLNode** pphead)
{
	//如果是NULL链表就不能再删
	assert(*pphead);

	//判断一个的情况
	if ((*pphead)->next == NULL)
	{
		free((*pphead)->next);
		*pphead= NULL;
	}
	else
	{
		//找尾
		SLNode* cur = *pphead;
		SLNode* tail = NULL;
		while (cur->next != NULL)
		{
			tail = cur;
			cur = cur->next;
		}

		//释放节点
		free(cur);
		cur = NULL;

		//将前一个节点置为NULL
		tail->next = NULL;
	}

}

头插

思路:

单链表头插,就是创建一个新节点,让这个新节点结构中的指针指向头节点,再让新节点成为头节点即可!


代码:

//头插
void SListPushFront(SLNode** pphead, SLDateType x)
{
	SLNode* newnode = CreateNode(x);

	SLNode* tail = *pphead;
	*pphead = newnode;
	newnode->next = tail;

}

头删

思路:

单链表头删,用一个指针指向头后面的节点,随后将头节点空间释放,再让后面的节点成为头即可!


代码:

//头删
void SListPopFront(SLNode** pphead)
{
	//如果是空链表就不进行删除
	assert(*pphead);

	SLNode* cur = (*pphead)->next;
	SLNode* tail = *pphead;
	free(tail);
	tail = NULL;
	*pphead = cur;
}

查找节点

思路:

循环遍历链表,找到我们要查找的节点,返回该节点的地址即可!若找不到返回NULL


代码:

//节点查找
SLNode* SListFind(SLNode* phead, SLDateType x)
{
	SLNode* cur = phead;
	while (cur)
	{
		if (cur->val == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}

在pos位置后插入

思路:

用一个指针记录pos位置的下一个节点,随后让pos节点结构体的指针指向新插入的节点。再让新插入节点结构的指针指向pos位置的下一个节点完成链接!


代码:
 

// 在pos位置之后插入x
void SListInsertAfter(SLNode* pos, SLDateType x)
{
	//后面插入pos为NULL 不行
	assert(pos);

	SLNode* newnode = CreateNode(x);
	SLNode* cur = pos->next;
	pos->next = newnode;
	newnode->next = cur;
}

删除pos位置后的节点

思路:

用一个指针记录pos位置的下一个节点。再用一个指针记录pos位置的下下个节点,也就是pos位置的下一个节点的下一个节点,随后将pos位置的下一个节点空间释放。让pos位置节点结构中的指针指向pos位置的下下个节点即可!


代码:

// 删除pos位置之后的值
void SListEraseAfter(SLNode* pos)
{
	//pos为NULL 不可删后面的
	assert(pos);
	SLNode* cur = pos->next;

	if (cur == NULL)
	{
		free(cur);
		cur = NULL;
		pos->next = NULL;
	}
	else
	{
		SLNode* tail = cur->next;
		free(cur);
		pos->next = tail;
	}
}

销毁

思路:

循环遍历链表,用一个指针记录销毁节点的后一个节点,一个指针记录销毁节点,让两者都往后走,依次循环进行销毁(释放节点空间)!


代码:

// 销毁
void SListDestroy(SLNode* phead)
{
	SLNode* cur = phead;
	SLNode* tail = cur;
	while (cur)
	{
		tail = cur;
		cur = cur->next;
		free(tail);
	}
	tail = NULL;
	phead = NULL;
}

总代码

#define _CRT_SECURE_NO_WARNINGS 1


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>

//用typedef重命名,后续可以继续使用
typedef int SLDateType;

//创建链表结构
typedef struct SListNode
{
	SLDateType val;//节点值
	struct SListNode* next;//下一个节点的位置

}SLNode;


//打印
void SListPrint(SLNode* phead)
{
	SLNode* cur = phead;
	while (cur != NULL)
	{
		printf("%d ->", cur->val);
		cur = cur->next;
	}
	printf("NULL\n");
}

//创建新节点
SLNode* CreateNode(SLDateType x)
{
	SLNode* newnode = (SLNode*)malloc(sizeof(SLNode));
	//判断是否开辟成功
	if (newnode == NULL)
	{
		perror("malloc fali:");
		return NULL;
	}
	//开始赋值
	newnode->val = x;
	newnode->next = NULL;

	//返回
	return newnode;
}

//尾插
void SListPushBank(SLNode** pphead, SLDateType x)
{
	//创建新节点
	SLNode* newnode = CreateNode(x);

	//判断是否是空链表,空链表直接进行尾插
	if (*pphead == NULL)
	{
		*pphead = newnode;
	}
	else
	{
		//找尾
		SLNode* cur = *pphead;
		while (cur->next != NULL)
		{
			cur = cur->next;
		}

		//进行链接
		cur->next = newnode;
	}
}

//尾删
void SListPopBank(SLNode** pphead)
{
	//如果是NULL链表就不能再删
	assert(*pphead);

	//判断一个的情况
	if ((*pphead)->next == NULL)
	{
		free((*pphead)->next);
		*pphead = NULL;
	}
	else
	{
		//找尾
		SLNode* cur = *pphead;
		SLNode* tail = NULL;
		while (cur->next != NULL)
		{
			tail = cur;
			cur = cur->next;
		}

		//释放节点
		free(cur);
		cur = NULL;

		//将前一个节点置为NULL
		tail->next = NULL;
	}

}

//头插
void SListPushFront(SLNode** pphead, SLDateType x)
{
	SLNode* newnode = CreateNode(x);

	SLNode* tail = *pphead;
	*pphead = newnode;
	newnode->next = tail;

	空链表直接插入
	//if ((*pphead) == NULL)
	//{
	//	*pphead = newnode;
	//}
	//else
	//{
	//	SLNode* cur = *pphead;
	//	*pphead = newnode;
	//	newnode->next = cur;
	//}
}

//头删
void SListPopFront(SLNode** pphead)
{
	//如果是空链表就不进行删除
	assert(*pphead);

	SLNode* cur = (*pphead)->next;
	SLNode* tail = *pphead;
	free(tail);
	tail = NULL;
	*pphead = cur;
}

//节点查找
SLNode* SListFind(SLNode* phead, SLDateType x)
{
	SLNode* cur = phead;
	while (cur)
	{
		if (cur->val == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}

// 在pos位置之后插入x
void SListInsertAfter(SLNode* pos, SLDateType x)
{
	//后面插入pos为NULL 不行
	assert(pos);

	SLNode* newnode = CreateNode(x);
	SLNode* cur = pos->next;
	pos->next = newnode;
	newnode->next = cur;
}

// 删除pos位置之后的值
void SListEraseAfter(SLNode* pos)
{
	//pos为NULL 不可删后面的
	assert(pos);
	SLNode* cur = pos->next;

	if (cur == NULL)
	{
		free(cur);
		cur = NULL;
		pos->next = NULL;
	}
	else
	{
		SLNode* tail = cur->next;
		free(cur);
		pos->next = tail;
	}
}

// 销毁
void SListDestroy(SLNode* phead)
{
	SLNode* cur = phead;
	SLNode* tail = cur;
	while (cur)
	{
		tail = cur;
		cur = cur->next;
		free(tail);
	}
	tail = NULL;
	phead = NULL;
}


int main()
{
	SLNode* plist = NULL;

	//尾插
	printf("开始尾插:\n");
	SListPushBank(&plist, 1);
	SListPrint(plist);//打印

	SListPushBank(&plist, 2);
	SListPrint(plist);//打印

	SListPushBank(&plist, 3);
	SListPrint(plist);//打印

	SListPushBank(&plist, 4);
	SListPrint(plist);//打印

	SListPushBank(&plist, 5);
	SListPrint(plist);//打印

	//尾删
	printf("开始尾删:\n");
	SListPopBank(&plist);
	SListPrint(plist);//打印

	SListPopBank(&plist);
	SListPrint(plist);//打印

	SListPopBank(&plist);
	SListPrint(plist);//打印

	SListPopBank(&plist);
	SListPrint(plist);//打印

	SListPopBank(&plist);
	SListPrint(plist);//打印

	//头插
	printf("开始头插:\n");
	SListPushFront(&plist, 7);
	SListPrint(plist);//打印

	SListPushFront(&plist, 8);
	SListPrint(plist);//打印

	SListPushFront(&plist, 9);
	SListPrint(plist);//打印

	//头删
	printf("开始头删:\n");
	SListPopFront(&plist);
	SListPrint(plist);//打印

	//SListPopFront(&plist);
	//SListPrint(plist);//打印

	//SListPopFront(&plist);
	//SListPrint(plist);//打印

	printf("查找节点:\n");
	SLNode* cur = SListFind(plist, 8);
	if (cur != NULL)
	{
		printf("找到了:%d \n", cur->val);
	}
	else
	{
		printf("找不到\n");
	}

	printf("pos位置之后插入:\n");
	//pos位置之后插入
	SListInsertAfter(plist->next, 10);
	SListPrint(plist);//打印
	SListInsertAfter(plist, 6);
	SListPrint(plist);//打印

	printf("删除pos位置之后的值:\n");
	// 删除pos位置之后的值
	SListEraseAfter(plist);
	SListPrint(plist);//打印

	SListEraseAfter(plist);
	SListPrint(plist);//打印

	SListEraseAfter(plist);
	SListPrint(plist);//打印

	//销毁
	SListDestroy(plist);
	return 0;
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

用于无线传感器网络路由的改进leach协议(Matlab代码实现)

目录 &#x1f4a5;1 概述 &#x1f4da;2 运行结果 &#x1f389;3 参考文献 &#x1f468;‍&#x1f4bb;4 Matlab代码 &#x1f4a5;1 概述 当前&#xff0c;无线传感器由于技术的发展得到更加广泛的应用&#xff0c;针对无线传感器网络&#xff08;WSN&#xff09;的…

CCED2000后,中文编程软件再次脱颖而出,系出金山

WPS抗衡微软&#xff0c;CCEDE却被淹没&#xff1f; DOS代&#xff0c;我们用WPS来进行文字编辑&#xff0c;CCED来做表格&#xff0c;两者在那个时代可以称得上是国产办公领域的“必装软件”。 如今&#xff0c;30年过去了&#xff0c;WPS一步一步成长为抗衡微软office的国产…

魔兽服务端编译部署NPCBots和 Al机器人模块教程

魔兽服务端编译部署NPCBots和 Al机器人模块教程 大家好,我是艾西。在平时自己一个人玩魔兽的时候是不是会比较无聊,因为游戏机制或副本难度自己一个人无法进行快乐的玩耍。今天艾西教大家编译部署NPCBots和 Al机器人模块,直接一个人玩魔兽也不孤单 首先到GIT去下载ai机器…

类与对象(上)

1.面向过程和面向对象初步认识 C语言是面向过程的&#xff0c;关注的是过程&#xff0c;分析出求解问题的步骤&#xff0c;通过函数调用逐步解决问题。 C是基于面向对象的&#xff0c;关注的是对象&#xff0c;将一件事情拆分成不同的对象&#xff0c;靠对象之间的交互完 成。…

法规标准-UN R152标准解读

UN R152是做什么的&#xff1f; UN R152 全名为关于M1和N1型机动车高级紧急制动系统&#xff08;AEBS&#xff09;型式认证的统一规定&#xff0c;是联合国对于M1和N1型车辆AEBS系统认证的要求说明&#xff0c;当满足其要求内容时&#xff0c;才可通过联合国的认证&#xff0c…

Node【Node.js 20】新特性

文章目录 &#x1f31f;前言&#x1f31f;Node.js 20: 一次重要的升级和改进&#x1f31f;Internationalization API Update&#x1f31f;端口管理器&#x1f31f;字符串处理&#x1f31f; 更好的调试工具&#x1f31f; Crypto模块的更新&#x1f31f;总结&#x1f31f;写在最后…

MPSOC(ZU9EG/ZU15EG)PCIE架构高性能数据预处理 FMC载板设计资料

板卡概述 PCIE707 是一款基于 PCIE 总线架构的高性能数据预处理 FMC载板&#xff0c;板卡具有 1 个 FMC&#xff08;HPC&#xff09;接口&#xff0c;1 路 PCIe x4 主机接口、 1 个 RJ45 千兆以太网口、2 个 QSFP 40G 光纤接口。板卡采用 Xilinx 的高性能 UltraScale MPSOC 系…

linux用户管理指令

这里写自定义目录标题 一 增加新用户及密码二 切换用户三 userdel 删除用户四 查看用户登录信息五 让普通用户成为管理员1. 修改环境配置文件2.设置用户和密码 六 查看创建哪些用户 一 增加新用户及密码 useradd:加用户名 passwd&#xff1a;加用户密码 [rootlocalhost ~]# u…

etcd原理剖析一

为什么Kubernetes使用etcd&#xff1f; 首先我们来看服务高可用以及数据一致性。单副本存在单点故障&#xff0c;而多副本又引入数据一致性问题。 为了解决数据一致性问题&#xff0c;需要引入一个共识算法。例如Raft等。etcd选择了Raft&#xff0c;它将复杂的一致性问题分解…

【SpringBoot】SpringBoot集成ElasticSearch

文章目录 第一步&#xff0c;导入jar包&#xff0c;注意这里的jar包版本可能和你导入的不一致&#xff0c;所以需要修改第二步&#xff0c;编写配置类第三步&#xff0c;填写yml第四步&#xff0c;编写util类第五步&#xff0c;编写controller类第六步&#xff0c;测试即可 第一…

基于FPGA+JESD204B 时钟双通道 6.4GSPS 高速数据采集模块设计(二)研究 JESD204B 链路建立与同步的过程

基于 JESD204B 的采集与数据接收电路设计 本章将围绕基于 JESD204B 高速数据传输接口的双通道高速数据采集实现展 开。首先&#xff0c;简介 JESD204B 协议、接口结构。然后&#xff0c;研究 JESD204B 链路建立与同 步的过程。其次&#xff0c;研究基于 JESD204B …

网易云音乐开发--主页静态页面搭建

如何用VScode来开发小程序 wxml和wxss来高亮小程序 窗口设置 轮播图制作 就是通过swiper来设置轮播图 iconfont字体图标使用 这里要借助阿里的iconfonticonfont-阿里巴巴矢量图标库 找到自己喜欢的图标&#xff0c;添加到购物车 添加到项目 这样就可以统一的管理图标的库 …

三分钟教你看懂 spring 官方文档

新手如何学会查看官方文档API 首先进入官网&#xff1a;这里以 spring boot 为例 &#xff0c;进入spring 官方地址 我们进入 spring boot 这里我们要看文档当然是要 learn 了&#xff0c;所以点进去。 我需要的东西在 IO 模块里面&#xff0c;点 IO 进入 发送邮件是不是有了…

MyBatisPlus代码生成器使用

MybatisPlus特点 无侵入&#xff1a;只做增强不做改变&#xff0c;引入它不会对现有工程产生影响&#xff0c;如丝般顺滑 损耗小&#xff1a;启动即会自动注入基本 CURD&#xff0c;性能基本无损耗&#xff0c;直接面向对象操作 强大的 CRUD 操作&#xff1a;内置通用 Mappe…

Java8新特性函数式编程 - Lambda、Stream流、Optional

1.Lambda表达式 1.1 概述 ​ Lambda是JDK8中一个语法糖。他可以对某些匿名内部类的写法进行简化。它是函数式编程思想的一个重要体现。让我们不用关注是什么对象。而是更关注我们对数据进行了什么操作。 1.2 核心原则 可推导可省略 1.3 基本格式 (参数列表)->{代码}例一…

mvn help:effective-pom命令的作用

无论 POM 文件中是否显示的声明&#xff0c;所有的 POM 均继承自一个父 POM&#xff0c;这个父 POM 被称为 Super POM。在pom的继承关系中&#xff0c;子pom可以覆盖父pom中的配置&#xff1b;如果子pom没有覆盖&#xff0c;那么父pom中的配置将会被继承。按照这个规则&#xf…

ChatGLM的搭建过程

本次搭建的是清华大学开源的ChatGLM。源码地址。模型地址。 1、开启BBR加速 如何开启BBR加速可以去看我的这篇文章&#xff0c;Linux开启内核BBR加速。 2、拉取ChatGLM源码和ChatGLM模型 点击这里跳转到源码处。 点击这里跳转到模型下载处。 我这里在下载之前创建了一个目…

大厂都用DevOps!十分钟带你了解自动化在DevOps中的运用

Hi&#xff0c;大家好。DevOps、CI/CD、Docker、Kubernetes……好像全世界都在谈论这些技术&#xff0c;以至于你觉得即将到达NoOps阶段。别担心&#xff0c;在工具和各种最佳实践的浩瀚海洋中感到迷失是正常的&#xff0c;是时候让我们来分析一下DevOps到底是什么了。 一、De…

机器学习随记(5)—决策树

手搓决策树&#xff1a;用决策树将其应用于分类蘑菇是可食用还是有毒的任务 温馨提示&#xff1a;下面为不完全代码&#xff0c;只是每个步骤代码的实现&#xff0c;需要完整跑通代码的同学不建议花时间看&#xff1b;适合了解决策树各个流程及代码实现的同学复习使用。 1 数据…

【Redis7】Redis7 持久化(重点:RDB与AOF重写机制)

【大家好&#xff0c;我是爱干饭的猿&#xff0c;本文重点介绍Redis7 持久化&#xff08;重点&#xff1a;RDB与AOF重写机制&#xff09;。 后续会继续分享Redis7和其他重要知识点总结&#xff0c;如果喜欢这篇文章&#xff0c;点个赞&#x1f44d;&#xff0c;关注一下吧】 …