单链表————单链表的构建,增删查改功能的实现

目录

1.什么是单链表

2.单链表的结构

3.开辟一个新的结点

4.在链表的尾部插入一个节点数据

图片示例:

代码示例:

 5.删除链表尾部的结点 

图片示例:

 代码示例:

 调用:

执行效果:

​6.查找一个结点,以及改动一个结点

 代码示例:

代码执行效果:

7. 在pos之后增加一个结点

函数调用: 

代码执行效果:

 8.删除pos后面的一个结点

 代码示例

函数调用代码:

代码执行效果:

9.单链表的优缺点

单链表的优点:

 单链表的缺点:


1.什么是单链表

    单链表也是一种存储数据的结构之一,他和我们上一节讲到的顺序表有很大的去区别,上一节讲到的顺序表是由一个结构体做的框架,结构体里面有malloc开出的空间来存储数据,而今天的单链表是由数个结点构成的,每一个结点又都是一个小的结构体,因为每一个结构体里不仅有存储数据的空间,还有一个指针来存储下一个结点的地址,用来指向下一个结点。这样我们就可以发现单链表是由数个结点串起来。基于这样的结构,单链表是在内存空间里面是不连续的,但是在逻辑上是连续的。这也是单链表和顺序表最大的区别。后面我们将基于这样的结构特点,实现单链表的各种功能。

2.单链表的结构

单链表又一个个结点构成,结点又是一个结构体。结构体里面有两个结构体成员,第一个是一个存储数据的变量(data),第二个是一个指向下一个结构的结构体类型的指针(next),但是单链表的最后一个结点的next指向NULL,代表链表结束。代码示例:

typedef struct SlistNode
{
	SListdataType data;
	struct SlistNode* next;
}SlistNode;

这里使用typedef 是struct SlistNode 重命名为 SlistNode。

3.开辟一个新的结点

当我们有一个数据需要一个结点存储的时候,就需要开辟一个结点,结点也就是一个结构体,我们可以使用malloc开辟一个struct SlistNode结构体大小的空间作为一个结点,并将数据存入结点的data里面,因为新开辟的结点自然而然也就是单链表的最后一个结点,所以新开辟的结点的next需要指向空。代码实例:

SlistNode* BuySListNode(SListdataType x)//开辟结点;x 代表存储的数据;
{
	//增加结点的时候,结点是一个结构体,里面有一个结构体类型的指针,和一个数据存储变量,
	//所以申请空间的时候要申请一个节点的大小;
	SlistNode* newNode = (SlistNode*)malloc(sizeof(SlistNode));
	if (newNode == NULL)//判断是否开辟成功;
	{
		printf("开辟失败!");
		exit(-1);
	}
	newNode->data = x;//结点开辟成功后,将待存数据 x,存入data;
	newNode->next = NULL;//新增加的结点也是最后一个结点,需要将节点里面的结构体指针置空;
	return newNode;
}

 这里如果开辟失败,后面也就没有必要对结构体操作的必要,直接exit(-1)暴力终止,最后我们返回新开辟节点的地址,因为需要将新开辟的节点的接到上一个结点的后面。

4.在链表的尾部插入一个节点数据

在链表的尾部插入一个节点数据,可以分为两步:(1)生成一个结点,并且将数据存入节点的data里面,将节点的next置空,因为新生成的结点也就是最后一个结点(2)将前一个结点里面的结构体指针指向新生成的结点的地址。这样就保持了我们单链表的逻辑上的连续性。

图片示例:

代码示例:

void SListpushback(SlistNode** pphead, SListdataType x)//尾插结点数据;
{
	SlistNode* newNode = BuySListNode(x);
	if (*pphead == NULL)//如果,*pphead为空,就意味着链表第一个结点就是空;直接增加一个结点
	{
		*pphead = BuySListNode(x);//将新增的节点的地址赋值给*pphead;
	}
	else//如果第一个结点不是空
	{
		SlistNode* tail = *pphead;
		while (tail->next != NULL)//找到最后一个结点;
		{
			tail = tail->next;
		}
		tail->next = newNode;//找到最后一个结点之后,将最后一个结点里面的结构体指针,赋值为新增加的节点的地址;
	}
}
int main()
{
	SlistNode* plist = NULL;
	SListpushback(&plist, 100);
	SListpushback(&plist, 200);
	SListpushback(&plist, 300);
	SListpushback(&plist, 400);
	SListpushback(&plist, 500);
	SListpushback(&plist, 600);
	SListpushback(&plist, 700);
    return 0;
}

 这里调用的时候使穿过的时plist的地址,plist是一个结构体的指针,如果我们只是传的时plist,也就是简单的传值,虽然pliat本身是个地址,但是传递过去的是plist,plist最为一个值传递过去了,传值的调用是无法对链表发生变化的,所以这里是使用了&plist,作为函数的形参,就要用二级指针,来接收实参。也是为数不多的使用到二级指针的地方。

调用BuySListNode(x)函数返回的是新生成的节点的地址。如果,*pphead为空,就意味着链表第一个结点就是空;直接增加一个结点,将新增的节点的地址赋值给*pphead;
    单链表打印函数:单链表的遍历,只要循环的打印data的值,并且将结点往后移一下,看代码示例:

void SlistPrintf(SlistNode* phead)//打印函数
{
	SlistNode* cur = phead;//为了不在原链表上操作;我们重新用一个链表的头来操作;
	while (cur != NULL)//当cur==NULL时,链表结束。
	{
		printf("%d ", cur->data);
		cur = cur->next;//结点往后移;
	}
}

 我们借助,打印函数,来看一下代码的效果,

 5.删除链表尾部的结点 

删除链表尾部的一个结点,其实很简单,我们只要找到最后一个结点,并且把它用free(),释放掉,再把倒数第二个结点的next置空。因为最后一个结点没有了以后,倒数第一个结点就是最后一个结点了。 但是有一个点需要注意,就是当我们找到最后一个结点的时候,就回不到倒数第二个结点里面的next,也就没办法给他置空了,所以为了能够在找到倒数第一个结点的时候还能找到倒数第二个,我们可以定义一前一后两个指针,后面的指针指到倒数第一个结点的时候,前面的指针就可以指向倒数第二个指针了。

图片示例:

 代码示例:

void SListpophback(SlistNode** pphead)//尾删结点;
{
	if (*pphead == NULL)//说明一个结点都没有,无需操作;
	{
		return;
	}
	else if ((*pphead)->next == NULL)//就一个结点,直接把结点的空间地址置空;
	{
		free(*pphead);
		*pphead = NULL;
	}
	else//有多个结点;
	{
		SlistNode* prev = NULL;
		SlistNode* tail = *pphead;
		while (tail->next != NULL)//先找到最后一个结点; 
		{
			prev = tail;
			tail = tail->next;
		}
		free(tail);
		prev->next = NULL;//将最后一个结点的地址置空;

	}
}

如果链表中就一个结点,就不必考虑这么多了,直接 将这个结点free就可以了;

 调用:

int main()
{
	SlistNode* plist = NULL;
	SListpushback(&plist, 100);
	SListpushback(&plist, 200);
	SListpushback(&plist, 300);
	SListpushback(&plist, 400);
	SListpushback(&plist, 500);
	SListpushback(&plist, 600);
	SListpushback(&plist, 700);
	printf("尾删前:");
	SlistPrintf(plist);
	printf("\n");
	printf("尾删后:");
	SListpophback(&plist);
    SlistPrintf(plist);
    return 0;
}

执行效果:

6.查找一个结点,以及改动一个结点

我们查找一个数据X,所在的结点,找到之后就直接返回结点的地址,如果没有就返回NULL;查询的方式很简单,接直接遍历链表,遍历的同时进行判断。

 代码示例:

SlistNode* FindList(SlistNode* phead, SListdataType x)//查找一个结点;
{
	SlistNode* cur = phead;
	while (cur != NULL)
	{
		if ((cur->data) == x)//如果相等,就找到了,返回cur;
		{
			return cur;
		}
		else
		{
			cur = cur->next;//不相等就往后走,判断下一个;
		}
	}
	return NULL;//走到这里,链表都遍历完了,就是没找到,返回NULL;
}

 这里代码方便演示,我们找到那个数据的结点地址,并且把它改动一下,调用的主函数:

int main()
{
	SlistNode* plist = NULL;
	SListpushback(&plist, 100);
	SListpushback(&plist, 200);
	SListpushback(&plist, 300);
	SListpushback(&plist, 400);
	SListpushback(&plist, 500);
	SListpushback(&plist, 600);
	SListpushback(&plist, 700);
	printf("改动前:");
	SlistPrintf(plist);
	printf("\n");
	SlistNode* pos= FindList(plist, 300);//获得数据所在结点的地址;
	if (pos != NULL)//找到数据的结点并且改动数据内容;
	{
		pos->data = 3000;
	}
	printf("改动后:");
	SlistPrintf(plist);
	return 0;
}

代码执行效果:

7. 在pos之后增加一个结点

在pos位之后,增加一个结点,首先我们结合查询函数,获得你想插入链表中的一个中间位置的地址,这个位置地址就是pos,我们申请一个新的结点(newNode),先将pos后一个结点的地址(pos->next)给新结点的next,再将新结点的地址给pos->next。这样新增加的结点就和我们的链表连接起来了。这里的先后顺序是不能变的,因为如果pos->next=newNode;后面也就找不到pos后面的一个结点。

 

void SlistInserAfter(SlistNode** pos, SListdataType x)//在pos之后增加一个结点;
{
	assert(*pos);
	SlistNode* newnode = BuySListNode(x);
	newnode->next = (*pos)->next;
	(* pos)->next = newnode;

}

函数调用: 

int main()
{
	SlistNode* plist = NULL;
	SListpushback(&plist, 100);
	SListpushback(&plist, 200);
	SListpushback(&plist, 300);
	SListpushback(&plist, 400);
	SListpushback(&plist, 500);
	SListpushback(&plist, 600);
	SListpushback(&plist, 700);
	printf("插入前:");
	SlistPrintf(plist);
	printf("\n");
	SlistNode* pos= FindList(plist, 300);
	SlistInserAfter(&pos, 350);
	printf("插入后:");
	SlistPrintf(plist);
	return 0;
}

我们利用查询函数查询了300的结点位置地址,300的后面增加一个存入350的结点; 

代码执行效果:

 8.删除pos后面的一个结点

首先先保存一下,被删除结点的地址,在将pos的next指向被删除后面的结点的地址,最后释放掉被删除结点的空间,并将指针置空。

 

 代码示例

void SlistEraseAfter(SlistNode** pos)//删除pos之后的一个结点;
{
	assert(*pos);
	SlistNode*Next=(*pos)->next;
	(*pos)->next = Next->next;
	free(Next);
	Next = NULL;
}

函数调用代码:

int main()
{
	SlistNode* plist = NULL;
	SListpushback(&plist, 100);
	SListpushback(&plist, 200);
	SListpushback(&plist, 300);
	SListpushback(&plist, 400);
	SListpushback(&plist, 500);
	SListpushback(&plist, 600);
	SListpushback(&plist, 700);
	printf("插入前:");
	SlistPrintf(plist);
	printf("\n");
	SlistNode* pos= FindList(plist, 300);
	SlistInserAfter(&pos, 350);
	printf("插入后:");
	SlistPrintf(plist);
	SlistEraseAfter(&pos);
	printf("\n");
	printf("删除后:");
	SlistPrintf(plist);
	return 0;
}

代码执行效果:

 

9.单链表的优缺点

单链表的优点:

1.链表是一个动态数据结构,因此它可以在运行时通过分配和取消分配内存来增长和收缩。 因此,无需给出链表的初始大小。

2.节点的插入和删除确实非常容易。 与数组不同,在插入或删除元素后,我们不必移动元素。 在链表中,我们只需要更新节点的下一个指针中存在的地址即可。

3.由于链表的大小可以在运行时增加或减小,因此不会浪费内存。 在数组的情况下,会浪费大量内存,例如,如果我们声明一个大小为10的数组并在其中仅存储6个元素,那么就会浪费4个元素的空间。 链表中没有这种问题,因为仅在需要时才分配内存。 

 单链表的缺点:

1.与数组相比,在链表中存储元素需要更多的内存。 因为在链表中,每个节点都包含一个指针,并且它本身需要额外的内存。

2.在链表中很难遍历元素或节点。 我们不能像按下标在数组中那样随机访问任何元素。 例如,如果我们要访问位置n处的节点,则必须遍历它之前的所有节点。 因此,访问节点所需的时间很大。

3.在链表中,反向遍历确实很困难。 在情况下,它双链表比较容易,但是向后指针需要额外的内存,因此浪费了内存。 

 

 

 

  • 14
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 6
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

我的代码爱吃辣

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

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

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

打赏作者

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

抵扣说明:

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

余额充值