单链表的含义与基本功能代码实现

1. 单链表的含义

 1.1 含义

单链表是一种常见的数据结构,它由一系列节点组成每个节点包含一个数据元素和一个指向下一个节点的指针。单链表中只能从头部开始遍历,每个节点只能访问下一个节点,而不能访问前一个节点。

2.0 单链表的代码讲解

 2.1 节点

2.1.0 定义单链表的节点结构

根据单链表的组成设置结构体成员

1.type类型的变量

2.指向下一个节点的指针

  2.1.1 创建节点的函数

节点的创建分三步

1.使用malloc、calloc去开辟动态内存空间

2.判断是否开辟成功

3.成功:赋值         失败:报错退出

  2.1.2 展示节点的函数

 展示节点:遍历各个节点,并输出type类型的数据

!展示的时候利用的指针一定不能是Phead,否则会丢失整个单链表(要创建一个新的变量)

 2.2 头尾的插入删除数据

  2.2.0 尾插数据  

因为涉及动态内存开辟,所以外围的大框架就有判断生成是否成功这条语句,成功就接着进行下一步,失败则直接报错退出。

然后核心步骤就是找尾:

因为涉及访问节点的成员,所以要把链表分为空链表和非空链表。

空链表情况可以直接把头指针指向新节点

非空链表就使用while循环找到尾结点,然后让尾结点指向新节点

  2.2.1 头插数据

这里不涉及头结点的成员访问,所以不一定要分空链表还是非空链表,我们可以写完非空链表之后看看是否可以兼容空链表,也就是说先完成普遍情况,再考虑特殊情况

普遍情况:这里只需要先让new节点指向原来的头结点,然后再把头指针指向新节点即可

特殊情况:当*phead为空时,则新节点下一个指向空,头节点指针也给到新节点

代入特殊情况进代码后,我们发现确实可以兼容

  2.2.2 尾删数据

节点删除的前提是链表不为空,所以在开头要有关于*pphead的断言

若删除后为空链表,我们直接释放掉节点之后,需要把头结点也改为空

若删除后不为空链表,我们就利用快慢指针找到尾和尾前的节点,然后让尾前的节点指向空。

  2.2.3 头删数据

头删数据就只需要把头结点指向的节点释放即可,但是为了不丢失整条链表,我们需要在删除节点之前把新的头结点地址给保存下来,然后将他的地址给到头结点指针

 2.3 查找数据

查找数据与展示节点方法类似:

需要遍历链表,如果找到对应数据就返回当前地址,最后都没找到就返回NULL

 2.4 指定位置前/后插入删除数据

  2.4.0 指定位置前插入数据

这里可以分两种情况

第一种:改变头结点指针,也就是pos指向的是头结点

第二种:不改变头节点,pos指向的不是phead

第一种情况我们可以知道他相当于头插一个数据,所以直接调用前面的头插函数即可

第二种情况我们需要找到pos前一个节点的地址,然后让prev->new_node,new_node->pos。

  2.4.1 指定位置后插入数据

这里我们不涉及改变头结点的指针,所以不用分开两种情况思考

我们需要的指针是pos->next和pos,为了不用多创建一个指针去保存pos->next,我们先让new_node指向pos->next,再让pos指向new_node。

  2.4.2 删除pos节点

这里同样涉及头结点的改变

情况1:删除头结点

情况2:删除非头节点

1.相当于头删,直接引用头删函数

2.需要找到pos前的节点,利用while循环完成。然后让prev指向pos->next,完成新链表的链接,随后再释放pos处的动态内存空间

  2.4.3 删除pos后的节点

pos后的节点绝对不可能是头结点,所以也不需要分类讨论

这里为了防止出现较为复杂的形式(pos->next->next),所以创建了一个新的指针pdel指向pos的下一个节点,让pdel->next代替pos->next->next

同理,先链接链表,然后销毁pos节点。

 2.5 销毁数据

 销毁链表:遍历链表,释放内存

!!!由于释放内存会让pcur指针变为野指针,如果不提前保存下一个节点的地址,链表会丢失(就是无法通过pcur找到下一个节点)

3. 完整代码分享

 slist.h

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#define type int
//定义单链表的节点结构
typedef struct slistnode
{
	type x;//单链表里面装的数据
	struct slistnode *next;//指向下一个节点的地址(此时还没完成重命名)
}slnode;
//创建节点的函数
slnode * BuyNode(type a);
//展示节点的函数
void SLTPrint(slnode * phead);
//尾插数据
void SLTPushBack(slnode** pphead, type a);
//??phead在哪里被初始化置为空了
//头插数据
void SLTPushFront(slnode** pphead,type a);
//尾删数据
void SLTPopBack(slnode** pphead);
//头删数据
void SLTPopFront(slnode** pphead);
//查找数据
slnode* SLTFind(slnode* phead, type a);
//指定位置前插入数据
void SLTInsert(slnode** pphead, slnode* pos, type a);
//在指定位置之后插入数据
void SLTInsertAfter(slnode* pos, type a);
//删除pos节点
void SLTErase(slnode** pphead, slnode* pos);
//删除pos之后的节点
void SLTEraseAfter(slnode* pos);
//销毁链表
void SListDesTroy(slnode** pphead);

slist.c

#include"slist.h"
slnode * BuyNode(type a)//节点创建
{
	slnode *new_node =(slnode *) malloc(sizeof(slnode));
	if (new_node)//进行创建是否成功的判断
	{
		new_node->x = a;
		new_node->next= NULL;
	}
	else
	{
		perror("malloc");
		exit(1);//不用return是因为return返回的是该函数外部,且返回值类型也不太对
	}
	return new_node;
}
void SLTPrint(slnode * phead)//节点展示(这里的头结点准确来说是有效节点第一位,而不是真正的头节点,因为头结点是哨兵位
{
	slnode*pcur = phead;//防止phead被改变
	while (pcur)
	{
		printf("%d->",pcur->x);
		pcur = pcur->next;
	}
	printf("NULL");
}
//尾插
void SLTPushBack(slnode** pphead, type a)//分为空链表和非空链表
{
	assert(pphead);
	slnode* new_node =(slnode*) BuyNode(a);
	if (new_node)
	{//开辟成功
		if (*pphead == NULL)//讲解指向phead的指针和phead指针和链表节点关系
		{
			//空链表
			*pphead = new_node;
		}
		else
		{
			//非空链表
			slnode*ptail = *pphead;
			while (ptail->next != NULL)
			{
				ptail = ptail->next;
			}
			ptail->next = new_node;
		}
	}
	else//开辟失败
	{
		perror("malloc");
		exit (1);
	}
	
}
void SLTPushFront(slnode** pphead,type a)//头插
{
	assert(pphead);
	slnode* new_node = (slnode*)malloc(sizeof(slnode));
	if (new_node)
	{
		new_node->next = *pphead;
		*pphead = new_node;
	}
	else
	{
		perror("malloc");
		exit(1);
	}
}
void SLTPopBack(slnode** pphead)//尾删
{
	assert(pphead&&*pphead);//确保不是空链表删除
	//分为单节点和多节点(区分是否要改变头节点)
	if ((*pphead)->next == NULL)//->的优先级高于*
	{
		free(*pphead);
		*pphead = NULL;//改变头结点
	}
	else
	{
		slnode*prev = *pphead;
		slnode*ptail = *pphead;
		while (ptail->next)//找到尾和尾前一位
		{
			prev = ptail;
			ptail = ptail->next;
		}
		prev->next = NULL;//断掉链条
		free(ptail);
		ptail = NULL;
	}
	
}
void SLTPopFront(slnode** pphead)//头删
{
	assert(*pphead && pphead);
	slnode*pnext = (*pphead)->next;//以免丢失下一节点的数据
	free(*pphead);
	*pphead = pnext;

}
slnode* SLTFind(slnode* phead, type a)//查找数据
{
	slnode*pcur = phead;
	while (pcur)
	{
		if (pcur->x == a)
		{
			return pcur;
		}
		pcur = pcur->next;
	}
	return NULL;
}
void SLTInsert(slnode** pphead, slnode* pos, type a)//指定位置前插入数据
{
	assert(pphead && *pphead);
	assert(pos);//不能在NULL前插入数据
	if ((*pphead)->next == NULL)//单节点
	{
		SLTPushFront( pphead, a);//相当于头插
	}
	else//多节点
	{
		slnode*new_node = (slnode*)BuyNode(a);
		slnode*prev = *pphead;//为了拿到pos前的地址
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		prev->next = new_node;
		new_node->next = pos;
	}
}
//在指定位置之后插入数据
void SLTInsertAfter(slnode* pos, type a)
{
	assert(pos);
	slnode*new_node=(slnode*)BuyNode(a);
	new_node->next = pos->next;
	pos->next = new_node;
}
//删除pos节点
void SLTErase(slnode** pphead, slnode* pos)
{
	assert(pos);
	assert(*pphead && pphead);
	if (pos == *pphead)//pos是头节点
	{
		SLTPopFront(* pphead);
	}
	else//非头节点
	{
		slnode*prev = *pphead;
		while (prev->next != pos)//找到删除节点前一个数据地址
		{
			prev = prev->next;
		}
		prev->next = pos->next;
		free(pos);
		pos = NULL;
	}
}
//删除pos后的节点
void SLTEraseAfter(slnode* pos)
{
	assert(pos && pos->next);//不能删除空节点
	slnode*pdel = pos->next;
	pos->next = pdel->next;
	free(pdel);
	pdel = NULL;

}
//销毁链表
void SListDesTroy(slnode** pphead)
{
	assert(pphead);
	slnode*pcur = *pphead;
	slnode*pnext = *pphead;
	while (pcur)
	{
		pnext = pcur->next;//保存下一个节点的地址
		free(pcur);
		pcur = pnext;
	}
	*pphead = NULL;
}

  • 13
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值