不带头不循环单链表—增删查改

目录

一、顺序表与与链表的对比

1.1顺序表的2个缺陷

2.顺序表和链表的优劣势对比

二、单链表的介绍

1.链表的概念

2.链表的结构

三、单链表各个功能函数的实现

1.创建结点函数BuySLTNode

2.创建n个结点的单链表函数CreateSList

下面展示了我在写关于链表OJ题时的调试代码的创建思路:

3.单链表打印函数SLTprint

4.单链表尾插函数SLTPushBack

5.头插函数SLTPushFront

6.单链表在pos位置之后插入数据函数SLTInsertAfter

7.单链表在pos位置之前插入数据函数SLTInsert

8.单链表尾删函数SLTPopBack

9.头删函数SLTPopFront

10.单链表删除pos位置之后的值的函数SLTEraseAfter

11.单链表删除pos位置的值的函数SLTErase

12.单链表查找函数SLTFind

13.单链表销毁函数SLTDestroy

四、不带头不循环单链表的整个工程

1.SList.h头文件

2.SList.c源文件

3.text.c源文件


一、顺序表与与链表的对比
1.1顺序表的2个缺陷

1.由于顺序表要求物理空间是连续的,若空间不够,需要扩容而扩容分为原地扩容和异地扩容(尤其是异地扩容)是有一定代价的。其次扩容还有可能存在一定空间浪费(因为顺序表进行扩容时一般扩2倍)。

2.顺序表在头部或者中部插入删除时,需要挪动数据,效率低下(注意:效率低指的是时间复杂度过大)。

注意:链表就可以克服顺序表的这两个缺陷。因为链表是按需申请动态空间的,而且在对链表进行插入函数数据时是不需要挪动数据。

2.顺序表和带头双向循环链表的优劣势对比

(1)

(2)

二、单链表的介绍
1.链表的概念

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

2.链表的结构

三、单链表各个功能函数的实现
1.创建结点函数BuySLTNode

每当我们需要增加一个结点之前,我们必定要先申请一个新结点,然后再插入到相应位置,于是我们可以将该功能封装成一个函数。

//由于单链表中有很多关于在链表插入数据的函数接口(例如:头插、尾插、在pos位置之后或之前插入等函数接口),所以我们才会单独创建1个BuySLTNode函数来创建结点的动态空间并对结点的成员进行初始化。
//创建结点并对结点中的成员进行初始化函数
SLTNode* BuySLTNode(SLTDataType x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	//判断newnode是否为空指针。若newnode != NULL,说明成功创建1个结点的动态空间
	if (newnode == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}

	//对结点的成员进行初始化
	newnode->next = NULL;
	newnode->data = x;
	return newnode;
}
2.创建n个结点的单链表函数CreateSList
//这里说明一下,单链表不用定义初始化函数Init的原因:由于顺序表、栈、队列等数据结构都是要用1个结构体进行控制的,所以这些数据结构必须要写初始化函数Init。但是单链表只需用1个指向头结点的指针控制即可
//(因为可以通过一个头结点指针就可以遍历整个链表),所以要想在主调函数中把头结点指针plist指向的单链表初始化为空链表的话只需把指针plist的值设置为空指针NULL即plist = NULL,所以在主调函数中单链表才不用写初始化函数把单链表初始化为空链表。
//
//创建n个结点的单链表
SLTNode* CreateSList(int n)
{
	//指针phead指向单链表的头结点,指针ptail指向单链表的尾结点
	SLTNode* phead = NULL;
	SLTNode* ptail = NULL;
	int i = 0;
	for (i = 1; i <= n; i++)
	{
		//写法1:
		int data = 0;
		scanf("%d", &data);
		SLTNode* newnode = BuySLTNode(data);//创建1个结点空间

		//写法2:
		/*SLTNode* newnode = BuySLTNode(i + 10);*/

		if (phead == NULL)//判断指针phead指向的链表是否为空链表,若是则把新创建结点的地址newnode赋值给指向头结点的指针phead,使得指针phead可以指向单链表的头结点。
			phead = ptail = newnode;
		else
		{
			ptail->next = newnode;//把指针ptail指向的单链表尾结点与新创建的结点链接起来。
			ptail = newnode;//更新ptail的值,使得指针ptail指向单链表新的尾结点。
		}
	}
	return phead;//若成功创建链表,则返回链表头结点的地址phead。
}
下面展示了我在写关于链表OJ题时的调试代码的创建思路:

OJ题中的任何关于实现一个接口型函数的链表练习题都要构建一个统一的链表进行调试即自己构建一个链表去调试接口函数:

(注意:不用在意下面调试代码中的removeElements函数,这个removeElements函数只是我做OJ题时要写的函数,这个removeElements函数不是创建链表的内容)

一、自己构建一个链表来调试接口函数的思路及过程

1. 自己构建一个链表来调试接口函数的规则:

(1)不用写一个sltpushback尾插函数来不断对主调函数中指针plist指向的空链表进行尾插来创建一个有结点的链表,而是在主调函数中直接利用创建好的struct ListNode* CreateSList(int*a ,int n)函数直接把指针plist指向的链表初始化为一个有n个结点的链表。

(注意:CreateSList函数的形参指针int*a指向的是主调函数中用OJ题中给的测试用例所创建的数组;CreateSList函数的形参int n表示的是数组的大小同时n也表示CreateSList函数要创建n个结点的链表。)

(2)在主调函数中利用OJ题中给的测试用例来创建一个数组并把这个数组传给CreateSList函数,而数组中的所有元素的值是CreateSList函数用来对链表所有结点中的成员变量数据val进行初始化的。

(注意:我们一定是要传OJ题中给的测试用例给CreateSList函数来调试函数接口的;而该题的函数接口是removeElements函数)

(3)在主调函数中还要计算数组的大小后传给CreateSList函数,而数组的大小的作用是即数组有多少个元素则就利用CreateSList函数创建多少个结点的链表来调试接口函数。

2. struct ListNode* CreateSList(int*a ,int n)函数的两种写法:

写法一:单独写一个struct ListNode* BuyNode(int x)用在CreateSList函数的内部创建结点的空间并对结点中的成员变量进行初始化。

代码:

写法二:不写BuyNode函数,而是在CreateSList函数的内部直接用malloc函数创建结点的空间并对结点的所有成员变量next和val进行初始化,而且成员变量val被初始化的值是数组a中的所有元素的值。

代码:

3.单链表打印函数SLTprint

打印链表时,我们需要从头指针指向的位置开始,依次向后打印,直到指针指向NULL时,结束打印。

// 单链表打印
void SLTPrint(SLTNode* phead)
{
	SLTNode* cur = phead;//由于我们一般不用指向链表头结点的指针phead来遍历链表而且要让指针phead始终指向链表的头结点以防止我们需要通过phead找到链表的头结点,所以我们一般会定义一个临时指针cur来遍历整个链表。

	//遍历整个链表
	while (cur)//当指针cur指向链表的空结点NULL时就会结束遍历链表。
	{
		//写法1:打印单链表的物理结构
		//printf("[%d|%p]->", cur->data, cur->next);
		
		//写法2:打印单链表的逻辑结构
		printf("%d->", cur->data);
		cur = cur->next;
	}
	printf("NULL\n");
}
4.单链表尾插函数SLTPushBack

尾插的时候我们需要先判断链表是否为空,若为空,则直接让头指针指向新结点即可;若不为空,我们首先需要利用循环找到链表的最后一个结点,然后让最后一个结点的指针域指向新结点。

注:新结点创建的时候指针域就已经置空,所以尾插时不需要再将新结点的指针域置空。

// 单链表尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
	//创建1个结点的空间
	SLTNode* newnode = BuySLTNode(x);

	//判断指针*pphead指向的单链表是否为空链表
	if (*pphead == NULL)
	{
		*pphead = newnode;//头指针直接指向新结点
	}
	else
	{
		SLTNode* cur = *pphead;
		//找尾结点
		while (cur->next)
		{
			cur = cur->next;
		}
		//链接
		cur->next = newnode;//此时指针cur指向链表的尾结点。这行代码的作用是让链表的尾结点与新创建的结点链接起来。
	}
}
5.头插函数SLTPushFront

头插时,我们只需要先让新结点的指针域指向头指针指向的位置(即原来的第一个结点),然后让头指针指向新结点即可。

注:这两步操作的顺序不能颠倒,若先让头指针指向新结点,那么就无法找到原来第一个结点的位置了。

// 单链表头插
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
	//创建1个结点
	SLTNode* newnode = BuySLTNode(x);
	//让新创建的结点与单链表的头结点链接起来
	newnode->next = *pphead;
	//更新头结点指针的指向,让头结点指针指向单链表新的头结点
	*pphead = newnode;
}
6.单链表在pos位置之后插入数据函数SLTInsertAfter

在给定位置后插入结点也只需要两步:先让新结点的指针域指向该位置的下一个结点,然后再让该位置的结点指向新结点即可。

/单链表在pos位置之后插入x
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{
	//判断指针pos是否为空指针即判断单链表是否存在该结点的地址pos。
	assert(pos);
	//创建结点
	SLTNode* newnode = BuySLTNode(x);

	//插入数据写法1:
	//SLTNode* next = pos->next;//先把pos位置的下一个结点的地址存放起来
	//pos->next = newnode;//再把pos位置的结点与新创建的结点链接起来
	//newnode->next = next;//最后让新创建的结点与pos位置的下一个结点链接起来

	插入数据写法2:
	newnode->next = pos->next;//先让新创建的结点与pos位置的下一个结点链接起来
	pos->next = newnode;//再把pos位置的结点与新创建的结点链接起来
}
7.单链表在pos位置之前插入数据函数SLTInsert

要想在给定位置的前面插入一个新结点,我们首先还是要找到该位置之前的一个结点,然后让新结点的指针域指向地址为pos的结点,让前一个结点指向新结点即可。需要注意的是,当给定位置为头指针指向的位置时,相当于头插。

// 单链表在pos位置之前插入x
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
	//判断指针pos是否为空指针即判断单链表是否存在该结点的地址pos。
	assert(pos);
	//判断是否是头插
	if (*pphead == pos)
	{
		//头插
		SLTPushFront(pphead, x);
	}
	else
	{
		//找pos位置的上一个结点
		SLTNode* cur = *pphead;
		while (cur->next != pos)
		{
			cur = cur->next;
		}
		//创建结点
		SLTNode* newnode = BuySLTNode(x);
		cur->next = newnode;//让pos位置的上一个结点与新创建的结点链接起来
		newnode->next = pos;//让新创建的结点与pos位置的结点链接起来
	}
}

8.单链表尾删函数SLTPopBack

尾删相对麻烦一些,我们需要考虑三种不同的情况:

(1)当链表为空时,不做处理。
(2)当链表中只有一个结点时,直接释放该结点,然后将头指针置空。
(3)当链表中有多个结点时,我们需要先找到最后一个结点的前一个结点,然后将最后一个结点释放,将前一个结点的指针域置空,使其成为新的尾结点。

// 单链表尾删
void SLTPopBack(SLTNode** pphead)
{
	//判断指针*pphead指向的单链表是否还有可以删除的数据即判断链表是否为空链表,若没有则不用继续尾删。
	assert(*pphead);

	//if语句的作用是:判断*pphead指向的单链表是否只有1个结点可以尾删
	if ((*pphead)->next == NULL)
	{
		//尾删
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		SLTNode* cur = *pphead;
		//找尾结点的上一个结点的写法1:
		while (cur->next->next)
		{
			cur = cur->next;
		}
		
		//尾删
		free(cur->next);//此时指针cur指向尾结点的上一个结点。
		cur->next = NULL;

		找尾结点的上一个结点的方法2:
		//SLTNode* prev = NULL;
		//SLTNode* cur = *pphead;
		整个while循环的过程中始终让指针prev和指针cur分别指向相邻的结点,当while循环结束后cur会指向尾结点,而prev会指向尾结点的上一个结点。
		//while (cur->next)
		//{
		//	prev = cur;
		//	cur = cur->next;
		//}
		//free(cur);//此时指针cur指向尾结点
		//prev->next = NULL;//此时prev会指向尾结点的上一个结点
	}
}
9.头删函数SLTPopFront

头删较为简单,若为空表,则不必做处理;若不为空表,则直接让头指针指向第二个结点,然后释放第一个结点的内存空间即可。

// 单链表头删
void SLTPopFront(SLTNode** pphead)
{
	//判断指针*pphead指向的单链表是否还有可以删除的数据即判断链表是否为空链表,若没有则不用继续头删。
	assert(*pphead);
	//把头结点的下一个结点的地址存储起来
	SLTNode* next = (*pphead)->next;
	//头删
	free(*pphead);
	//更新头结点指针的指向,让头结点指针指向单链表新的头结点
	*pphead = next;
}
10.单链表删除pos位置之后的值的函数SLTEraseAfter

要删除给定位置之后的值,我们首先判断传入地址是否为最后一个结点的地址,若是,则不做处理,因为最后一个结点后面没有结点可删除。若不是最后一个结点,我们首先让地址为pos的结点指向待删除结点的后一个结点,然后将待删除结点释放即可。

//单链表删除pos位置之后的值
void SLTEraseAfter(SLTNode* pos)
{
	//判断指针pos是否为空指针即判断单链表是否存在该结点的地址pos。
	assert(pos);
	//判断是否是删除尾结点的下一个结点,若是则不用删。
	if (pos->next == NULL)
		return;
	else
	{
		SLTNode* newnode = pos->next;//把pos位置的下一个结点的地址存放起来
		pos->next = newnode->next;//先把pos位置的结点与pos位置的下下一个结点链接起来
		free(newnode);//再删除pos位置的下一个结点
	}
}
11.单链表删除pos位置的值的函数SLTErase

要删除给定位置的结点,我们首先要判断该结点是否为第一个结点,若是,则操作与头删相同;若不是,我们就需要先找到待删除结点的上一个结点,然后让其指向待删除结点的下一个结点,最后才能释放待删除的结点。

// 单链表删除pos位置的值
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
	assert(pos);
	//判断是否是头删
	if (*pphead == pos)
	{
		SLTPopFront(pphead);
	}
	else
	{
		//找pos位置的上一个结点
		SLTNode* cur = *pphead;
		while (cur->next != pos)
		{
			cur = cur->next;
		}

		cur->next = pos->next;//把pos位置的上一个结点与pos位置的下一个结点链接起来
		free(pos);//删除pos位置的结点
	}
}
12.单链表查找函数SLTFind

查找数据相对于前面的来说就非常简单了,我们只需要遍历一遍链表,在遍历的过程中,若找到了目标结点,则返回结点的地址;若遍历结束也没有找到目标结点,则直接返回空指针。

// 单链表查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{
	SLTNode* cur = phead;
	while (cur)//遍历链表
	{
		if (cur->data == x)//判断结点是否为待找结点
			return cur;//返回目标结点的地址
		cur = cur->next;//指针后移
	}
	return NULL;//没有找到数据为x的结点
}
13.单链表销毁函数SLTDestroy
//单链表销毁函数
void SLTDestroy(SLTNode** pphead)
{
	SLTNode* cur = *pphead;
	while (cur)
	{
		SLTNode* next = cur->next;
		free(cur);
		cur = next;
	}

	*pphead = NULL;
}
四、不带头不循环单链表的整个工程
1.SList.h头文件
SList.h

#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

//单链表存放的数据类型
typedef int SLTDataType;

//单链表结点的结构体类型
//由于链表的结点是由数据和指向下一个结点的指针组成,而且结构体是用来表示不同值的集合,所以链表的结点应用结构体声明。
typedef struct SListNode
{
	SLTDataType data;//数据
	struct SListNode* next;//指针next是指向下一个结点的,所以指针next的类型是struct SListNode*。
}SLTNode;

//创建结点并对结点中的成员进行初始化函数
SLTNode* BuySLTNode(SLTDataType x);

//创建n个结点的单链表
SLTNode* CreateSList(int n);//若成功创建链表,则该函数返回一个指向单链表头结点的地址(指针)。

// 单链表打印
void SLTPrint(SLTNode* phead);

// 单链表尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x);//由于空链表也可以进行尾插,所以要想改变指向链表头结点指针phead的值,则我们必须传链表头结点指针phead的地址SLTNode** pphead给尾插函数。

// 单链表尾删
void SLTPopBack(SLTNode** pphead);//当对只有1个结点的单链表进行尾删时会把指向链表头结点指针phead赋值为NULL,由于这里涉及了改变头结点指针phead的值,则我们必须传链表头结点指针phead的地址SLTNode** pphead给尾删函数。

// 单链表头插
void SLTPushFront(SLTNode** pphead, SLTDataType x);//由于头插会使得指针phead指向新的头结点,这其中涉及到改变指向链表头结点指针phead的值,则我们必须传链表头结点指针phead的地址SLTNode** pphead给头插函数。

// 单链表头删
void SLTPopFront(SLTNode** pphead);//这里传参传二级指针SLTNode** pphead的原因与头插函数同理。

// 单链表查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x);//查找函数并不会改变指向链表头结点指针phead的值,所以我们只需传一级指针SLTNode* phead给查找函数即可。

// 单链表在pos位置之后插入x
void SLTInsertAfter(SLTNode* pos, SLTDataType x);//这里传参传一级指针SLTNode* phead的原因与查找函数同理。

// 单链表在pos位置之前插入x
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);//当指针pos指向链表头结点时,SLTInsert函数会使得指针phead指向新的头结点所以要传头结点指针phead的地址SLTNode** pphead给SLTInsert函数。

// 单链表删除pos位置之后的值
void SLTEraseAfter(SLTNode* pos);


// 单链表删除pos位置的值
void SLTErase(SLTNode** pphead, SLTNode* pos);//这里传参传二级指针SLTNode** pphead的原因与SLTInsert函数同理。

//单链表销毁函数
void SLTDestroy(SLTNode** pphead);
2.SList.c源文件
SList.c

#include "Slist.h"

//由于单链表中有很多关于在链表插入数据的函数接口(例如:头插、尾插、在pos位置之后或之前插入等函数接口),所以我们才会单独创建1个BuySLTNode函数来创建结点的动态空间并对结点的成员进行初始化。
//创建结点并对结点中的成员进行初始化函数
SLTNode* BuySLTNode(SLTDataType x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	//判断newnode是否为空指针。若newnode != NULL,说明成功创建1个结点的动态空间
	if (newnode == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}

	//对结点的成员进行初始化
	newnode->next = NULL;
	newnode->data = x;
	return newnode;
}

//这里说明一下,单链表不用定义初始化函数Init的原因:由于顺序表、栈、队列等数据结构都是要用1个结构体进行控制的,所以这些数据结构必须要写初始化函数Init。但是单链表只需用1个指向头结点的指针控制即可
//(因为可以通过一个头结点指针就可以遍历整个链表),所以要想在主调函数中把头结点指针plist指向的单链表初始化为空链表的话只需把指针plist的值设置为空指针NULL即plist = NULL,所以在主调函数中单链表才不用写初始化函数把单链表初始化为空链表。
//
//创建n个结点的单链表
SLTNode* CreateSList(int n)
{
	//指针phead指向单链表的头结点,指针ptail指向单链表的尾结点
	SLTNode* phead = NULL;
	SLTNode* ptail = NULL;
	int i = 0;
	for (i = 1; i <= n; i++)
	{
		//写法1:
		int data = 0;
		scanf("%d", &data);
		SLTNode* newnode = BuySLTNode(data);//创建1个结点空间

		//写法2:
		/*SLTNode* newnode = BuySLTNode(i + 10);*/

		if (phead == NULL)//判断指针phead指向的链表是否为空链表,若是则把新创建结点的地址newnode赋值给指向头结点的指针phead,使得指针phead可以指向单链表的头结点。
			phead = ptail = newnode;
		else
		{
			ptail->next = newnode;//把指针ptail指向的单链表尾结点与新创建的结点链接起来。
			ptail = newnode;//更新ptail的值,使得指针ptail指向单链表新的尾结点。
		}
	}
	return phead;//若成功创建链表,则返回链表头结点的地址phead。
}

// 单链表打印
void SLTPrint(SLTNode* phead)
{
	SLTNode* cur = phead;//由于我们一般不用指向链表头结点的指针phead来遍历链表而且要让指针phead始终指向链表的头结点以防止我们需要通过phead找到链表的头结点,所以我们一般会定义一个临时指针cur来遍历整个链表。

	//遍历整个链表
	while (cur)//当指针cur指向链表的空结点NULL时就会结束遍历链表。
	{
		//写法1:打印单链表的物理结构
		//printf("[%d|%p]->", cur->data, cur->next);
		
		//写法2:打印单链表的逻辑结构
		printf("%d->", cur->data);
		cur = cur->next;
	}
	printf("NULL\n");
}

// 单链表尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
	//创建1个结点的空间
	SLTNode* newnode = BuySLTNode(x);

	//判断指针*pphead指向的单链表是否为空链表
	if (*pphead == NULL)
	{
		*pphead = newnode;
	}
	else
	{
		SLTNode* cur = *pphead;
		//找尾结点
		while (cur->next)
		{
			cur = cur->next;
		}
		//链接
		cur->next = newnode;//此时指针cur指向链表的尾结点。这行代码的作用是让链表的尾结点与新创建的结点链接起来。
	}
}

// 单链表尾删
void SLTPopBack(SLTNode** pphead)
{
	//判断指针*pphead指向的单链表是否还有可以删除的数据,若没有则不用继续尾删。
	assert(*pphead);

	//if语句的作用是:判断*pphead指向的单链表是否只有1个结点可以尾删
	if ((*pphead)->next == NULL)
	{
		//尾删
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		SLTNode* cur = *pphead;
		//找尾结点的上一个结点的写法1:
		while (cur->next->next)
		{
			cur = cur->next;
		}
		
		//尾删
		free(cur->next);//此时指针cur指向尾结点的上一个结点。
		cur->next = NULL;

		找尾结点的上一个结点的方法2:
		//SLTNode* prev = NULL;
		//SLTNode* cur = *pphead;
		整个while循环的过程中始终让指针prev和指针cur分别指向相邻的结点,当while循环结束后cur会指向尾结点,而prev会指向尾结点的上一个结点。
		//while (cur->next)
		//{
		//	prev = cur;
		//	cur = cur->next;
		//}
		//free(cur);//此时指针cur指向尾结点
		//prev->next = NULL;//此时prev会指向尾结点的上一个结点
	}
}

// 单链表头插
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
	//创建1个结点
	SLTNode* newnode = BuySLTNode(x);
	//让新创建的结点与单链表的头结点链接起来
	newnode->next = *pphead;
	//更新头结点指针的指向,让头结点指针指向单链表新的头结点
	*pphead = newnode;
}

// 单链表头删
void SLTPopFront(SLTNode** pphead)
{
	//判断指针*pphead指向的单链表是否还有可以删除的数据,若没有则不用继续头删。
	assert(*pphead);
	//把头结点的下一个结点的地址存储起来
	SLTNode* next = (*pphead)->next;
	//头删
	free(*pphead);
	//更新头结点指针的指向,让头结点指针指向单链表新的头结点
	*pphead = next;
}

// 单链表查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{
	SLTNode* cur = phead;
	while (cur)
	{
		if (cur->data == x)
			return cur;
		cur = cur->next;
	}
	return NULL;
}

//单链表在pos位置之后插入x
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{
	//判断指针pos是否为空指针即判断单链表是否存在该结点的地址pos。
	assert(pos);
	//创建结点
	SLTNode* newnode = BuySLTNode(x);

	//插入数据写法1:
	//SLTNode* next = pos->next;//先把pos位置的下一个结点的地址存放起来
	//pos->next = newnode;//再把pos位置的结点与新创建的结点链接起来
	//newnode->next = next;//最后让新创建的结点与pos位置的下一个结点链接起来

	插入数据写法2:
	newnode->next = pos->next;//先让新创建的结点与pos位置的下一个结点链接起来
	pos->next = newnode;//再把pos位置的结点与新创建的结点链接起来
}

// 单链表在pos位置之前插入x
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
	//判断指针pos是否为空指针即判断单链表是否存在该结点的地址pos。
	assert(pos);
	//判断是否是头插
	if (*pphead == pos)
	{
		//头插
		SLTPushFront(pphead, x);
	}
	else
	{
		//找pos位置的上一个结点
		SLTNode* cur = *pphead;
		while (cur->next != pos)
		{
			cur = cur->next;
		}
		//创建结点
		SLTNode* newnode = BuySLTNode(x);
		cur->next = newnode;//让pos位置的上一个结点与新创建的结点链接起来
		newnode->next = pos;//让新创建的结点与pos位置的结点链接起来
	}
}

//单链表删除pos位置之后的值
void SLTEraseAfter(SLTNode* pos)
{
	//判断指针pos是否为空指针即判断单链表是否存在该结点的地址pos。
	assert(pos);
	//判断是否是删除尾结点的下一个结点,若是则不用删。
	if (pos->next == NULL)
		return;
	else
	{
		SLTNode* newnode = pos->next;//把pos位置的下一个结点的地址存放起来
		pos->next = newnode->next;//先把pos位置的结点与pos位置的下下一个结点链接起来
		free(newnode);//再删除pos位置的下一个结点
	}
}

// 单链表删除pos位置的值
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
	assert(pos);
	//判断是否是头删
	if (*pphead == pos)
	{
		SLTPopFront(pphead);
	}
	else
	{
		//找pos位置的上一个结点
		SLTNode* cur = *pphead;
		while (cur->next != pos)
		{
			cur = cur->next;
		}

		cur->next = pos->next;//把pos位置的上一个结点与pos位置的后一个结点链接起来
		free(pos);//删除pos位置的结点
	}
}

//单链表销毁函数
void SLTDestroy(SLTNode** pphead)
{
	SLTNode* cur = *pphead;
	while (cur)
	{
		SLTNode* next = cur->next;
		free(cur);
		cur = next;
	}

	*pphead = NULL;
}
3.text.c源文件
text.c

#include "SList.h"

//TestSList1()函数用于测试BuySLTNode创建结点函数、CreateSList创建链表函数、SLTPrint打印函数
void TestSList1()
{
	//创建有5个结点的单链表
	SLTNode* plist = CreateSList(5);
	//打印单链表
	SLTPrint(plist);
}

//TestSList2()函数用于测试SLTPushBack尾插函数
void TestSList2()
{
	//创建有5个结点的单链表
	SLTNode* plist = CreateSList(5);
	SLTPushBack(&plist, 100);
	SLTPushBack(&plist, 200);
	SLTPushBack(&plist, 300);
	SLTPrint(plist);
}

//TestSList3()函数用于测试SLTPopBack尾删函数
void TestSList3()
{
	SLTNode* plist = NULL;
	SLTPushBack(&plist, 100);
	SLTPushBack(&plist, 200);
	SLTPushBack(&plist, 300);
	SLTPrint(plist);

	SLTPopBack(&plist);
	SLTPrint(plist);

	SLTPopBack(&plist);
	SLTPrint(plist);

	SLTPopBack(&plist);
	SLTPrint(plist);

	SLTPopBack(&plist);
	SLTPrint(plist);
}

//TestSList4()函数用于测试SLTPushFront头插函数
void TestSList4()
{
	SLTNode* plist = NULL;
	SLTPushFront(&plist, 100);
	SLTPushFront(&plist, 200);
	SLTPushFront(&plist, 300);
	SLTPushFront(&plist, 400);

	SLTPrint(plist);
}

//TestSList5()函数用于测试SLTPopFront头删函数
void TestSList5()
{
	SLTNode* plist = NULL;
	SLTPushFront(&plist, 100);
	SLTPushFront(&plist, 200);
	SLTPushFront(&plist, 300);
	SLTPushFront(&plist, 400);

	SLTPrint(plist);

	SLTPopFront(&plist);
	SLTPrint(plist);
	SLTPopFront(&plist);
	SLTPrint(plist);
	SLTPopFront(&plist);
	SLTPrint(plist);
	SLTPopFront(&plist);
	SLTPrint(plist);
	/*SLTPopFront(&plist);
	SLTPrint(plist);*/
}

//TestSList6()函数用于测试SLTFind查找函数
void TestSList6()
{
	SLTNode* plist = NULL;
	SLTPushBack(&plist, 1);
	SLTPushBack(&plist, 2);
	SLTPushBack(&plist, 3);
	SLTPushBack(&plist, 4);
	SLTPushBack(&plist, 5);
	SLTPrint(plist);

	SLTNode* pos = SLTFind(plist, 5);
	if (pos)
		printf("找到了\n");
	else
		printf("没有找到\n");
}

//TestSList7()函数用于测试SLTInsertAfter单链表在pos位置之后插入x函数、SLTInsert单链表在pos位置之前插入x函数。
void TestSList7()
{
	SLTNode* plist = NULL;
	SLTPushBack(&plist, 1);
	SLTPushBack(&plist, 2);
	SLTPushBack(&plist, 3);
	SLTPushBack(&plist, 4);
	SLTPushBack(&plist, 5);
	SLTPrint(plist);

	SLTNode* pos = SLTFind(plist, 3);
	SLTInsertAfter(pos, 30);
	SLTPrint(plist);


	pos = SLTFind(plist, 2);
	SLTInsert(&plist, pos, 20);
	SLTPrint(plist);
}

//TestSList8()函数用于测试SLTEraseAfter单链表删除pos位置之后的值函数、SLTErase单链表删除pos位置的值。
void TestSList8()
{
	SLTNode* plist = NULL;
	SLTPushBack(&plist, 1);
	SLTPushBack(&plist, 2);
	SLTPushBack(&plist, 3);
	SLTPushBack(&plist, 4);
	SLTPushBack(&plist, 5);
	SLTPrint(plist);

	SLTNode* pos = SLTFind(plist, 3);
	SLTEraseAfter(pos);
	SLTPrint(plist);

	pos = SLTFind(plist, 3);
	SLTErase(&plist, pos);
	SLTPrint(plist);
}

//TestSList9()函数用于测试SLTDestroy单链表销毁函数
void TestSList9()
{
	SLTNode* plist = NULL;
	SLTPushBack(&plist, 1);
	SLTPushBack(&plist, 2);
	SLTPushBack(&plist, 3);
	SLTPushBack(&plist, 4);
	SLTPushBack(&plist, 5);
	SLTPrint(plist);

	SLTNode* pos = SLTFind(plist, 3);
	SLTEraseAfter(pos);
	SLTPrint(plist);

	pos = SLTFind(plist, 3);
	SLTErase(&plist, pos);
	pos = NULL;
	SLTPrint(plist);

	SLTDestroy(&plist);
	SLTPrint(plist);
}

int main()
{
	TestSList1();
	TestSList2();
	TestSList3();
	TestSList4();
	TestSList5();
	TestSList6();
	TestSList7();
	TestSList8();
	TestSList9();
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值