数据结构:超详细的详解链表(上)——单链表

本文介绍了链表的概念、特点,包括链式结构的逻辑连续性、节点动态分配、单向和双向链表的区别。详细讲解了单链表的节点设置、动态申请、头插、尾插、删除等操作,并强调了二级指针在修改指针指向的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

首先我们要明白他的基本概念:

概念:链表是一种物理存储结构上非连续、非顺序的存储结构数据元素的逻辑顺序是通过链表中的指针链接次序实现的。就像一个火车一样,连接每一个车厢。

我们会发现链表的特点:

1.从上图可看出,链式结构在逻辑上是连续的,但是在物理上不一定连续

2.现实中的结点一般都是从堆上申请出来的

3.从堆上申请的空间,是按照一定的策略来分配的,两次申请的空间可能连续,也可能不连续

 介绍完链表的基本概念和特点后,我们将会学习到许多不同类型的链表。

1.单向或双向链表

2.带头不带头链表

3.循环不循环链表

我们平时学习到的链表就是将这些分别组合起来,形成一个个链表结构。

虽然有这么多的链表的结构,但是我们实际中最常用还是两种结构

1. 无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。
2. 带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单了,后面我们代码实现了就知道了。

 现在我们先进行单链表的实现:

1.我们先进行节点的设置,链表结构的设置。

typedef int SLTDatetype;//将int类型重定义为SLTDatetype,方便之后如果类型变了,只需变一下重定义类型
typedef struct SListNode
{
    SLTDatetype date;//链表节点中的数据域,储存节点中的数据
    struct SListNode* next;//链表节点的指针域,储存节点中的指针,指向下一个节点。
}SLTNode;

 2.实现链表中的各项功能。

//动态申请一个节点。

SLTNode* buyListNode(SLTDatetype x);

//头插节点

void SLTpushfront(SLTNode** pphead, SLTDatetype x);

//尾插节点

void SLTpushback(SLTNode* phead,SLTDatetype x);

//尾删节点

void SLTpopback(SLTNode** pphead, SLTDatetype x);

//头删节点

void SLTpopfront(SLTNode** pphead, SLTDatetype x);
//pos位置前插

void SLTInsert(SLTNode**pphead,SLTNode*pos, SLTDatetype x);
//pos位置后插
void SLTInsertAfter(SLTNode* pos, SLTDatetype x);

//pos位置之前删除

void SLTerase(SLTNode** pphead, SLTNode* pos);

//pos位置后删除

void SLTeraseAfter(SLTNode** pphead, SLTNode* pos);

1.动态分配申请一个节点,这里我们将用到malloc函数,进行动态内存分配,在推中开辟一个空间,用来储存链表节点中的信息数据。

C语言:辨析malloc,realloc,calloc三个动态内存函数_阿威昂的博客-CSDN博客

SLTNode* buyListNode(SLTDatetype x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));//malloc动态分配函数
	if (newnode== NULL)
	{
		perror("malloc failed");
	}
	newnode->date = x;//将数据域置于x
	newnode->next == NULL;//下一节点指向空
	return newnode;//返回节点,因为返回类型是结构体变量
}

 2.头插节点。(头插法)

void SLTpushfront(SLTNode** pphead, SLTDatetype x)
{
	SLTNode* newnode = buyListNode(x);//先创建一个节点
	newnode->next = *pphead;//将那一个个节点指向头节点
	*pphead = newnode;//重新将那个节点置于头节点
}
 函数形参中为啥是**pphead?

是因为如果要改变指针的内容,就用一级指针,如果要改变指针指向,要用二级指针。头插了,头结点变了,第一个节点变了,就要二级指针。

3.尾插节点

void SLTpushback(SLTNode** pphead, SLTDatetype x)
{
	SLTNode* newnode = buyListNode(x);
	if (*pphead == NULL)//如果头节点为空,直接将新节点置于头节点
	{
		//改变的是结构体指针
		*pphead = newnode;
	}
	SLTNode* tail=*pphead;//创建一个tail尾节点,先置于头节点
	while (tail->next != NULL)//遍历到最后一个尾节点
	{
		tail = tail->next;
	}
	tail->next = newnode;//将链表最后一个节点指向新节点,实现尾插
}

4.头删节点

void SLTpopfront(SLTNode** pphead)
{
	//空
	assert(*pphead);
	//非空
	SLTNode* newnode = (*pphead)->next;//创建一个节点newhead表示pphead的下一个节点
	free(*pphead);//删除头节点
	*pphead = newnode;//重新将newhead置于头节点

}

     

5.尾删节点

void SLTpopback(SLTNode** pphead, SLTDatetype x)
{
	assert(*pphead);
	if ((*pphead)->next==NULL)//只有头节点时
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		SLTNode* tailprev = NULL;//创建一个节点表示尾节点的上一个节点
		SLTNode* tail = *pphead;//尾节点
		while (tail->next)//遍历
		{
			tailprev = tail;
			tail = tail->next;
		}
		free(tail);//删除尾节点
		tailprev->next = NULL;//让尾节点的上一个节点指向空
	}
}

 

 6.pos前插节点

void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDatetype x)
{
	assert(pos);
	if (pos == *pphead)
	{
		SLTpushfront(pphead, x);//如果pos为头节点则为需要头插。
	}
	else
	{
		SLTNode* prev = *pphead;
		while (prev->next !=pos )//遍历到pos前一个位置
		{
			prev = prev->next;
		}
		SLTNode* newnode = buyListNode(x);
		prev->next = newnode;//将上一个节点指向newnode新节点,然后指向pos节点
		newnode->next = pos;
	}

}

 

 7.pos后插入节点

void SLTInsertAfter( SLTNode* pos, SLTDatetype x)
{
	assert(pos);
	SLTNode* newnode = buyListNode(x);
	newnode->next = pos->next;//直接加入
	pos->next = newnode;

}

 8.pos前删除节点

void SLTerase(SLTNode** pphead, SLTNode* pos)
{
	assert(pos);
	if (pos == *pphead)//当pos为头节点时
	{
		SLTpopfront(pphead);
	}
	else
	{
		SLTNode* prev = *pphead;
		while (prev->next != pos)//遍历到pos前一个位置
		{
			prev = prev->next;

		}
    //删除操作
		prev->next = pos->next;
		free(pos);

	}
}

 

 9.pos后删除节点

void SLTeraseAfter(SLTNode** pphead, SLTNode* pos)
{
	assert(pos->next);
	SLTNode* posnext=pos->next;
	pos->next = posnext->next;//pos下一个节点指向pos下一个节点的下一个
	free(posnext);
	posnext = NULL;
}

 通过上面的这些操作,我们就已经学会掌握了单链表的基本操作,基本原理。学完之后,我们也可以去尝试做一下链表oj题。

链表中的常见oj问题解析_阿威昂的博客-CSDN博客

注意我们在使用链表时我们需要考虑一些情况进行判断,比如头节点是否为空,头节点下一个节点是否为空等.......

关于二级指针:如果要改变指针的内容,就用一级指针,如果要改变指针指向,要用二级指针。头插了,头结点变了,第一个节点变了,就要二级指针。

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值