对单链表为所欲为(增删改查,随你心意)

一:SList.h

  • 首先我们把整个工程的实现分到三个文件中,分别是SList.c,SList.h,Test.c
  • 在SList.h中完成结构体的定义头文件的包含函数的声明三个任务
  • 其中我们有两点需要注意:
    1.我们链表的每一个节点都是一个结构体,包含数据域和指针域
    2.数据域中的类型不要直接写死,比如直接写成int,double,不方便修改
    为了便于修改,可以使用typedef 将所需的类型重命名为SLTDataType,这样后续只要修改typedef后的类型就可实现数据域存储数据的类型修改
  • 代码如下:
#define _CRT_SECURE_NO_WARNINGS 1
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int SLTDataType;
typedef struct SListNode
{
	SLTDataType data;
	struct SListNode* next;
}SLTNode;
//链表打印
void SLTPrint(SLTNode* phead);
//链表尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x);
//链表头插
void SLTPushFront(SLTNode** pphead, SLTDataType x);
//链表尾删
void SLTPopBack(SLTNode** pphead);
//链表头删
void SLTPopFront(SLTNode** pphead);
//链表查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x);
//链表任意位置删除
void SLTErase(SLTNode** pphead, SLTNode* pos);
//链表pos位置前插入 
void SLTInsertBefore(SLTNode** pphead, SLTNode* pos, SLTDataType x);
//链表pos位置后插入 
void SLTInsertAfter(SLTNode** pphead, SLTNode* pos, SLTDataType x);
//链表销毁
void SLTDestory(SLTNode** pphead);

二:SList.c(完成函数的实现)

1.SLTPrint(链表打印)

  • 这个函数实现非常简单,只需要链表的头节点的地址,用一个指针cur从头开始遍历链表,打印数据域的数值之后,将cur指针指向下一个节点
  • 代码如下:
void SLTPrint(SLTNode* phead)
{
	SLTNode* cur = phead;
	while (cur)
	{
		printf("%d->", cur->data);
		cur = cur->next;
	}
	printf("NULL");
}

2.CreateListNode(创建新节点)

  • 这个函数是一个辅助函数,功能是创建一个新节点并返回这个节点的地址
  • 在后面进行头插,尾插,任意位置的插入时都会使用
  • 为了避免代码重复,所以写成一个函数
  • 代码如下:
SLTNode* CreateListNode(SLTDataType x)
{
	SLTNode* NewNode = (SLTNode*)malloc(sizeof(SLTNode));
	if (NewNode == NULL)
	{
		perror("malloc:");
		exit(-1);
		//申请失败结束程序,如果使用return,其它函数仍会继续执行,会得到一个空指针,导致程序出错
	}
	NewNode->data = x;
	NewNode->next = NULL;
	return NewNode;
}

3.SLTPushBack(链表尾插)

  • 这个函数有几点需要注意:
  1. 函数传参是需要用二级指针接收,因为传参会传头指针的地址
  2. 传头指针地址的原因是头指针有可能被修改,比如空链表进行尾插时
  3. 为了避免使用者在传参时误传成头指针而不是头指针的地址,可以对pphead加上断言。因为pphead存的是头指针的地址,不可能是空指针,但要是链表为空时进行尾插,参数误传成头指针而不是头指针的地址,pphead的值就会为NULL,加上assert断言可以帮助使用者快速找出错误。其它需要传头指针地址时同样需要加上断言。
  4. 需要对空链表的情况特殊考虑
  • 具体实现:

  • 先使用CreateListNode创建一个新节点,之后对链表是否为空进行判断

  • 如果为空直接将新节点赋给头指针

  • 如果不为空创建指针tail,遍历链表找到链表的尾部

  • 终止条件是tail->next为空指针,此时tail指向最后一个节点

  • 将tail的next即指针域存储新节点的地址,链接成功请添加图片描述

  • 代码如下

void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);
	SLTNode* NewNode = CreateListNode(x);
	if (*pphead == NULL)
	{
		*pphead = NewNode;
	}
	else
	{
		SLTNode* tail = *pphead;
		while (tail->next)
		{
			tail = tail->next;
		}
		tail->next = NewNode;
	}
}

4.SLTPushFront(链表头插)

  • 相比于尾插,头插就十分的简单

  • 不需要考虑链表是否为空,所有情况都一样

  • 效果展示:请添加图片描述

  • 代码如下:

void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);
	SLTNode* NewNode = CreateListNode(x);
	NewNode->next = *pphead;
	*pphead = NewNode;
}

5.SLTPopBack(链表尾删)

  • 这个函数有几点需要注意:
  1. 函数传参是需要用二级指针接收,因为传参会传头指针的地址
  2. 传头指针地址的原因是头指针有可能被修改,比如链表只有一个节点进行尾删时
  3. 为了避免使用者在传参时误传成头指针而不是头指针的地址,可以对pphead加上断言。(具体原因在尾插中解释过,不赘述)
  4. 需要防止空链表进行尾删
  5. 只有一个节点进行删除时需要进行另外考虑
  • 具体实现:

  • 先对pphead和phead进行断言防止空指针

  • 判断是否链表只有一个节点

  • 如果只有一个节点,直接进行free,然后将头指针置为NULL

  • 如果不止一个节点,创建变量tail找到倒数第二个节点和尾节点

  • 终止条件是tail->next->next为空指针,此时tail指向倒数第二个节点

  • 释放tail->next,即释放尾节点将tail节点的指针域next置为空指针请添加图片描述

  • 代码如下:

void SLTPopBack(SLTNode** pphead)
{
	assert(pphead);
	assert(*pphead);
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		SLTNode* tail = *pphead;
		while (tail->next->next)
		{
			tail = tail->next;
		}
		free(tail->next);
		tail->next = NULL;
	}
}

6.SLTPopFront(链表头删)

  • 相比于尾删,头删又是十分简单
  • 不过我们还是要注意
  1. 函数传参是需要用二级指针接收,因为传参会传头指针的地址
  2. 传头指针地址的原因是头指针有可能被修改,比如链表只有一个节点进行头删时
  3. 为了避免使用者在传参时误传成头指针而不是头指针的地址,可以对pphead加上断言。(具体原因在尾插中解释过,不赘述)
  4. 需要防止空链表进行头删
  • 具体实现:

  • 先对pphead和phead进行断言防止空指针

  • 创建变量Newhead存储下一个节点的地址

  • 释放头节点,将头指针的值改为Newhead

  • 如果先释放头指针就找不到新的头了,所以需要先保存再释放

  • 效果展示:
    请添加图片描述

  • 代码如下:

void SLTPopFront(SLTNode** pphead)
{
	assert(pphead);
	assert(*pphead);
	SLTNode* Newhead = (*pphead)->next;
	free(*pphead);
	*pphead = Newhead;
}

7.SLTFind(链表查找)

  • 这个函数实现也比较简单
  • 因为不会改变头指针我们可以不使用二级指针
  • 具体实现:
  • 创建变量cur先指向链表头,开始遍历链表
  • 判断cur此时数据域是否是想要的值
  • 如果是返回此时的cur
  • 如果不是cur指向下一个节点
  • 如果没找到返回NULL

多个相同值的查找

  • 也许你会想有多个相同的值如何查找,下面的代码或许能帮助你少许请添加图片描述

修改链表节点的值

  • 能找到某个节点修改它就是轻而易举了
    请添加图片描述

  • 代码如下:

SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{
	SLTNode* cur = phead;
	while (cur)
	{
		if (cur->data == x)
		{
			return cur;
		}
		else
		{
			cur = cur->next;
		}
	}
	return NULL;
}

8.SLTErase(链表任意位置删除)

  • 这个函数有几点需要注意:
  1. 函数传参是需要用二级指针接收,因为传参会传头指针的地址
  2. 传头指针地址的原因是头指针有可能被修改,比如链表只有一个节点进行尾删时
  3. 为了避免使用者在传参时误传成头指针而不是头指针的地址,可以对pphead加上断言。(具体原因在尾插中解释过,不赘述)
  4. 需要防止空链表进行删除
  5. 其中参数之一的pos是通过SLTFind函数查找而得,可以说SLTFind也是个辅助函数
  6. 头删可以另外考虑,也可以复用代码
  • 具体实现:

  • 先对pphead和phead进行断言防止空指针

  • 判断是否是头删,是头删可以复用前面头删函数

  • 不是头删则创建指针PosPrev,目的是找到Pos前一个节点

  • 遍历链表找到Pos的前一个节点,注意终止条件为PosPrev->next等于Pos

  • 之后将PosPrev的指针域的值改为pos的next即pos指针域的值

  • 这一步相当于PosPrev的指针域指向Pos之后的一个节点

  • 释放Pos,完成节点删除

  • 将Pos置为NULL请添加图片描述

  • 代码如下

void SLTErase(SLTNode** pphead, SLTNode* pos)
{
	assert(pphead);
	assert(*pphead);
	//删头时(复用头删代码)
	if (*pphead == pos)
	{
		//*pphead = pos->next;
		//free(pos);
		SLTPopFront(pphead);
	}
	else
	{
		SLTNode* PosPrev = *pphead;
		while (PosPrev->next != pos)
		{
			PosPrev = PosPrev->next;
		}
		PosPrev->next = pos->next;
		free(pos);
		pos = NULL;
	}
}

9.SLTInsertBefore(链表任意节点前插入)

  • 这个函数有几点需要注意:
  1. 函数传参是需要用二级指针接收,因为传参会传头指针的地址
  2. 传头指针地址的原因是头指针有可能被修改,比如链表只有一个节点进行尾删时
  3. 为了避免使用者在传参时误传成头指针而不是头指针的地址,可以对pphead加上断言。(具体原因在尾插中解释过,不赘述)
  4. 空链表插入需要单独考虑
  5. 其中参数之一的pos是通过SLTFind函数查找而得,可以说SLTFind也是个辅助函数
  6. 头插可以另外考虑,也可以复用代码
  • 具体实现:

  • 先对pphead和断言防止空指针

  • 如果链表为NULL,pos也为NULL,是空链表插入,可以复用头插代码

  • 如果单纯pos为NULL,链表不为空,则是参数传错,进行断言规避这种情况

  • 判断是否是头插,是头插可以复用前面头插函数

  • 不是头插则 申请新节点准备插入,再创建指针PosPrev,目的是找到Pos前一个节点

  • 遍历链表找到Pos的前一个节点,注意终止条件为PosPrev->next等于Pos

  • 之后将PosPrev的指针域的值改为新节点NewNode的next即NewNode指针域的值

  • 这一步相当于PosPrev的指针域指向NewNode这个新节点

  • 将NewNode的next改为pos,就是让NewNode的指针域指向pos,到此完成在任意位置的前面插入新节点请添加图片描述

  • 效果展示:请添加图片描述

  • 代码如下:

void SLTInsertBefore(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
	assert(pphead);
	//空链表插入
	if (*pphead == NULL && pos==NULL)
	{
		SLTPushFront(pphead, x);
		return;
	}
	assert(pos);
	//头部的插入(复用头插函数)
	 if (pos==*pphead)
	{
		SLTPushFront(pphead, x);
	}
	else
	{
		SLTNode* NewNode = CreateListNode(x);
		SLTNode* PosPrev = *pphead;
		while (PosPrev->next != pos)
		{
			PosPrev = PosPrev->next;
		}
		PosPrev->next = NewNode;
		NewNode->next = pos;
	}
}

10.SLTInsertAfter(链表任意节点后插入)

  • 这个函数实现起来比在链表任意节点前插入要简单,因为不用遍历链表找pos的上一个节点了,实现也十分的相似
  • 注意点如下:
  1. 函数传参是需要用二级指针接收,因为传参会传头指针的地址
  2. 传头指针地址的原因是头指针有可能被修改,比如链表只有一个节点进行尾删时
  3. 为了避免使用者在传参时误传成头指针而不是头指针的地址,可以对pphead加上断言。(具体原因在尾插中解释过,不赘述)
  4. 空链表插入需要单独考虑
  5. 其中参数之一的pos是通过SLTFind函数查找而得,可以说SLTFind也是个辅助函数
  6. 头插可以另外考虑,也可以复用代码
  • 具体实现:

  • 先对pphead和断言防止空指针

  • 如果链表为NULL,pos也为NULL,是空链表插入,可以复用头插代码

  • 如果单纯pos为NULL,链表不为空,则是参数传错,进行断言规避这种情况

  • 判断是否是头插,是头插可以复用前面头插函数

  • 申请新节点准备插入

  • 将NewNode的next改为pos的next,就是让NewNode的指针域指向pos的next

  • 再讲pos的next改为NewNode,就是让pos的指针域指向NewNode请添加图片描述

  • 效果展示:请添加图片描述

  • 代码如下:

void SLTInsertAfter(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
	assert(pphead);
	//空链表插入
	if (*pphead == NULL && pos == NULL)
	{
		SLTPushBack(pphead, x);
		return;
	}
	assert(pos);
	SLTNode* NewNode = CreateListNode(x);
	NewNode->next = pos->next;
	pos->next = NewNode;
}

11.SLTDestory(链表的销毁)

  • 或许有的人会想直接free(*pphead)就算结束了,但这会有问题
  • 这只释放了头节点,后面的节点不释放会造成内容泄露
  • 所以我们需要遍历链表对每个节点进行释放
  • 为此我们需要先创建变量存储下一个节点的地址
  • 因为如果直接先释放我们就找不到下一个节点了
  • 代码如下:
void SLTDestory(SLTNode** pphead) 
{
	assert(pphead);
	SLTNode* cur = *pphead;
	SLTNode* next = *pphead;
	while (cur)
	{
		next = cur->next;
		free(cur);
		cur = next;
	}
	*pphead = NULL;
}

三:菜单的实现

  • 菜单的编写还是十分简单的,对玩家输入的值进行判断进入不同的接口

  • 不再赘述,直接上代码了

  • 效果展示:
    请添加图片描述

  • 代码如下:

#include"SList.h"

void menu()
{
	printf("************************\n");
	printf("***  1.头插 2.尾插******\n");
	printf("***  3.头删 4.尾删******\n");
	printf("***5.在任意位置前插入***\n");
	printf("***6.在任意位置后插入***\n");
	printf("***7.删除任意位置节点***\n");
	printf("***8.修改任意节点的值***\n");
	printf("******9.打印链表********\n");
	printf("***0.销毁链表并退出*****\n");
	printf("************************\n");
}
int main()
{
	menu();
	int input = 0;
	int x = 0;
	SLTNode* plist = NULL;
	do
	{
		printf("请选择> ");
		scanf("%d", &input);
		switch (input)
		{
			case 1:
				printf("请输入你想要头插的值> ");
				scanf("%d", &x);
				SLTPushFront(&plist, x);
				SLTPrint(plist);
				printf("\n");
				break;
			case 2:
				printf("请输入你想要尾插的值> ");
				scanf("%d", &x);
				SLTPushBack(&plist, x);
				SLTPrint(plist);
				printf("\n");
				break;
			case 3:
				SLTPopFront(&plist);
				SLTPrint(plist);
				printf("\n");
				break;
			case 4:
				SLTPopBack(&plist);
				SLTPrint(plist);
				printf("\n");
				break;
			case 5:
			{
				SLTNode* pos = NULL;
				while (1)
				{
					printf("请输入节点的值> ");
					scanf("%d", &x);
					pos = SLTFind(plist, x);
					if (pos == NULL)
					{
						printf("不存在这个节点,请重新输入\n");
					}
					else
					{
						break;
					}
				}
				printf("请输入想要插入的值> ");
				int y = 0;
				scanf("%d", &y);
				SLTInsertBefore(&plist, pos, y);
				SLTPrint(plist);
				printf("\n");
				break;
			}
			case 6:
			{
				SLTNode* pos = NULL;
				while (1)
				{
					printf("请输入节点的值> ");
					scanf("%d", &x);
					pos = SLTFind(plist, x);
					if (pos == NULL)
					{
						printf("不存在这个节点,请重新输入\n");
					}
					else
					{
						break;
					}
				}
				printf("请输入想要插入的值> ");
				int y = 0;
				scanf("%d", &y);
				SLTInsertAfter(&plist, pos, y);
				SLTPrint(plist);
				printf("\n");
				break;
			}
			case 7:
			{
				SLTNode* pos = NULL;
				while (1)
				{
					printf("请输入节点的值> ");
					scanf("%d", &x);
					pos = SLTFind(plist, x);
					if (pos == NULL)
					{
						printf("不存在这个节点,请重新输入\n");
					}
					else
					{
						break;
					}
				}
				SLTErase(&plist, pos);
				SLTPrint(plist);
				printf("\n");
				break;
			}
			case 8:
			{
				SLTNode* pos = NULL;
				while (1)
				{
					printf("请输入节点的值> ");
					scanf("%d", &x);
					pos = SLTFind(plist, x);
					if (pos == NULL)
					{
						printf("不存在这个节点,请重新输入\n");
					}
					else
					{
						break;
					}
				}
				printf("请输入想要修改的值> ");
				int y = 0;
				scanf("%d", &y);
				pos->data = y;
				SLTPrint(plist);
				printf("\n");
				break;
			}
			case 9:
				SLTPrint(plist);
				printf("\n");
				break;
			case 0:
				SLTDestory(&plist);
				break;
			default:
				printf("无效数字,请重新输入");
				break;
		}
	} while (input);
	return 0;
}
  • 9
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

dhdw

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

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

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

打赏作者

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

抵扣说明:

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

余额充值