<1>不带头 单向 不循环链表

        本文基于链表的规定实现了对应的增删查改,同时实现了很多其它接口,这些接口都是有广泛应用的。链表的接口实现约在十个左右。        

        本文结构是先将链表的每一部分进行分析,最后给出完整代码,完整代码中只包含链表的实现。想要调用链表需要自己写main函数。

1、定义结点的结构

结构定义的代码如下:

typedef int SLDataType;
typedef struct SLNode
{
	SLDataType data;
	struct SLNode* next;
}SLNode;

分析:①在代码的第一行我们使用了typedef对int进行了重定义,这是因为,我们此时保存的数据是int类型的,如果以后想要存储其它类型的数据,我们只需要在重定义的地方修改数据类型就可以了,不用在整个代码中去一个一个修改。

           ②我们使用typedef对结构体进行了重定义,这是因为对于C语言来说,定义结构体时struct不能够省略掉,所以我们在写代码时总是带着struct会浪费很多时间,所以我们对结构体进行了类型重定义;

           ③在结构体中,我们定义了要保存的数据类型,和指向下一个结点的指针,因为链表在空间上不一定连续,是用指针来连接的。

2、头插

头插的代码如下:

void SListPushFront(SLNode** head, SLDataType x)
{
	assert(head);
	SLNode* newnode = BuySListNode(x);
	//1.没有头结点
	//2.有头结点
	newnode->next = *head;
	*head = newnode;
}

分析:①头插时,我们要考虑两种情况,一种情况是链表为空,一种是链表不为空,但是不管是哪一种情况,我们都需要改变指针指向第一个结点,因此我们需要用二级指针。因为我们定义的是一个指向链表第一个结点的指针,我们要改变这个指针的指向就必须要传这个指针的地址,也就是二级指针。(这个需要好好理解,弄清传址调用和传值调用)

           ②我们要在链表中插入,必须要有新的结点,因此我们需要申请一个新的结点,又因为尾插,任意位置插,都需要申请新的结点,这样就造成重复的代码写多次,所以我们就将申请结点的代码封装成了函数。也就是函数SLNode* BuySListNode(SLDataType x);代码如下:

SLNode* BuySListNode(SLDataType x)
{
	SLNode* newnode = (SLNode*)malloc(sizeof(SLNode));
	if (newnode == NULL)
	{
		perror("malloc fail:");
		exit(-1);
	}
	newnode->data = x;
	newnode->next = NULL;
	return newnode;
}

           ③我们要考虑我们提出的那两种情况,我们应该是从普遍考虑到特殊。对于头插来说,链表不为空时是大多数情况,而链表为空是特殊情况。所以我们先写了链表不为空时的情况,思路就是:申请一个结点,让这个结点指向原本链表的第一个结点,然后改变头指针的指向,使头指针指向这个新结点。然后我们考虑特殊情况,将特殊情况带入代码中读,看是否会出现错误,通读之后发现没错,因此,两种情况可以同时处理掉。

3、尾插

尾插的代码如下;

void SListPushBack(SLNode** head, SLDataType x)
{
	assert(head);
	SLNode* newnode = BuySListNode(x);
	//1.没有头节点
	if (*head == NULL)
	{
		*head = newnode;
		return;
	}
	//2.有头节点
	SLNode* tail = *head;
	while (tail->next)
	{
		tail = tail->next;
	}
	tail->next = newnode;
}

分析:①尾插时,我们仍然要考虑两种情况,一种情况是链表为空,一种情况是链表不为空。如果链表为空,我们在插入一个结点之后,头指针就不再是空,就需要改变头指针指向这个结点,因此我们也用了二级指针(如果对二级指针了解的不是很清楚,建议看一眼头插的①,再不行请翻我的C笔记,关于指针的部分),如果链表不为空,我们就在尾部进行插入,因此链表不为空时不需要改变头指针的指向。

           ②在尾插中,我们也申请了新的结点,结点的申请,我们封装成了一个函数,函数的具体实现在头插的②部分。

           ③我们来讨论上述的两种情况,尾插时,链表不为空是大多数的情况,链表为空是特殊情况,因此我们先来写链表不为空时的代码,思路是:创建一个指针,找到尾结点,让尾结点指向新的结点。然后我们带着特殊情况通读代码,发现tail->next这句代码对空指针进行了解引用,编译器会报错,所以我们就需要对特殊情况进行特殊的处理。判断如果链表为空时,让头指针指向新结点。

4、头删

头删的代码如下:

void SListPopFront(SLNode** head)
{
	assert(head);
	//1.没有结点
	assert(*head);
	//2.有一个结点
	//3.有多个结点
	SLNode* NextNode = (*head)->next;
	free(*head);
	*head = NextNode;
}

分析:①头删时,我们要考虑三种情况,一种是链表为空时,一种是链表有一个结点时,一种是链表有多个结点时。因为第二种情况,链表只有一个结点时,删掉,会让链表为空。所以可能需要使头指针指向空,所以我们还是需要传二级指针。

           ②我们来讨论三种情况,第一种情况,如果链表为空,是不能再删除的,所以我们直接使用assert断言,来限制链表为空的这种情况,如果链表为空进行删除的话,就会抛出错误。第二种情况和第三种情况,第三种情况是头删中比较普遍的,而第二种情况是一种特殊的情况,因此我们根据第三种情况写出代码,思路:先让头指针指向第二个结点,然后释放掉第一个结点的空间。然后我们根据代码带入第二种特殊的情况,通读代码发现代码可以处理这个特殊情况。

5、尾删

尾删的代码如下:

void SListPopBack(SLNode** head)
{
	assert(head);
	//1.没有结点
	assert(*head);
	//2.有一个结点
	if ((*head)->next == NULL)
	{
		free(*head);
		*head = NULL;
	}
	else
	{
		//3.有多个结点
		SLNode* prev = NULL;
		SLNode* tail = *head;
		while (tail->next)
		{
			prev = tail;
			tail = tail->next;
		}
		free(tail);
		tail = NULL;
		prev->next = NULL;
	}
}

分析:①尾删时我们考虑三种情况,一种是链表为空时,一种是链表只有一个结点时,一种是链表有多个结点时。如果链表只有一个结点,那么删掉这个结点之后,链表就会为空,我们就需要将头指针置为空,所以,在尾删时我们也要传二级指针。

           ②我们来讨论这三种情况,第一种情况链表为空时,这种情况我们是直接使用assert断言来限定死的,因为链表为空时没有结点可删。第二种情况链表只有一个结点时很明显是第三种情况的特殊情况,所以我们先来写第三种情况的代码,思路是:创建两个指针,让一个指针指向尾结点,另一个指针指向尾结点前面的那个结点,释放尾结点,同时将尾结点前面的结点指向置空。通过思路写出代码,我们再带入特殊情况来读代码,发现代码不可行,也就是代码处理不了那种特殊情况,所以我们再讨论如果只有一个结点的情况,这种情况,我们只需要将此结点释放,然后将头指针置空即可。

6、打印链表(方便我们的测试)

打印链表代码如下:

void SListPrint(SLNode* head)
{
	assert(head);
	SLNode* cur = head;
	while (cur)
	{
		printf("%d->",cur->data);
		cur = cur->next;
	}
	printf("NULL\n");
}

分析:①打印代码时不会改变链表,更不会改变头指针,所以只需要传一级指针即可。打印链表即是遍历一遍链表,定义一个指针,从头结点开始,依次遍历链表,当指针指向空时结束遍历。

7、查找

查找代码如下:

SLNode* SListFind(SLNode* head, SLDataType x)
{
	assert(head);
	SLNode* cur = head;
	while(cur)
	{
		if (cur->data == x)
			return cur;
		cur = cur->next;
	}
	return NULL;
}

分析:①查找链表时也是不需要改变链表,更不需要改变头指针,所以也是传的一级指针。查找链表的方式和打印链表的方式几乎相似,打印链表是打印每个结点存储的值,而查找链表是要获得我所指定的值的结点的地址,如果链表中有这个值,则返回这个结点的地址,如果不存在,则返回空指针。

8、任意位置之后插入

任意位置之后插入代码如下:

void SListInsertAfter(SLNode* pos, SLDataType x)
{
	assert(pos);
	SLNode* newnode = BuySListNode(x);
	SLNode* NextNode = pos->next;
	pos->next = newnode;
	newnode->next = NextNode;
}

分析:①在任意位置之后插入一个值,我们首先断言了pos不为空指针,也就是一定有一个结点,我们只需要在其后连接一个结点即可。那么就有两种情况,一种是此结点为尾结点,一种是此结点不为尾结点。

   ②此结点不为尾结点一定是比较普遍的情况,所以我们先写这种情况的代码,思路:创建一个指针,保存好pos结点指向的结点的地址,然后让pos结点指向新结点,再让新结点指向原本pos指向的结点。然后我们通过写的代码,来带入特殊的情况,发现代码可以处理。

9、任意位置之后删除

任意位置之后删除代码如下:

void SListEraseAfter(SLNode* pos)
{
	assert(pos);
	assert(pos->next);
	SLNode* NextNode = pos->next;
	SLNode* NextNodeS = NextNode->next;
	free(NextNode);
	NextNode = NULL;
	pos->next = NextNodeS;
}

分析:①在任意位置之后删除,我们需要考虑三种情况,一种情况是pos结点之后为空,一种情况是pos结点之后还有一个结点,一种情况是pos结点之后还有多个结点。如果pos结点之后为空,那就没有结点可删,所以这种情况是不可能发生的,所以我们断言一下,防止这种情况。剩下的两种情况中后者明显是普遍的那个情况,前者是特殊的那种情况,所以我们先写出处理普遍情况的那个代码,思路是:创建两个指针,一个指针指向pos之后的结点,一个指针指向pos之后的结点指向的结点。删除pos之后的结点,然后将pos后面那个结点指向的结点连接到pos结点上。然后通读代码,可以发现,这段代码可以处理前面的那两种情况。

10、任意位置之前插入

任意位置之前插入代码如下:

void SLTInsert(SLNode** pphead, SLNode* pos, SLDataType x)
{
	assert(pphead);
	//1.pos是头节点
	if (pos == *pphead)
	{
		SListPushFront(pphead,x);
	}
	else
	{
		//2.pos不是头节点
		SLNode* prev = *pphead;
		SLNode* newnode = BuySListNode(x);
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		prev->next = newnode;
		newnode->next = pos;
	}
}

分析:①在任意位置之前插入结点,这种插入是有可能改变头结点的,并且这种插入还需要pos结点之前的那个结点,所以需要传头指针的二级。一共有两种情况,一种情况是pos是头指针,一种情况是pos不是头指针。

           ②我们来讨论这两种情况,我们先写pos不是头指针的情况,思路是:我们通过头指针,找到pos结点之前的那个结点,然后让这个结点指向新结点,然后再让新结点指向pos结点。然后我们来通读代码,发现不能处理pos是头指针的情况,所以我们判断一下pos是不是头指针,如果pos是头指针,我们直接调用了一个头插,这个地方也可以自己写代码处理一下不用调用头插。

11、任意位置删除

任意位置删除代码如下:

void SLTErase(SLNode** pphead, SLNode* pos)
{
	assert(pphead);
	assert(*pphead);
	//pos是头节点
	if (pos == *pphead)
	{
		SLNode* next = pos->next;
		free(pos);
		*pphead = next;
	}
	else
	{
		//pos不是头节点
		SLNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		SLNode* next = pos->next;
		free(pos);
		pos = NULL;
		prev->next = next;
	}
}

分析:①删除任意位置的结点,我们发现,这种情况也是可能会改变头指针的,并且我们也需要pos结点之前那个结点。所以我们也需要传头指针的二级。这种删除有三种情况,一种是链表为空,一种是pos是头结点,一种是pos不是头结点。

           ②第一种情况链表为空,为空时不可以删除,所以可以使用assert来断言一下。第三种情况明显比第二种情况更普遍,所以我们先写第三种情况,思路是:我们通过头指针,找到pos结点之前的结点,然后通过pos结点找到之后的一个结点,释放掉pos结点,然后让pos之前的结点指向pos之后的结点。然后我们通读代码,发现当pos是头指针是,代码处理不了,所以我们可以调用头删来解决,也可以自己写代码来解决,代码思路:保存pos结点之后的结点,然后释放掉pos结点,再让头结点指向pos结点之后的那个结点。

12、销毁链表

销毁链表代码如下:

void SLTDestroy(SLNode** pphead)
{
	assert(pphead);
	SLNode* cur = *pphead;
	while (cur)
	{
		SLNode* next = cur->next;
		free(cur);
		cur = next;
	}
	*pphead == NULL;
}

分析:①销毁链表,当然是需要改变链表,改变头指针的,所以必传二级。链表的销毁本质上也是遍历链表。思路是:创建一个指针,再保存下一个结点,不断的释放当前的指针指向的结点,然后再让这个释放指针等于下一个结点的指针,不断的重复,直到释放完所有的结点。但是最后一定要将头指针置空。

完整代码如下所示:

SList.h

#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
typedef int SLDataType;
typedef struct SLNode
{
	SLDataType data;
	struct SLNode* next;
}SLNode;
void SListPushFront(SLNode** head,SLDataType x);
void SListPushBack(SLNode** head,SLDataType x);
void SListPopFront(SLNode** head);
void SListPopBack(SLNode** head);
void SListPrint(SLNode* head);
// 单链表查找
SLNode* SListFind(SLNode* plist, SLDataType x);
// 单链表在pos位置之后插入x
// 分析思考为什么不在pos位置之前插入?
void SListInsertAfter(SLNode* pos, SLDataType x);
// 单链表删除pos位置之后的值
// 分析思考为什么不删除pos位置?
void SListEraseAfter(SLNode* pos);
// 在pos的前面插入
void SLTInsert(SLNode** pphead, SLNode* pos, SLDataType x);
// 删除pos位置
void SLTErase(SLNode** pphead, SLNode* pos);
void SLTDestroy(SLNode** pphead);

SList.c

#include"SList.h"
//申请一个新的结点
SLNode* BuySListNode(SLDataType x)
{
	SLNode* newnode = (SLNode*)malloc(sizeof(SLNode));
	if (newnode == NULL)
	{
		perror("malloc fail:");
		exit(-1);
	}
	newnode->data = x;
	newnode->next = NULL;
	return newnode;
}
void SListPushFront(SLNode** head, SLDataType x)
{
	assert(head);
	SLNode* newnode = BuySListNode(x);
	//1.没有头结点
	//2.有头结点
	newnode->next = *head;
	*head = newnode;
}
void SListPushBack(SLNode** head, SLDataType x)
{
	assert(head);
	SLNode* newnode = BuySListNode(x);
	//1.没有头节点
	if (*head == NULL)
	{
		*head = newnode;
		return;
	}
	//2.有头节点
	SLNode* tail = *head;
	while (tail->next)
	{
		tail = tail->next;
	}
	tail->next = newnode;
}
void SListPopFront(SLNode** head)
{
	assert(head);
	//1.没有结点
	assert(*head);
	//2.有一个结点
	//3.有多个结点
	SLNode* NextNode = (*head)->next;
	free(*head);
	*head = NextNode;
}
void SListPopBack(SLNode** head)
{
	assert(head);
	//1.没有结点
	assert(*head);
	//2.有一个结点
	if ((*head)->next == NULL)
	{
		free(*head);
		*head = NULL;
	}
	else
	{
		//3.有多个结点
		SLNode* prev = NULL;
		SLNode* tail = *head;
		while (tail->next)
		{
			prev = tail;
			tail = tail->next;
		}
		free(tail);
		tail = NULL;
		prev->next = NULL;
	}
}
void SListPrint(SLNode* head)
{
	assert(head);
	SLNode* cur = head;
	while (cur)
	{
		printf("%d->",cur->data);
		cur = cur->next;
	}
	printf("NULL\n");
}
// 单链表查找
SLNode* SListFind(SLNode* head, SLDataType x)
{
	assert(head);
	SLNode* cur = head;
	while(cur)
	{
		if (cur->data == x)
			return cur;
		cur = cur->next;
	}
	return NULL;
}
// 单链表在pos位置之后插入x
// 分析思考为什么不在pos位置之前插入?
void SListInsertAfter(SLNode* pos, SLDataType x)
{
	assert(pos);
	SLNode* newnode = BuySListNode(x);
	SLNode* NextNode = pos->next;
	pos->next = newnode;
	newnode->next = NextNode;
}
// 单链表删除pos位置之后的值
// 分析思考为什么不删除pos位置?
void SListEraseAfter(SLNode* pos)
{
	assert(pos);
	assert(pos->next);
	SLNode* NextNode = pos->next;
	SLNode* NextNodeS = NextNode->next;
	free(NextNode);
	NextNode = NULL;
	pos->next = NextNodeS;
}
// 在pos的前面插入
void SLTInsert(SLNode** pphead, SLNode* pos, SLDataType x)
{
	assert(pphead);
	//1.pos是头节点
	if (pos == *pphead)
	{
		SListPushFront(pphead,x);
	}
	else
	{
		//2.pos不是头节点
		SLNode* prev = *pphead;
		SLNode* newnode = BuySListNode(x);
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		prev->next = newnode;
		newnode->next = pos;
	}
}
// 删除pos位置
void SLTErase(SLNode** pphead, SLNode* pos)
{
	assert(pphead);
	assert(*pphead);
	//pos是头节点
	if (pos == *pphead)
	{
		SLNode* next = pos->next;
		free(pos);
		*pphead = next;
	}
	else
	{
		//pos不是头节点
		SLNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		SLNode* next = pos->next;
		free(pos);
		pos = NULL;
		prev->next = next;
	}
}
void SLTDestroy(SLNode** pphead)
{
	assert(pphead);
	SLNode* cur = *pphead;
	while (cur)
	{
		SLNode* next = cur->next;
		free(cur);
		cur = next;
	}
	*pphead == NULL;
}

  • 14
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

不会算法的笨小孩

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

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

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

打赏作者

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

抵扣说明:

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

余额充值