C C++最新今天小胡杨手绘28张图只为教会你单链表_画图学习链表,一文带你搞懂C C++多线程Handler

img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以戳这里获取

此时的plist是为NULL,那我们直接吧plist指向 d 的地址就好呀,并且只有一个 d 刚好d的下一个结点为NULL。****(注意:plist已经由空指针变为了指向新节点 d 了)

看下代码**(很多人都会错误的示范)****:**

void SListPushBack(SLTNode *pphead, SLTDataType x)
{
	SLTNode* newnode = BuySListNode(x);

	if (pphead == NULL)
	{
		pphead = newnode;
	}
	else
	{
		SLTNode* cur = pphead;
		while (cur->next)
		{
			cur = cur->next;
		}
		cur->next = newnode;
	}
}

这样写的话有没有一点点的不妥呢?

我们调试下,发现plist没有变,链表没有插入,接着看图:

我们得出了一个结论:

不是传指针,就可以改变这个指针的指向的,我们应该传二级指针的形式,然后解引用,达到改变plist地址的效果,所以我们链表所有的插入 和 删除函数都传二级指针,传二级指针时,我们使用头结点只要解引用就好了。

所以正确的代码为:

void SListPushBack(SLTNode **pphead, SLTDataType x)
{
	SLTNode* newnode = BuySListNode(x);

	if (*pphead == NULL)
	{
		*pphead = newnode;
	}
	else
	{
		SLTNode* cur = *pphead;
		while (cur->next)
		{
			cur = cur->next;
		}
		cur->next = newnode;
	}
}

其他的都没有改变,只是把用链表头的地方,改为解引用链表头。

链表的头插

头插我们依然要考虑这个问题就是 plist 是否改变呢?

这是肯定的,头插就是换个头呀!就是链表的头被改变了,所以我们要传地址。

我们接着看个图来理解下:

这是刚开始的情况,我们要把 plist 的地址放入新结点,并且把plist改为新结点的地址。

下面我们接着看图示:

我们是没有plist,但是我们有 pphead,直接解引用就是头结点。

需要注意的点:

必须要先进行步骤一,再进行步骤二,否则,我们的头结点就被覆盖了,然后就找不到以后的节点啦,所以我们一定要先执行步骤一,在执行步骤二。

下图是先执行步骤二,在执行步骤一的后果(死循环):

下面是正确的代码:

void SListPushFrond(SLTNode **pphead, SLTDataType x)
{
	SLTNode* newnode = BuySListNode(x);
	newnode->next = *pphead;
	*pphead = newnode;
}

链表的头删:

链表的头删很简单,我们还是传递二级指针,因为如果就一个结点,删除后变为了NULL,此时链表的指向还是发生了变换。

看下面图示:

我们只需要把原来头结点的指向第二个结点,然后把原来头结点free掉就可以啦。

需要注意的是:

我们要先创建一个结点指针,保存第二个结点的地址

然后我们要释放原来的头指针(*pphead)

接着把保存第二个结点的地址赋值给  *pphead,让它成为新的链表的头指针。

如果刚好是一个结点,也是可以的,因为*pphead的下个结点为NULL

我们先把NULL存起来,然后把 *pphead 这个结点 free掉

再把NULL放到*pphead中,就变为NULL了,符合条件。

下面是代码:

void SListPopFrond(SLTNode **pphead)
{
	assert(pphead);
	
	SLTNode* next = (*pphead)->next;
	free(*pphead);
	*pphead = next;
}

链表的尾删

链表的尾删我们要考虑的就要多点,如果有一个结点,和有多个结点分开分析

还是先看图:

如果是这种情况,就直接free掉,然后置为NULL就好了。

这种情况就要把d3这个结点 free掉,然后把 d2 结点的指向NULL就好了。

那我们怎么找到 d2 呢?接着看图:

这就是我们写代码的实际逻辑啦,下面是代码,代码中的注释部分是第二种办法,通过访问两次下一个指针的指向:

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

	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		//SLTNode *cur = *pphead;
		//while (cur->next->next != NULL)
		//{
		//	cur = cur->next;
		//}
		//free(cur->next);
		//cur->next = NULL;
		SLTNode *tail = *pphead;
		SLTNode *tailprev = NULL;
		while (tail->next != NULL)
		{
			tailprev = tail;//失误三
			tail = tail->next;
		}
		free(tail);
		tailprev->next = NULL;
	}
}

查找链表元素:

查找就很简单了,我们直接暴力查找就好啦:

看下代码:

SLTNode* SListFind(SLTNode *pphead, SLTDataType x)
{
	SLTNode* cur = pphead;
	while (cur)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}

	return NULL;
}

需要注意的地方:

首先,我们要先保存头指针,不要改变头指针。

其次,我们要考虑代码的健壮性,并且配合其他的函数接口使用,所以我们返回值设定为结点指针。如果找不到,我们就返回NULL。

修改链表元素

修改就可以配合查找一起使用也很简单,直接看代码吧:

我们可以通过查找到的下标,进行修改,没啥说的。

void SListModify(SLTNode *pos, SLTDataType x)
{
	assert(pos);

	pos->data = x;
}

在链表任意位置后插

在链表任意位置后插入,首先我们应该找该位置,然后进行插入。

先看图:

这个插入位置我们已知,然后我们只需要改变指向就可以啦,接着看图:

我们要注意的是先进行步骤一,在进行步骤二,顺序一定不能反,否则就和头插一样死循环了。

下面是具体的代码:

void SListInsertAfter(SLTNode *pos, SLTDataType x)
{
	assert(pos);

	SLTNode* newnode = BuySListNode(x);//注意下面代码的位置
	newnode->next = pos->next;
	pos->next = newnode;
}

在链表任意位置前插

在链表任意位置后插,这个比较复杂啦。下面看图一点点的分析:

首先如果只有一个结点,那就相当于头插了

和头插一样,不在赘述啦~

如果有多个结点,就复杂啦~  我们先看图吧:

我们怎么知道这个位置前一个的结点值呢?方法其实和尾删一样,使用一个变量遍历,循环结束的条件变为 p1->next != 这个位置 。

看下图:

最后看下代码:
void SListInsertBefore(SLTNode **pphead, SLTNode *pos, SLTDataType x)
{
	assert(pos);

	if (pos == *pphead)
	{
		SListPushFrond(pphead, x);
	}
	else
	{
		SLTNode *prve = *pphead;
		while (prve->next != pos)
		{
			prve = prve->next;
		}
		SLTNode* newnode = BuySListNode(x);
		newnode->next = pos;
		prve->next = newnode;
	}
}

在链表任意位置后删

删除任意位置比较简单,我们只需要注意一点,
就是链表元素为1时,就没有后续啦,就不能删除啦。

还是看下图吧:

只有一个结点,不能删除后面的元素,其他的,直接覆盖,然后free掉pos位置的结点

我们直接看代码吧:

void SListEraseAfter(SLTNode *pos)
{
	assert(pos);
	SLTNode *next = pos->next;
	if (next)
	{
		pos->next = next->next;
		free(next);
		next = NULL;
	}
}

删除链表的任意位置

这个又点麻烦,我们一点点分析:

首先如果这个位置在链表的头上,那么我们就是头删,

还是看图:

如果在其他位置删除,和任意位置前插一样,我们要创建一个指针结点,进行遍历,循环结束的条件是 p1->next != pos,然后把断裂的链表链接就好啦。

接着看图吧:

下面是代码:
void SListErase(SLTNode **pphead, SLTNode *pos)
{
	assert(pphead);
	assert(pos);

	if (*pphead == pos)
	{
		SListPopFrond(pphead);
	}
	else
	{
		SLTNode *prve = *pphead;
		while (prve->next != pos)
		{
			prve = prve->next;
		}
		prve->next = pos->next;
		free(pos);
		pos = NULL;
	}
}

链表的打印

打印可以不用传链表地址,我们不会修改链表内容,还有一点就是创建临时变量来遍历。

看代码吧:

void SListPrint(SLTNode* pphead)
{
	SLTNode* cur = pphead;
	while (cur)
	{
		printf("%d->", cur->data);
		cur = cur->next;
	}
	printf("NULL\n");
}

链表空间释放

这个没说的,还是要传链表的地址,因为我们要把链表置为NULL

直接看代码吧:

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

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

	*pphead = NULL;
}

关于整体的代码头文件篇:

#pragma once

#define  _CRT_SECURE_NO_WARNINGS

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

typedef int SLTDataType;

typedef struct SListNode
{
	SLTDataType data;
	struct SListNode *next;
}SLTNode;

void SListPrint(SLTNode* pphead);//打印

SLTNode* BuySListNode(SLTDataType x);//申请新节点

void SListPushBack(SLTNode **pphead, SLTDataType x);//尾插

void SListPushFrond(SLTNode **pphead, SLTDataType x);//头插

void SListPopFrond(SLTNode **pphead);//头删

void SListPopBack(SLTNode **pphead);//尾删

SLTNode* SListFind(SLTNode *pphead, SLTDataType x);//查找

void SListInsertBefore(SLTNode **pphead, SLTNode *pos, SLTDataType x);//任意位置前插

void SListInsertAfter(SLTNode *pos, SLTDataType x);//任意位置后插

void SListErase(SLTNode **pphead, SLTNode *pos);//删除任意位置

void SListEraseAfter(SLTNode *pos);//任意位置后删

void SListDestroy(SLTNode **pphead);//释放链表

void SListModify(SLTNode *pos, SLTDataType x);//修改链表

关于整体代码源文件:

#include "SList.h"

void SListPrint(SLTNode* pphead)
{
	SLTNode* cur = pphead;
	while (cur)
	{
		printf("%d->", cur->data);
		cur = cur->next;
	}
	printf("NULL\n");
}

SLTNode* BuySListNode(SLTDataType x)
{

img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以戳这里获取

r(SLTNode *pos);//任意位置后删

void SListDestroy(SLTNode **pphead);//释放链表

void SListModify(SLTNode *pos, SLTDataType x);//修改链表



关于整体代码源文件:

#include "SList.h"

void SListPrint(SLTNode* pphead)
{
	SLTNode* cur = pphead;
	while (cur)
	{
		printf("%d->", cur->data);
		cur = cur->next;
	}
	printf("NULL\n");
}

SLTNode* BuySListNode(SLTDataType x)
{

[外链图片转存中…(img-INH3sEAd-1715727468758)]
[外链图片转存中…(img-ufrXpUkU-1715727468758)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以戳这里获取

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值