单链表中头节点带与不带哨兵位的区别(一级指针与二级指针)

一、前言

大家最开始接触单链表时,大多都是用不带哨兵位的方法实现的。不带哨兵位时我们通常需要用到二级指针去接收所定义的SLNode*phead的地址才能实现自己想要的,那为什么带哨兵位用一级指针就可以呢?我想大家对此或多或少都会有些疑惑,甚至自己也想不通要怎样实现带哨兵位的单链表。我接下来对带哨兵位的单链表进行实现并对上述问题进行讲解。

二、带哨兵位单链表的实现

Fir:创建头文件、函数实现源文件、主函数源文件

这都是程序员敲代码的老三样了

Sec:头文件的初始准备

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int SLDataType;
typedef struct SList
{
	SLDataType  val;
	struct SList* next;
}sl;

之后我们在函数实现的时候要现在头文件中声明,再去实现,方便主函数调用。

Thir:函数实现

1、创造新节点

sl * CreatNewNode(SLDataType x)
{
	sl* newnode = (sl*)malloc(sizeof(sl));
	if (newnode == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	newnode->val = x;
	newnode->next = NULL;
	return newnode;
}

我们创造一个新节点,就要为它申请一个空间,用malloc函数来申请

如果申请失败,perror(''malloc fail'')打印出申请失败的原因

并且exit(-1)会向主机环境返回不成功的终止状态。

如果成功,对其进初始化并且return返回

我们在插入节点时都会用到这个函数,并且,我们对这个单链表的要求是需要带哨兵位的,我们需要自己创建一个哨兵位,也会用到这个函数来创建。

2、初始化函数 

sl* InitSList()
{
	sl* phead = CreatNewNode(-1);
	phead->next = NULL;
	return phead;
}

我们既然所要求的是带哨兵位的单链表,那么我们希望的是它本来就有的,既然没有,那我们在刚开始就要自己创建,所以我们在初始化的时候,申请一个哨兵位出来,并且对这个哨兵位里面的值进行赋值为-1(随机值)。

所以为什么带哨兵位的单链表可以用一级指针?

重点来了 ,我们在函数实现,比如头插、尾插的过程中。由于不带哨兵位的单链表我们在主函数会创建一个头节点,进而会对这个头节点里面的值进行改变,既然涉及到改变,我们都知道,形参只不过是实参的临时拷贝,在函数内修改过后出作用域就销毁了,并不会对实参有影响,所以当我们要改变实参的值时,我们就需要将实参的地址传过去,找到它的地址就可以修改了,所以不带哨兵位的单链表才会运用到二级指针。

我们创建的哨兵位里面存在一个随机值(我们上面给的-1),我们也不会进行修改,哨兵位的永远指向下一节点,我们头插、尾插从头到尾,都不会对哨兵位有任何影响,所以我们就不需要传过去它的地址。所以我们就用一级指针就可以了。

3、打印函数

void SListPrint(sl*phead)
{
	assert(phead->next);
	sl* cur = phead->next;
	while (cur != NULL)
	{
		printf("%d->", cur->val);
		cur = cur->next;
	}
	printf("NULL\n");
}

phead是我们的哨兵位,真正链表的开始在它的下一个位置,所以我们断言时,判断的是phead->next是否为空。

4、尾插函数

void InsertPushBack(sl* phead, SLDataType x)
{
	
	sl* newnode = CreatNewNode(x);
	if (phead->next == NULL)
	{
		phead->next = newnode;
	}
	else
	{
		sl* cur = phead->next;
		while(cur->next != NULL)
		{
			cur = cur->next;
		}
		cur->next = newnode;
	}
}

创造一个新节点后,我们首先判断哨兵位指向的下一个节点是否为空,是的话就直接插入,不是的话就要去找这个链表的尾巴,在最后插入。

找尾巴的时候,定义一个结构体指针cur,将哨兵位指向的下一个节点赋给cur,用cur来找尾巴。因为我们的哨兵位是不能改变的,当找到尾巴(cur->next==NULL)时,直接将让cur->next==newnode。

5、头插函数

void InsertPushFront(sl* phead, SLDataType x)
{
	
	sl* newnode = CreatNewNode(x);
	if (phead->next == NULL)
	{
		phead->next = newnode;
		newnode->next = NULL;
	}
	else
	{
		sl* cur = phead->next;
		phead->next = newnode;
		newnode->next = cur;
	}
}

头插也是一样,我们创造一个新节点后,判断哨兵位指向的下一个节点是否为空,如果为空,那么就直接phead->next=NULL,如果不为空,我们需要先定义一个结构体指针cur将phead->next存起来,因为如果我们直接将phead->next=newhead的话,我们就找不到原来phead指向的下一个节点了,所以插入之前我们需要将phead->next存起来。

5、尾删函数

void PopBack(sl* phead)
{
	assert(phead->next);
	sl* cur = phead->next;
	while (cur->next->next != NULL)
	{
		cur = cur->next;
	}
    free(tail->next);
	cur->next = NULL;
	
}

先断言,目的是为了防止phead->next=NULL,空了就不能再删除了

如果不为空,就开始找尾巴,操作与尾插相同。

找到之后我们将它free释放掉就可以了

6、头删函数

void Popfront(sl* phead)
{
	assert(phead->next);
	sl* cur = phead->next->next;
    free(phead->next);
	phead->next = cur;
	
}

断言与尾删相同。

但是需要注意的是,这里删除第一个节点时,只需要将phead直接连接第二个节点就好了。

先定义一个结构体指针cur,将第二个节点赋给它,再释放掉phead->next。最后直接将phead->next=cur就好了。

7、查找函数

sl* SListFind(sl* phead, SLDataType x)
{
	assert(phead->next);
	sl* cur = phead->next;
	while (cur)
	{
		
		if (cur->val == x)
		{
			return cur;
		}
        else
        {
            cur=cur->next;
        }

	}
	return NULL;
}
	

这里的查找并不难,暴力遍历查找就可,判断条件就是查看节点中的值是否等于x,最后返回该节点。

8、在任意位置插入一个节点

void SLTInsert(sl* phead, sl* pos, SLDataType x)
{
	assert(pphead);
	assert(pos);

	if (phead->next == pos)
	{
		InsertPushFront(phead, x);
	}
	else
	{
		sl* prev = phead->next;
		while (prev->next != pos)
		{
			prev = prev->next;
		}

		sl* newnode = CreateNode(x);
		prev->next = newnode;
		newnode->next = pos;
	}
}

这里用两个assert函数来断言是因为我们要保证pos节点是链表中的一个有效节点

9、删除任意节点

void SLTErase(sl* phead, sl* pos)
{
	assert(phead);
	
	assert(pos);

	
	if (phead->next == pos)
	{
		
		Popfront(phead);
	}
	else
	{
		sl* prev = phead->next;
		while (prev->next != pos)
		{
			prev = prev->next;
		}

		prev->next = pos->next;
		free(pos);
		pos = NULL;
	}
}

10、销毁函数

void SLTDestroy(sl* phead)
{
	assert(phead->next);

	sl* cur = phead->next;
	while (cur)
	{
		sl* next = cur->next;
		free(cur);
		cur = next;
	}

	phead = NULL;
}

三、总结

我在这里主要想说的是为什么带哨兵为的单链表可以不用二级指针,其他函数的实现其实都与不带哨兵位的单链表函数实现差别不大。

这方面涉及到的知识是指针,我觉得这是C语言学习中最让人头疼的一个知识点,但是它真的很重要。

  • 26
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值