【数据结构】:线性表之单链表(不带头节点)

前言

给爷冲。之后还会有带头结点的。
在这里插入图片描述

一 单链表的存储结构

单链表存储结构最重要的就是数据域指针域了,我们定义了一个额外的link 类型,其实就是LinkNode*类型,为了之后链表操作不用写二级指针,比较方便。当然,你不定义Link类型也可以。本质都一样。有些小伙伴不理解为什么单链表的操作(比如插入节点操作)要用二级指针?(c语言实现的基础上才有指针玩法)。
就这么简单和你说吧,我们在单链表用的指针用法,大多数都是在形参列表时候会用到,你就简单理解,因为你需要改变单链表里面的 next指针(一级),所以要传二级指针,就像你要改变一个变量一样(类比于0级指针),你需要传一级指针去改变这个变量。简单的理解,你需要改变多少级的变量,你就设计比它高一级的指针为形参就可以。先理解这一点很重要。

#define ELemType int
typedef struct LinkNode
{
	ELemType date; //数据域
	struct LinkNode* next; //指针域
}LinkNode;
typedef LinkNode* Link; 

由于单链表最最最常用的操作也就是尾插尾删头插头删了,所以掌握这几点就比较重要咯.

二 常用的接口实现

初始化

void InitLink(Link* head)
{
	*head = NULL;
}

初始化时候直接给链表赋值为空就可以,表示为空表。我手画了一个理解。
在这里插入图片描述
在vs2013平台的调试监视的结果:
在这里插入图片描述

1.尾插

尾插可是有好多细节呀!(因为指针,哎,就很容易出事)可要好好看看!尾删要说简单也简单,就是在链表的尾部插入节点嘛!先看代码:注意理解每一块的设计逻辑。

void Push_back(Link* head,ELemType x)
{
	//创建一个新结点newNode ,为尾插做准备
	LinkNode* newNode = (LinkNode*)malloc(sizeof(LinkNode));
	assert(newNode);
	newNode->date = x;
	newNode->next = NULL;
	
	//单独处理头指针
	if (*head == NULL)
		*head = newNode;
	else
	{
		//定义一个cur指针,保持*head指针不动,让cur指针动,用来迭代后移,尾插newNode节点
		LinkNode* cur = *head;
		while (cur->next)
		{
			cur = cur->next;
		}
		//尾插
		cur->next = newNode;
	}

先梳理以下尾插的步骤:

  1. 先开辟一个新节点newNode,并为其赋值。
  2. 然后单独出来头结点。
  3. 定义cur指针,保存*head不动,移动cur,尾插newNode节点。

我们思考一个问题:假如我们不单独处理头指针*head会怎么样?

答:程序会奔溃中断。因为,我们刚开始不是已经初始化了head = NULL了嘛。假如你没有单独处理头指针head,就会发生这种:cur 指向 *head 的同时也是指向 NULL,而 cur = cur->next;语句就会变成 cur = NULL->next;哎,出问题就在这里了。用一个空指针去找next这不是明摆着“诬陷”人家NULL嘛,人家NULL里面什么都没有,你还去找next,肯定找不到啊,肯定会出事,所以单独处理很必要。

不单独处理*head的测试:
在这里插入图片描述
正常条件的测试:
在这里插入图片描述


2.头插

头插比较简单,记住要修改头指针,使它始终指向第一个节点。

void Push_front(Link* head,ELemType x)
{	//申请节点
	LinkNode* newNode = (LinkNode*)malloc(sizeof(LinkNode));
	assert(newNode);
	newNode->date = x;
	newNode->next = NULL;
	//单独处理头结点
	if (*head == NULL)
		*head = newNode;
	else
	{  //头插
		newNode->next = *head;
		修改头指针,保证头指针指向第一个节点
		*head = newNode;
	}
}

测试图:
在这里插入图片描述

3.尾删

//尾删,最主要是找到尾节点,可是单链表不可以随机访问,也就是不能通过下标直接找到尾节点,所以要迭代去找。注意:删除节点时候要判断表是否为空,并且为了提高效率,可以单独处理只有一个节点的链表,这样就不用去迭代找了,直接删除即可。

void Pop_back(Link*phead)
{	//空链表不删
	if (*head == NULL)
	{
		return;
	}
	//只有一个节点,即为头结点,也是尾节点,直接删除
	else if ((*head)->next == NULL)
	{
		free(*head);
		*head = NULL;
	}
	//找尾节点
	else
	{   
		LinkNode* previous = NULL; //定义一个previous指针,为了保存为节点之前的节点。
		LinktNode* tail = *head;
		//迭代找尾节点
		while (tail->next != NULL)
		{
			previous = tail;
			tail = tail->next;
		}
		free(tail);
		//释放尾节点后,previous成为新的尾节点
		previous->next = NULL;
	}
}

4.头删

把头节点删除,记得头指针要重新指向即可。

void Pop_front(Link* head)
{
	if (*head == NULL)
		return;
	else
	{
		LinkNode* newNext = (*head)->next;
		free(*head);
		*head = newNext;
	}
}

5.申请节点

由于插入的操作都用到申请节点,其实可以直接写个函数就行。

LinkNode* BuyListNode(ElemType x)
{
	SListNode* newNode = (SListNode*)malloc(sizeof(SListNode));
	assert(newNode);
	newNode->date = x;
	newNode->next = NULL;
	return newNode;
}

总结

我给出的单链表操作不多,其实这几个理解掌握就很好了,方便你以后刷leetcode那些题,当然只会这些还是不行的。刷leetcode还是需要一些技巧,这只是最基础的东西。
同时你也发现,这些操作总是要单独处理头结点,那有没有一种好的办法可以把头节点的处理和普通处理方式同一呢?其实有的,在下一篇我会讲到,就是设置一个虚拟节点就行。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

呋喃吖

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

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

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

打赏作者

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

抵扣说明:

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

余额充值