数据结构之单向不循环链表

        由上文提及因顺序表有连续的物理存储结构,所以其头插头删效率较低,而本文就来介绍一种头插头删效率较高的数据结构——链表

一、定义:

        链表是一种物理上不连续,逻辑上连续的线性数据结构,可以将数据依次存放在一个又一个节点中,并且前后的两个节点都有一定的关联性。下图是一种链表类型(单向不带头不循环链表)的逻辑图

二、分类:

        链表有三个可以分类的点,分别是单向还是双向、是否循环,有无头结点

        头结点:它是在第一个存储数据的节点之前附设的一个新节点,其指针域指向第一个存储数据的节点。头结点可以不存储任何数据,也可根据需求存储数据(可存可不存)。

        由上图可得严格意义上链表分为8类,但从实用性来分析,主要用的是单向不带头不循环链表

和双向带头循环链表。本文主要详细讲单向不带头不循环链表,下节再讲双向带头循环链表。

上面展示了单向不带头不循环链表的逻辑图就不再重复了

下图则为双向带头循环链表的逻辑图(首元结点指的是链表中的存储第一个有效数据的节点

三、结构:

        根据三个特点:单向、不带头、不循环。

        实现单向:链表中的每个节点不仅要存储数据还要可以单一地指向下一个节点

        而在C语言中只有指针具有指向作用的,所以我们每个节点要需要通过结构体包含一个数据和一个指针,结构就如下所示

typedef int SLTDataType;
struct Seqlist
{
	SLTDataType data;
	struct Seqlist* next;
};
typedef struct Seqlist SLTNode;

        不带头:不另设一个节点指向第一个存储数据的节点。

        不循环:尾结点不指向首节点,而是指向空(NULL)。

如下图所示:

四、代码实现(单向不带头不循环):

1.创建节点

        两大步骤:

        一、申请一块空间可以存储数据

        二、存储好数据、将指针置为空(防止出现野指针问题)

SLTNode* SListBuyNode(SLTDataType x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		return;
	}
	newnode->data = x;
	newnode->next = NULL;
	return newnode;
}

2.打印

        两步走:一、打印节点数据

                       二、通过每个节点的指针找到下一个节点,遍历每个节点,直到为空结束。

void SLTPrint(SLTNode* phead)
{
	SLTNode* pcur = phead;
	while (pcur != NULL)
	{
		printf("%d->", pcur->data);
		pcur = pcur->next;
	}
	printf("NULL");
}

3.尾插

        在链表尾部插入一个节点,只需三大步

一、创建新的一个节点、存储好数据,将指针置空

二、通过指针,遍历链表,找到当前链表的尾结点。

三、改变尾结点指针指向,使新创建的节点变成新的尾结点

SLTNode* PushBack(SLTNode** pphead, SLTDataType x)
{
	SLTNode* node = (SLTNode*)malloc(sizeof(SLTNode));
	node->data = x;
	node->next = NULL;
	if (*pphead == NULL)
	{
		*pphead = node;
		
	}
	else
	{
		SLTNode* tail = *pphead;
		while (tail->next != NULL)
		{
			tail = tail->next;
		}
		tail->next = node;
	}
		
}

4.尾删

        删除链表尾部节点,需要分情况讨论

        若链表除了尾结点还有其余节点则需三步走

 一、通过指针,遍历链表,找到当前链表的尾结点以及尾结点前一个节点

二、销毁尾结点,使原尾结点前一个节点成为新的尾结点。

三、将新尾结点的指针置空。

        若链表除了尾结点无其余节点,则只需销毁尾节点。

assert(*pphead );

if ((*pphead)->next == NULL)
{
	free(*pphead);
	*pphead = NULL;

}
else
{
	SLTNode* tail = *pphead;
	SLTNode* plist = *pphead;
	while (tail->next)
	{
		plist = tail;
		tail = tail->next;
	}
	free(tail);
	plist->next = NULL;
}
	

5.头插

        在链表头部新插入一个节点,只需

一、创建新的一个节点、存储好数据,将指针置空。

二、新的节点指针指向原链表中的头结点,成为新头结点

SLTNode* PushFront(SLTNode** pphead, SLTDataType x)
{
	SLTNode* node = (SLTNode*)malloc(sizeof(SLTNode));
	node->data = x;
	assert(*pphead != NULL);
		
		node->next = *pphead;
		*pphead = node;
	
}

6.头删

  删除链表尾部节点,

        分情况处理:

        若链表除了头结点还有其余节点则两步走

一、通过指针关系,找到头节点的下一个节点

二、销毁原头结点、使头结点的下一个节点成为新头节点

        若链表除了头结点无其余节点,则只需销毁头节点。

SLTNode* PopFront(SLTNode** pphead)
{
	assert(pphead);
	assert(*pphead);

	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		SLTNode* tail=*pphead;
		*pphead = (*pphead)->next;
		free(tail);
		tail = NULL;
	}
	
}

7.查找

        通过指针关系遍历这个链表,将链表每个节点的数据与所要找的数据一一判断,若没有则返回空,若有则返回查找到的节点位置

SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{
	SLTNode* pcure = phead;
	while (pcure)
	{
		if (pcure->data == x)
		{
			return pcure;
		}
		pcure = pcure->next;
	}
	return NULL;
}

8.插入

        一般和查找搭配使用,在所找到的节点前插入新的节点,需分情况解决

        若链表本身无节点则直接头插即可。

        若有节点则需两步走

一、通过指针关系遍历整个链表找到所查找到节点的前一个节点。

 二、改变指针关系,将所查找到节点的前一个节点的指针指向新节点,新节点则指向所查找到的节点。

void SLTInsert(SLTNode** pphead, SLTNode* pcure, SLTDataType x)
{
	SLTNode* node = (SLTNode*)malloc(sizeof(SLTNode));
	node->data = x;

	if (pcure==*pphead)
	{
		SLTNode* PushFront(pphead, x);
	}
	else
	{
		
		SLTNode* tail = *pphead;
		while (tail->next != pcure)
		{
			tail = tail->next;
		}
		node->next = pcure;
		tail->next = node;
	}
	
}

  9.销毁

三步走: 一、通过指针关系遍历每个节点

               二、记录节点的下一个节点位置

               三、销毁节点,直到为空

void SLTDestory(SLTNode** pphead)
{
	assert(pphead && *pphead);
	SLTNode* tail = *pphead;
	SLTNode* next = *pphead;
	while (tail )
	{
		next = tail->next;
		free(tail);
		tail = next;
	}
	*pphead = NULL;//若不进行这个操作,phead就会变成野指针,很容易造成错误
}

五、总结:

        链表逻辑上是连续的,物理存储是不连续的。

        优点:头插头删效率较高,而且即用即插,需要一个数据就直接在合适的位置插入一个节点,没有很多空间上的浪费,

        缺点:因为物理存储是不连续的,所以访问效率很低,需要从头开始,才能访问到所要的数据,故不支持随机访问。

        在本文中,我们需要充分认识到链表的特性,用好其长处,避掉其短处,可以做到在合适的地方用好链表结构实现所需功能,对于我们未来学习更难的数据结构有重要的基础作用。

        以上就是我分享的全部内容了,希望对大家有些        帮助,也希望与一样喜欢编程的朋友们共进步

     谢谢观看

        如果觉得还阔以的话,三连一下,以后会持续更新的,我会加油的

        若有错误和不足,欢迎各位读者前来指正

        祝大家早安午安晚安

     

  • 25
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值