不带头单向不循环链表的实现

本文档详细介绍了C++中的单链表模板类SList的实现,包括节点定义、基本操作如头插、尾插、查找、删除和插入,以及测试代码展示了如何使用这些功能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.SList.h文件

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

typedef int SListType;
//定义一个单链表的模板
typedef struct SListNode {
	SListType data;//数据域
	struct SListNode* next;//指针域,存储下一个节点的地址
}SLNode;

//打印单链表的数据域的内容
void SListPrint(SLNode* phead);

//创建一个新节点
SLNode* BuySLNode(int x);

//单链表的尾插(在单链表的末尾加一个节点)
void SListPushBack(SLNode** pphead, SListType x);//因为plist是头节点的地址,是传一级指针还是传二级指针,就看有没有需要改变plist的情况(需不需要换头)

//单链表的头插(在单链表的开头加一个节点)
void SListPushFront(SLNode** pphead, SListType x);

//单链表的尾删(删除最后一个节点)
void SListPopBack(SLNode** pphead);

//单链表的头删(删除第一个节点)
void SListPopFront(SLNode** pphead);

//单链表中查找某个元素x,找到就返回其所在节点地址(才有修改的副属性),找不到就返回NULL,查找到节点地址后,可以通过地址访问到data,并修改其值
SLNode* SListFind(SLNode* phead, SListType x);//查找不需要改变plist,所以不需要传plist的地址,传plist的值就行

//在pos地址指向节点的前面插入一个新节点
void SListInsert(SLNode** pphead, SLNode* pos, SListType x);

//在pos地址指向节点的后面插入一个新节点
void SListInsertAfter(SLNode* pos, SListType x);//因为永远不可能换头节点,所以不用传&plist

//删除pos地址处的节点
void SListErase(SLNode** pphead, SLNode* pos);//有可能换头,所以必须传头节点的地址&plist

//删除pos地址处节点的后一个节点
void SListEraseAfter(SLNode* pos);//肯定不可能换头,所以不需要传&plist,也就是pphead

//销毁单链表,释放每个节点的空间
void SListDestroy(SLNode** pphead);//需要改变plist,所以要传&plist

2.SList.c文件

#define _CRT_SECURE_NO_WARNINGS 1
#include "SList.h"

void SListPrint(SLNode* phead)//打印不需要改变plist的值,所以传plist就可以,不需要传plist的地址
{//空链表也可以打印,所以这里不需要断言
	SLNode* cur = phead;//创建单链表节点实时指针cur,最开始指向首个节点
	while (cur != NULL)//直到实时指针为空指针,说明没有下一个节点了,则停下
	{
		printf("%d->", cur->data);//通过实时指针访问节点成员data,打印
		cur = cur->next;//将下个节点的地址赋值给实时指针cur,也就是实时指针cur指向下个节点
	}
	printf("NULL\n");//为了是打印出来的结尾很形象地表示链表的结尾
}

SLNode* BuySLNode(int x)//创建新节点
{
	SLNode* newnode = (SLNode*)malloc(sizeof(SLNode));
	if (newnode == NULL)
	{
		printf("newnode fail\n");
		exit(-1);
	}
	newnode->data = x;
	newnode->next = NULL;
	return newnode;
}

void SListPushBack(SLNode** pphead, SListType x)//一开始单链表还没有节点,也就是节点的指针是NULL,想要插入新节点,也就必须要改变plist的值,所以需要传plist的地址
{	//plist可能为空,但&plist不可能为空,也就是pphead不可能为空,所以需要断言
	assert(pphead != NULL);
	//上来直接创建一个新节点(因为不管此时单链表原本有没有节点,都需要新增新节点)
	SLNode* newnode = BuySLNode(x);

	//若一开始单链表还没有节点,也就是*pphead是NULL,那就直接将上面开辟的新节点的地址给*pphead
	if (*pphead == NULL)//其实*pphead就是plist
	{
		*pphead = newnode;
	}
	//若一开始就存在有节点,则*pphead肯定不为NULL,所以就要找尾节点,将上面开辟好的新节点的地址赋值给尾节点的成员next
	else
	{
		SLNode* tail = *pphead;//从头开始找尾节点
		while (tail->next != NULL)//找尾节点,尾节点的特点是成员next是NULL
		{
			tail = tail->next;
		}
		tail->next = newnode;//找到尾节点后,将上面开辟好的新节点的地址赋值给尾节点的成员next
	}
}

void SListPushFront(SLNode** pphead, SListType x)
{	//plist可能为空,但&plist不可能为空,也就是pphead不可能为空,所以需要断言
	assert(pphead != NULL);
	//上来直接创建一个新节点(因为不管此时单链表原本有没有节点,都需要新增新节点)
	SLNode* newnode = BuySLNode(x);
	newnode->next = *pphead;//*pphead就是plist,也就是将plist赋值给新节点的成员next
	*pphead = newnode;//然后把新节点的地址赋值给plist
}

void SListPopBack(SLNode** pphead)
{	//plist可能为空,但&plist不可能为空,也就是pphead不可能为空,所以需要断言
	assert(pphead != NULL);
	//删除节点前,必须要确保单链表内有节点
	//温柔的方式
	if (*pphead == NULL)
	{
		return;
	}
	 
	//暴力的方式
	//assert(*pphead != NULL);

	//已经确保单链表内有节点,还得分只有1个节点和有2个及2个以上节点的情况
	if ((*pphead)->next == NULL)//只有一个节点的情况,因为不存在倒数第二个节点,不需要把其成员next置为NULL的操作
	{
		free(*pphead);
		*pphead = NULL;
	}
	else//有2个及2个以上节点的情况,因为存在倒数第二个节点,需要把其成员next置为NULL的操作
	{
		////方式1:找尾节点tail,前面还得有个prev记录倒数第二个节点
		//SLNode* tail = *pphead;//从头开始找尾节点
		//SLNode* prev = NULL;
		//while (tail->next != NULL)
		//{
		//	prev = tail;
		//	tail = tail->next;
		//}
		//free(tail);//释放最后一个节点的空间
		//tail = NULL;//把最后一个节点的指针置为NULL,防止也野指针
		//prev->next = NULL;//把倒数第二个节点的成员next置为NULL,防止野指针
		
		//方式2:直接找到倒数第二个节点prev
		SLNode* prev = *pphead;
		while (prev->next->next != NULL)
		{
			prev = prev->next;
		}
		free(prev->next);//prev->next就是最后一个节点的地址,这句就是释放最后一个节点的空间
		prev->next = NULL;//这里prev->next即表示最后一个节点的地址又表示倒数第二个节点的成员next,一句就可以把2者都置为NULL
	}
}

void SListPopFront(SLNode** pphead)
{	//plist可能为空,但&plist不可能为空,也就是pphead不可能为空,所以需要断言
	assert(pphead != NULL);
	//删除节点前,必须要确保单链表内有节点
	//温柔的方式
	if (*pphead == NULL)
	{
		return;
	}

	//暴力的方式
	//assert(*pphead != NULL);

	//需要把第2个节点的地址先保存,然后把第一个节点释放,最后把第2个节点的地址赋值给*pphead
	SLNode* tmp = (*pphead)->next;
	free(*pphead);
	*pphead = tmp;
}

SLNode* SListFind(SLNode* phead, SListType x)//查找不需要改变plist,所以不需要传plist的地址,传plist的值就行
{
	SLNode* cur = phead;//从头开始找
	while (phead != NULL && cur->next != NULL)//单链表内无节点或者从头开始查找到最后一个节点还未找到就停下
	{
		if (cur->data == x)
		{
			return cur;//找到了就返回其节点地址
		}
		cur = cur->next;//到下个节点去找
	}
	return NULL;//找到最后一个节点都找不到就返回NULL
}

void SListInsert(SLNode** pphead, SLNode* pos, SListType x)
{	//plist可能为空,但&plist不可能为空,也就是pphead不可能为空,所以需要断言
	assert(pphead != NULL);
	SLNode* newnode = BuySLNode(x);//进来直接创建一个新节点
	//考虑到当pos不是首节点地址时,需要找到pos前一个节点posprev,将prev->next置为pos,
	//当pos就是首节点地址时,因为不存在前一个节点,如果强行去访问,属于越界,所以必须分情况讨论

	if (pos == *pphead)//当pos就是首节点地址,相当于就是头插
	{
		newnode->next = pos;
		*pphead = newnode;
	}
	else//当pos不是首节点地址时,就要找到posd的前一个节点posprev
	{
		SLNode* posprev = *pphead;//从头开始找posprev,时间复杂度是O(n)
		while (posprev->next != pos)
		{
			posprev = posprev->next;
		}
		newnode->next = pos;//找到posprev后
		posprev->next = newnode;
	}
}

void SListInsertAfter(SLNode* pos, SListType x)//因为永远不可能换头节点,所以不用传&plist
{
	SLNode* newnode = BuySLNode(x);//进来直接创建一个新节点
	if (pos == NULL)//防止找不到x的情况
	{
		return;
	}
	newnode->next = pos->next;
	pos->next = newnode;
}

void SListErase(SLNode** pphead, SLNode* pos)//有可能换头,所以必须传头节点的地址&plist
{	//plist可能为空,但&plist不可能为空,也就是pphead不可能为空,所以需要断言
	assert(pphead != NULL && pos != NULL);
	if (pos == *pphead)//如果要删除的节点是头节点,那就是必须换头
	{
		*pphead = pos->next;
		free(pos);
	}
	else//如果要删除的节点不是头节点,那就要去找前一个节点
	{
		SLNode* posprev = *pphead;//从头开始往后找
		while (posprev->next != pos)
		{
			posprev = posprev->next;
		}
		posprev->next = pos->next;
		free(pos);
		pos = NULL;//可以不写这句,因为pos是形参是实参的临时拷贝,pos置空不会影响实参,
		//且pos是局部变量,虽然不置空就是野指针,但出了此函数就销毁了,其他地方无法访问到此野指针
	}
}

void SListEraseAfter(SLNode* pos)//肯定不可能换头,所以不需要传&plist,也就是pphead
{
	assert(pos != NULL);
	SLNode* next = pos->next;
	pos->next = next->next;
	free(next);
	next = NULL;//这句可以不写,虽然不写的话next就是也指针,但因为next是局部变量,出了此函数,就销毁了,外面访问不到next
}

void SListDestroy(SLNode** pphead)//需要改变plist,所以要传&plist
{
	assert(pphead != NULL);
	SLNode* cur = *pphead;//从头开始释放
	SLNode* next = NULL;
	while (cur)
	{
		next = cur->next ;//释放前,先将其下个节点的地址保存,要不然释放后会找不到下个节点,就无法释放下个节点了
		free(cur);
		cur = next;
	}
	*pphead = NULL;//全部节点释放完后把plist置空
}

3.test.c文件

#define _CRT_SECURE_NO_WARNINGS 1
#include "SList.h"

void SListTest1()
{
	SLNode* plist = NULL;//一开始单链表还没有节点,也就是节点的指针是NULL
	SListPushBack(&plist, 1);//想要插入新节点,也就必须要改变plist的值,所以需要传plist的地址
	SListPushBack(&plist, 2);
	SListPushBack(&plist, 3);
	SListPrint(plist);//打印不需要改变plist的值,所以传plist就可以,不需要传plist的地址
}

void SListTest2()
{
	SLNode* plist = NULL;//一开始单链表还没有节点,也就是节点的指针是NULL
	SListPushFront(&plist, 1);//想要插入新节点,也就必须要改变plist的值,所以需要传plist的地址
	SListPushFront(&plist, 2);
	SListPushFront(&plist, 3);
	SListPrint(plist);//打印不需要改变plist的值,所以传plist就可以,不需要传plist的地址

	SListPopBack(&plist);
	SListPopBack(&plist);
	SListPopBack(&plist);
	//SListPopBack(&plist);
	SListPrint(plist);
}

void SListTest3()
{
	SLNode* plist = NULL;//一开始单链表还没有节点,也就是节点的指针是NULL
	SListPushFront(&plist, 1);//想要插入新节点,也就必须要改变plist的值,所以需要传plist的地址
	SListPushFront(&plist, 2);
	SListPushFront(&plist, 3);
	SListPrint(plist);//打印不需要改变plist的值,所以传plist就可以,不需要传plist的地址

	SListPopFront(&plist);
	SListPrint(plist);

	SListPopFront(&plist);
	SListPrint(plist);

	SListPopFront(&plist);
	SListPrint(plist);
}

void SListTest4()
{
	SLNode* plist = NULL;
	SListPushFront(&plist, 1);
	SListPushFront(&plist, 2);
	SListPushFront(&plist, 3);
	SListPushFront(&plist, 2);
	SListPushFront(&plist, 5);
	SListPrint(plist);
	
	SLNode* pos = SListFind(plist, 2);//查找单链表内的2
	int i = 1;
	while (pos != NULL)//若存在多个2的情况,则一一打印出其所在节点地址
	{
		printf("第%d个%d的地址:%p\n", i++, pos->data, pos);
		pos->data = 9;//查找还带有修改的副属性:这里找到2了,将其全部修改为9,如果只想修改第一个2,那就别用循环,直接用if
		pos = SListFind(pos->next, 2);
	}
	SListPrint(plist);
}

void SListTest5()
{
	SLNode* plist = NULL;
	SListPushFront(&plist, 1);
	SListPushFront(&plist, 2);
	SListPushFront(&plist, 3);
	SListPushFront(&plist, 2);
	SListPushFront(&plist, 5);
	SListPrint(plist);

	SLNode* pos = SListFind(plist, 2);//查找单链表内的第一个2
	if (pos != NULL)//找到后
	{
		SListInsert(&plist, pos, 0);//在第一个2的前一个节点插入0
	}
	//如果想在第2个2前面插一个0,则应该用循环
	int i = 1;
	while (pos != NULL)
	{
		if (i == 2)
		{
			SListInsert(&plist, pos, 0);
		}
		pos = SListFind(pos->next, 2);
		i++;
	}
	SListPrint(plist);
}

void SListTest6()
{
	SLNode* plist = NULL;
	SListPushFront(&plist, 1);
	SListPushFront(&plist, 2);
	SListPushFront(&plist, 3);
	SListPushFront(&plist, 2);
	SListPushFront(&plist, 5);
	SListPrint(plist);

	SLNode* pos = SListFind(plist, 2);//查找单链表内的第一个2
	if (pos != NULL)//找到了
	{
		SListInsertAfter(pos, 0);//在第一个2后插入0
	}
	SListPrint(plist);

	////如果想在第2个2后面插一个0,则应该用循环
	//int i = 1;
	//while (pos != NULL)
	//{
	//	if (i == 2)
	//	{
	//		SListInsertAfter(pos, 0);
	//	}
	//	pos = SListFind(pos->next, 2);
	//	i++;
	//}
	//SListPrint(plist);

	//SListErase(&plist, pos);//删除第一个2
	//SListPrint(plist);

	SListEraseAfter(pos);//把第一个2后面的节点删掉
	SListPrint(plist);

	SListDestroy(&plist);
}
int main()
{
	//SListTest1();
	//SListTest2();
	//SListTest3();
	//SListTest4();
	//SListTest5();
	SListTest6();
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值