【数据结构】单链表的概念与实现

目录

前言

链表的概念与理解

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

在这里插入图片描述

此图为单向链表的物理图(箭头便于理解,实际上并不存在直接链接)
  1. 链式结构在逻辑上是连续的,但是在物理上不一定连续
  2. 现实中的结点一般都是从堆上申请出来的
  3. 从堆上申请的空间,是按照一定的策略来分配的,两次申请的空间可能连续,也可能不连续

链表的分类

  1. 单向或者双向
  2. 带头或不带头
  3. 循环或非循环

最常用的链表有两种:

  • 无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。

在这里插入图片描述

  • 带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。

在这里插入图片描述

我们要实现的就是无头单向非循环链表

单链表的实现

头文件

  • 头文件我们进行头文件的引用以及类型定义函数声明
#pragma once

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

//把类型命名为SLNDateType,当改变链表数据类型时改变此句int即可
typedef int SLNDateType;

//结构体 - 节点
typedef struct SListNode
{
	SLNDateType data;
	struct SListNode* next;
}SLN;//将结构体名为SLN方便使用

//打印
void SListPrint(SLN* phead);
//创建节点
SLN* CreatListNode(SLNDateType x);
//尾插
void SListPushBack(SLN** pphead, SLNDateType x);
//头插
void SListPushFront(SLN** pphead, SLNDateType x);
//尾插
void SListPopBack(SLN** pphead);
//头删
void SListPopFront(SLN** pphead);
//查找
SLN* SListFind(SLN* phead, SLNDateType x);
//特定下标插入(向前插入)
void SListInsert(SLN** phead, SLN* pos, SLNDateType x);
//特定下标插入(向后插入)
void SListInsertAfter(SLN** phead, SLN* pos, SLNDateType x);
//特定节点删除
void SListErase(SLN** phead, SLN* pos);
//循环
void SListDestory(SLN* phead);

链表打印

  • 我们从首位开始读取,将phead地址给cur,判断cur若不为空则没有到达尾部,继续打印,把cur置为cur->next
void SListPrint(SLN* phead)
{
	SLN* cur = phead;
	while (cur != NULL)
	{
		printf("%d->", cur->data);
		cur = cur->next;
	}
	printf("NULL\n");//结尾+换行
}

开辟空间/创建节点

  • 在进行插入相关的函数时需要先创建一个节点(开辟一个空间)
SLN* CreatListNode(SLNDateType x)
{
	SLN* newnode = (SLN*)malloc(sizeof(SLN));
	if (newnode == NULL)//判断malloc是否申请成功
	{
		perror("malloc fail");//报错
		exit(-1);
	}
	newnode->data = x;//开辟的空间数据的值置为x
	newnode->next = NULL;//开辟空间的next置空,根据需要改变

	return newnode;
}

尾插

  • 需要注意的是因为我们传过来的值是指针,而尾插需要改变改变传过来的值(plist),形参使用二级指针。

在这里插入图片描述

void SListPushBack(SLN** pphead, SLNDateType x)
{
	SLN* newnode = CreatListNode(x); //先开辟一个空间
	if (*pphead == NULL)//如果链表为空
	{
		*pphead = newnode;//直接把头放到开辟出的空间
	}
	else
	{
		//找到尾节点
		SLN* tail = *pphead;
		//找尾部
		while (tail->next != NULL)
		{
			tail = tail->next;
		}
		tail->next = newnode;//把newnode变为尾部
	}
}

头插

  • 开辟空间后其next=*pphead变成首,再将*pphead重新放回首位
  • 头插不必考虑链表此时是否只有一个节点
void SListPushFront(SLN** pphead, SLNDateType x)
{
	SLN* newnode = CreatListNode(x);//开辟空间
	newnode->next = *pphead;
	*pphead = newnode;
}

尾删

  • 尾删需要考虑只有一个节点两个或以上节点的情况
  • 两个及以上节点时可以采用两种方法

法一
在这里插入图片描述
法二

在这里插入图片描述

void SListPopBack(SLN** pphead)
{
	//用assert或if语句均可
	assert(*pphead != NULL);
	/*if (*pphead == NULL)
		return;*/	

	if ((*pphead)->next == NULL)//一个节点
	{
		free(*pphead);
		*pphead = NULL;
	}
	else//两个及以上节点
	{
		//法一
		SLN* prev = NULL;
		SLN* tail = *pphead;
		while (tail->next != NULL)//先找尾
		{
			prev = tail;
			tail = tail->next;
		}
		free(tail);
		tail = NULL;
		prev->next = NULL;
		
		//法二
		/*SLN* tail = *pphead;
		while (tail->next->next)
		{
			tail = tail->next;
		}
		free(tail->next);
		tail->next = NULL;*/
	}
}

while(tail->next != NULL)while(tail->next)同等效应,意义不完全一样

头删

  • 前面的理解后相信接下来的就没有过于难理解了‘’
  • 头删先把原本第一个节点的next给到新创建的next中,然后释放掉第一个节点,最后把首位的指针指向此时的next(原本第二个节点)
  • 过程中next作临时储存
void SListPopFront(SLN** pphead)
{
	assert(*pphead != NULL);//断言链表不能为空
	SLN* next = (*pphead)->next;
	free(*pphead);
	*pphead = next;
}

查找

  • 对于需要对传入的参数进行改变的使用二级指针,而查找不对形参改变,使用一级指针即可
  • 我们使用循环,在cur逐渐向下一位移动的过程中如果找到即返回该类值。找不到返回空
SLN* SListFind(SLN* phead, SLNDateType x)
{
	SLN* cur = phead;
	while (cur)
	{
		if (cur->data == x)
		{
			return cur;
		}
		else
		{
			cur = cur->next;//走到下一位
		}
	}
	//找不到返回空
	return NULL;
}

任意位置插入(前插和后插)

插入到该位置前面
  • 插入需要考虑只有一个节点两个或以上节点的情况

在这里插入图片描述

void SListInsert(SLN** pphead, SLN* pos, SLNDateType x)
{
	//最好加上断言
	assert(pos);
	
	SLN* newnode = CreatListNode(x);
	//判断是否只有一个节点
	if (*pphead == pos)
	{
		//(可直接调用头插函数)
		newnode->next = *pphead;
		*pphead = newnode;
	}
	else
	{
		//找到pos前一位
		SLN* posPrev = *pphead;
		while (posPrev->next != pos)
		{
			posPrev = posPrev->next;
		}
		//改变链接
		posPrev->next = newnode;
		newnode->next = pos;
	}

}
插入到该位置后面
  • 单链表更倾向于使用插入位置后面,效率更高
  • 创建新节点,让其指向该位置的下一位,再将该位置指向创建的新节点
void SListInsertAfter(SLN* pos, SLNDateType x)
{
	assert(pos);
	SLN* newnode = CreatListNode(x);
	newnode->next = pos->next;
	pos->next = newnode;
}

删除任意位置(该位置或后删)

删除该位置
  • 首位和剩下的其余位置分两种情况讨论
void SListErase(SLN** pphead, SLN* pos)
{
	//断言不能为空
	assert(*pphead);
	assert(pos);
	//头位
	if (*pphead == pos)
	{
		*pphead = pos->next;
		free(pos);
		//或调用头删函数
		//SListPopFront(pphead);
	}
	else//非头位
	{
		SLN* prev = *pphead;
		while (prev->next != pos)//向后走到pos位的前一位
		{
			prev = prev->next;
		}
		prev->next = pos->next;
		free(pos);
		pos = NULL;//非必须,可加可不加
		//基于编程习惯,通常会置空
	}
}
删除该位置后面的
  • 此函数与尾删的法二比较类似
void SListEraseAfter(SLN* pos)
{
	//该位置后面不能为空
	assert(pos->next);
	SLN* next = pos->next;
	pos->next = next->next;
	free(next);
	//next = NULL;//非必要
	//出了函数,next也就销毁了
}

销毁/释放链表

  • 链表不同于顺序表,需要逐步释放完所有节点
  • 我们使next存储cur的下一位,释放cur后再给cur,实现逐步向后释放
  • 最后把plist(*pphead)置空
void SListDestory(SLN** pphead)
{
	SLN* cur = *pphead;

	while (cur)
	{
		SLN* next = cur->next;//临时存储cur的下一位
		free(cur);
		cur = next;
	}
	*pphead = NULL;
}

完整代码及测试用例

卜及中 -
单链表完整代码

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
【优质项目推荐】 1、项目代码均经过严格本地测试,运行OK,确保功能稳定后才上传平台。可放心下载并立即投入使用,若遇到任何使用问题,随时欢迎私信反馈与沟通,博主会第一时间回复。 2、项目适用于计算机相关专业(如计科、信息安全、数据科学、人工智能、通信、物联网、自动化、电子信息等)的在校学生、专业教师,或企业员工,小白入门等都适用。 3、该项目不仅具有很高的学习借鉴价值,对于初学者来说,也是入门进阶的绝佳选择;当然也可以直接用于 毕设、课设、期末大作业或项目初期立项演示等。 3、开放创新:如果您有一定基础,且热爱探索钻研,可以在此代码基础上二次开发,进行修改、扩展,创造出属于自己的独特应用。 欢迎下载使用优质资源!欢迎借鉴使用,并欢迎学习交流,共同探索编程的无穷魅力! 基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip 基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip 基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值