单链表的实现

文章目录

之前我们实现了顺序表,顺序表是在逻辑上连续物理上也连续,而链表是一种在逻辑上连续但是在物理上并不连续的数据结构。


一、链表是什么?

链表是在逻辑上连续的数据结构,具体如图所示,在使用链表时是使用指针用来指向其下一个地址的位置,而地址并不是连续的,只是我们在逻辑想着为连续的,因此并不用向顺序表一样开辟多个空间,减少了相应的空间浪费。

二、使用步骤

1.创建链表结构体

链表也是有着多组数据,因此我们仍然使用结构体用来存放数据,我们说到了链表是指向下一个的指针,所以我们在结构体中存放下一个结构体的结构体指针。(注:因为代码运行从上而下,所以结构体内的typedef在其本身中无法生效,所以指针老实打完全部)

代码如下(示例): 

typedef int typedata;
typedef struct Linklist {
	struct Linklist* next;
	typedata data;
}Slist;

2.链表的头插

既然说到插入了那一定要开辟空间,因为插入都要开辟空间所以我们直接将其封装为一个函数。链表的头插比较简单,直接将创建好的下一个直接指向链表,如下图一样,直接将开辟的空间的下一个指向原来链表的头指针,就实现了头插。

相信也有人发现了,我们传过来的是二级指针,那为什么传的为二级指针而不是一级指针呢,我们要找到下一个空间的目标是要靠指针找的,所以我们创建数据一定是一个结构体指针类型的数据,如果用一级指针我们无法对其进行改变,我们要做到对指针进行改变,所以我们传二级指针,使其可以改变一级指针。

代码如下(示例):

Slist* capicity(typedata data)
{
	Slist* newnode = (Slist*)malloc(sizeof(Slist));
	if (newnode == NULL)
	{
		perror("malloc fail");
		return NULL;
	}
	newnode->data = data;
	newnode->next = NULL;
	return newnode;
}
void Pushhead(Slist** list, typedata data)
{
	assert(list);
	Slist* newnode = capicity(data);
	newnode->next = *list;
	*list = newnode;
}

3.链表的头删

既然说到删除,那一定要检查是否还有数据可以被删除,头删我们需要记住头节点的下一个,将原来的头节点free释放,将原来头节点的下一个赋给头节点,要记得保存一下头节点的下一个再进行释放,因为如果先进行释放的话会导致找不到头节点的下一个。

代码如下(示例): 

void Pophead(Slist** list)
{
	assert(list);
	Slist* newnode = *list;
	*list = newnode->next;
	free(newnode);
	newnode = NULL;
}

 4.链表的尾插

链表的尾插我们需要分情况查看,

如果头节点为空,说明一个数据也没有,直接将新开辟的空间付给头节点,如果头节点不为空,就需要通过循环找到链表的尾节点,链表的尾节点的下一个为NULL可以根据这个特性通过循环遍历找到,找到后将尾节点的next赋值为新开辟的空间。

代码如下(示例): 

void Pushtail(Slist** list, typedata data)
{
	Slist* newnode = capicity(data);
	Slist* phead = *list;
	if (phead == NULL)
	{
		*list = newnode;
	}
	else
	{
		while (phead->next)
		{
			phead = phead->next;
		}
		phead->next = newnode;
	}
}

 4.链表的尾删

同样的删除要检查是否有数据可删,我们如果要删除尾节点的话,我们也要分情况讨论,如果头节点下一个就为空的话那么说明链表只有一个节点,直接将其释放置空,如果不是的话我们就需要找到尾节点的之前的一个节点,因为我们是单向不循环链表,是无法找到自己的前一个的,所以需要在找到尾节点的前一个的时候就循环进行停止。

代码如下(示例): 

void Poptail(Slist** list)
{
	assert(list);
	Slist* phead = *list;
	if (phead->next == NULL)
	{
		free(phead);
		phead = NULL;
	}
	else
	{
		while (phead->next->next)
		{
			phead = phead->next;
		}
		Slist* prev = phead->next;
		free(prev);
		prev = NULL;
	}
}

5.链表的插入

当实现链表的插入时,我们需要增加要插入目标的地址,以及判断传过来的地址,如果插入地址和头节点地址相同,那么直接运用头插的方式将新开辟空间地址的next指向头节点再将其赋值回去,如果为其他情况,通过循环找到插入目标地址的前一个地址,通过这个地址插入数据。

代码如下(示例): 

void SLinsert(Slist** list, Slist* pos, typedata data)
{
	if (pos == *list)
	{
		Slist* newnode = capicity(data);
		newnode->next = *list;
		*list = newnode;
	}
	else
	{
		Slist* prev = *list;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		Slist* newnode = capicity(data);
		prev->next = newnode;
		newnode->next = pos;
	}
}

6.链表的删除

先判断是否有数据可删,如果只有一位数据,直接将其free置空,如不不是只有一位数据,通过循环遍历找到指向目标地址的前一个指针地址,将其指向目标地址指向的下一个地址,将目标地址free掉置空。

代码如下(示例): 

void SLerase(Slist** list, Slist* pos)
{
	assert(list);
	if (pos == *list)
	{
		free(pos);
		pos = NULL;
	}
	else
	{
		Slist* prev = *list;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		prev->next = prev->next->next;
		free(pos);
		pos = NULL;
	}
}

 7.链表的打印

链表的打印通过循环遍历的方式,如果节点不为空,就将其赋值为它的下一个节点的地址。

代码如下(示例): 

void Pinttail(Slist* list)
{
	Slist* cur = list;
	while (cur)
	{
		printf("%d->", cur->data);
		cur = cur->next;
	}
	printf("NULL\n");
}

三、执行效果

#include"link.h"
int main()
{
	Slist* p = NULL;
	Pushhead(&p,10);
	Pushhead(&p,10);
	Pushhead(&p,1);
	Pushhead(&p,2);
	Pophead(&p);
	Pushtail(&p, 4);
	Pinttail(p);
	return 0;
}

总结 

很明显,我们可以看出单链表是不适合尾插尾删的,并且链表难在其逻辑上和物理上是不同的,相比顺序表单链表比顺序表适合头插头删,所以在合适的场景我们可以选择相应合适的数据结构,希望这篇文章对你有所帮助。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值