数据结构基础:一篇文章教你单链表(头插,尾插,查找,头删等的解析和代码)

目录

链表的结构及结构

单链表的头插

单链表的尾插

单链表的头部删除

单链表的尾部删除

单链表的查找

在指定位置之前插入数据

在指定位置之后插入数据

删除pos节点

销毁链表


和我一起学编程呀,大家一起努力!

这篇文章耗时比较久,所以大家多多支持啦


链表的结构及结构

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

 理解:可以把链表理解为火车,每一节火车车厢都有下一节车厢的钥匙

如下图

ee201a0eb16c480599aeda938fa4793c.png

 与顺序表不同的是:链表里面的‘车厢’都是独立申请下来的空间,我们称为节点

3b591deef0a84df2ae585eabc2119f04.png

如上图就可以理解为一个节点

当前节点主要有两个部分组成:要保存的数据和保存下一个节点的地址(指针变量)

为什么需要指针变量保存下一个节点的位置?

因为链表里面的每一个节点都是独立申请的,需要保存下一个节点的位置才能找到下一个节点

假设保存的节点为整型,则代码如图:

struct SListNode
{
int data; //节点数据
struct SListNode* next; //指针变量⽤保存下⼀个节点的地址
};

给定的链表结构中,如何实现链表的从头到尾的打印

void SLTPrint(SLTNode*phead)
{
 SLTNode*pcur=phead;
 while(pcur)
  {
       printf("%d ",pcur->date);
       pcur=pcur->next;
  } 
  printf("\n");
}

ee201a0eb16c480599aeda938fa4793c.png

 解析

1.pcur指针变量保存第一个节点的地址

2.对pcur解引用拿到next指针变量的地址一个节点的地址

3.赋值给pcur,此时的pcur保存的地址就指向了下一个节点

补充:

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

2、节点一般是从堆上申请的

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

 单链表的存储结构

先写出结构体改名比较简单的名字为SLTNode

​​​​typedef int SLTDataType;//便于后期修改
typedef struct SListNode
{
SLTDataType data; //节点数据
struct SListNode* next; //指针保存下⼀个节点的地址
}SLTNode;

单链表的头插

1、断言,pphead不能为空

2、给新节点申请空间

3、让新节点的next指向原本的头

4、新节点设为新的头

void SLTPushFront(SLTNode** pphead, SLTDateType x)
{
	assert(pphead);
	SLTNode* newnode = SLTBuyNode(x);
	newnode->next = *pphead;
	*pphead = newnode;
}

50148876f6a1493eb079942a815aacd2.png

单链表的尾插

1.分为链表为空和不为空

2.链表为空:头节点直接为新的节点,即newnode

3.链表不为空,找到尾节点,尾节点的next指向新节点newnode

注意:这里使用了二级指针,因为你需要传的是地址

//尾插
void SLTPushBack(SLTNode** pphead, SLTDateType x)
{
    assert(pphead);
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	newnode->date = x;
	newnode->next = NULL;
	//链表为空
	if (*pphead == NULL)
	{
		*pphead = newnode;
		return;
	}
	//链表不为空
	SLTNode* ptail = *pphead;
	while (ptail->next)
	{
		ptail = ptail->next;
	}
	ptail->next = newnode;
}
void SListTest02()//尾插
{
	SLTNode* plist = NULL;
	SLTPushBack(&plist, 1);
	SLTPrint(plist);
}

单链表的头部删除

1、pphead,*pphead不能为空

2、保存第二个节点

3、释放旧的头也就是*pphead

4、把保存的第二个节点设为头结点

void SLTPopFront(SLTNode** pphead)
{
	assert(pphead);
	assert(*pphead);
	//第二个节点成为新的头结点,释放旧的头结点
	SLTNode* next = (*pphead)->next;
	free(*pphead);
	*pphead = next;
}

单链表的尾部删除

1、分为两种情况,一个是链表只有一个节点的情况,另一个是不止有一个节点的情况,当然这都是在pphead *phead不为空的情况

2、只有一个节点的情况就是释放(free)

3、不只有一个节点:就是找到尾节点前面一个节点,让这个节点(prev)指向NULL找到尾节点,使用while循坏,把头结点保存在ptail中,当ptail->next为NULL就终止循坏,找到了prev    prev->next指向空指针,然后释放掉之前的尾节点

void SLTPopBack(SLTNode** pphead)
{
	assert(pphead);
	assert(*pphead);
	//一个节点
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
		return;
	}
	SLTNode* ptail = *pphead;
	SLTNode* prev = NULL;
	while (ptail->next)
	{
		prev = ptail;
		ptail = ptail->next;
	}
	prev->next = NULL;
	//销毁
	free(ptail);
	ptail = NULL;
}

单链表的查找

1、断言pphead

2、遍历链表,采用循坏就可以,找到了就返回x

SLTNode* SLTFind(SLTNode** pphead, SLTDateType x)
{
	assert(pphead);
	//遍历链表
	SLTNode* pcur = *pphead;
	while (pcur)
	{
		if (pcur->date == x)
			return x;
		pcur = pcur->next;
	}
	return NULL;
}

在指定位置之前插入数据

1、pphead *pphead pos不能为空

2、分为两种情况,pos是头结点,采用头插

3、pos不是头结点,采用while循坏,找到pos前面的一个节点(prev),prev->next指向新节点,新节点->next指向pos

void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDateType x)
{
	assert(pphead);
	assert(pos);
	assert(*pphead);

	//Pos是头结点
	if (pos == *pphead)
	{
		SLTPushFront(pphead, x);
		return;
	}
	//不是头结点情况
	SLTNode* newnode = SLTBuyNode(x);
	SLTNode* prev = *pphead;
	
	while (prev->next != pos)
	{
		prev = prev->next;
	}
	prev->next = newnode;
	newnode->next = pos;
}

在指定位置之后插入数据

1、直接申请新节点空间

2、新节点的->next指向pos的后面一个节点

3、pos—>next指向新节点

void SLTInsertAfter(SLTNode* pos, SLTDateType x)
{
	assert(pos);
	SLTNode* newnode = SLTBuyNode(x);
	newnode->next = pos->next;
	pos->next = newnode;
}

3e6c113a20f24096a31f80644f359a9e.png

删除pos节点

1、pos是头结点,采用头删

2、pos不是头节点,找到pos节点的前面一个节点(prev),让prev->next指向pos->next,然后释放pos节点

void SLTErase(SLTNode** pphead, SLTNode*pos)
{
	assert(pphead);
	assert(*pphead);
	assert(pos);

	//posg刚好是头结点
	if (*pphead == pos)
	{
		SLTPopFront(pphead);
		return;
	}
	SLTNode* prev = *pphead;
	while (prev->next != pos)
	{
		prev = prev->next;
	}
	prev->next = pos->next;
	free(pos);
	pos = NULL;
}

销毁链表

采用循坏,依次把每一个节点释放掉

void SListDesTroy(SLTNode** pphead)
{
	assert(pphead);
	assert(*pphead);;

	SLTNode* pcur = *pphead;
	while (pcur)
	{
		SLTNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	*pphead = NULL;
}


希望大家多多支持!谢谢大家可以阅读到这里^  ^

  • 42
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 15
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

颦颦Admin@123

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值