【详细图解加源代码和测试截图】单链表的一系列操作合集

【详细图解加源代码和测试截图】单链表的一系列操作合集_第1张图片

文章目录

前言

  • 单链表建立和初始化

  • 尾插和尾删

  • 头插和头删

  • 查找

  • 插入

  • 打印链表

  • 删除和销毁链表

SList.h头文件
SList.c文件
text.c测试文件
部分测试截图


前言

单链表虽然不经常使用,但是在找offer的时候很多面试笔试考试都有考单链表的一些操作,虽然本章不直接涉及笔试考题,但都是做单链表考题的基础,很多考题就是在基础上进行变形
所以想找offer学会单链表的基本操作是必不可少的!!

下面我们可以直接看到牛客网和Leecode上单链表的题目就能知道它的重要性

【详细图解加源代码和测试截图】单链表的一系列操作合集_第2张图片【详细图解加源代码和测试截图】单链表的一系列操作合集_第3张图片

下面才是本章的重点开始,先简单的看一个我们最终要写的操作功能

功能菜单>

【详细图解加源代码和测试截图】单链表的一系列操作合集_第4张图片

SList.h为工程头文件(头文件和函数声明)
SList.c为工程的函数实现文件
text.c为工程测试代码文件


单链表建立和初始化

单链表创建有两类型:
第一种是带哨兵位(带头)的链表
第二种是不带哨兵位(不带头)的
【详细图解加源代码和测试截图】单链表的一系列操作合集_第5张图片

带头的链表在函数传参时比较方便
不带头的话函数传参时如果函数没有返回就要修改链表是要传二级指针的,下面所有涉及链表的修改都需要传二级指针,像打印,查找这些不用修改链表的就不用传二级指针
下面图示先带你回忆一下传值和传址的区别,为后文传二级指针做好准备

【详细图解加源代码和测试截图】单链表的一系列操作合集_第6张图片【详细图解加源代码和测试截图】单链表的一系列操作合集_第7张图片

本章使用的是不带哨兵位的链表

单链表节点创建>

typedef int SLDateType;
typedef struct SList
{
	SLDateType data;
	struct SList* next;
}SLNode;

无哨兵位初始化就直接将结构体值空即可

SLNode* list=NULL


尾插和尾删

尾插:唯一要注意的就是链表为空时的尾插方式
代码实现>

//尾插
void SListPushBack(SLNode** pphead,SLDateType x)
{
	//创建新节点
	SLNode* newnode = (SLNode*)malloc(sizeof(SLNode));
	newnode->data = x;
	newnode->next = NULL;

	SLNode* tail = *pphead;
	//如果链表为空,则直接把新的节点给pphead
	if (*pphead==NULL)
	{
		*pphead = newnode;
	}
	else
	{	//找尾
		while (tail->next)
		{
			tail = tail->next;
		}
		
		tail->next = newnode;
	}

}

尾删:要注意的是删之前要把要删的前一个节点地址保存下来,因为单链表无法找到前一个节点,就无法将前一个节点的next置空,这就是为什么往后引出了双链表的原因
代码实现>

//尾删
void SListPopBack(SLNode** pphead)
{
	SLNode* tail = *pphead;
	//prev用来记录tail的前一个节点
	SLNode* prev = NULL;
	//只有一个节点的时候
	if (tail->next==NULL)
	{
		free(tail);
		*pphead = prev;
	}
	//两个及两个以上的节点
	else
	{
		//找尾节点
		while (tail->next)
		{
			prev = tail;
			tail = tail->next;
		}
		//释放尾节点
		free(tail);
		//尾节点前一个节点的next置空
		prev->next = NULL;
	}
}


头插和头删

头插:只需把pphead指向newNode,然后把newNode->next指向原来的头即可,注意的是把pphead指向新节点的前要用cur来存储原来头结点的地址

图示>【详细图解加源代码和测试截图】单链表的一系列操作合集_第8张图片

代码实现>

//头插
void SListPushFront(SLNode** pphead, SLDateType x)
{
	//cur记录头节点位置
	SLNode* cur = *pphead;
	SLNode* newnode = (SLNode*)malloc(sizeof(SLNode));
	newnode->data = x;
	//新节点的next指向头节点
	newnode->next = cur;
	//newnode作为新的头节点
	*pphead = newnode;
}

头删 :头删先也是要把下一个节点地址存储起来,然后free掉头结点,最后把pphead指向下一个节点

图示>【详细图解加源代码和测试截图】单链表的一系列操作合集_第9张图片

代码实现>

//头删
void SListPopFront(SLNode** pphead)
{
	//链表为空就报错
	assert((*pphead) != NULL);
	SLNode* cur = *pphead;
	SLNode* latter = cur->next;	//用latter存储头结点的后一个节点
	free(cur);
	*pphead = latter;	//设置新头节点
}



查找

查找的话,只能遍历链表,我这里就定义成:找到返回x地址,找不到返回NULL

查找功能是和接下来要介绍的插入功能进行搭配使用的

代码实现>

//查找,找到返回x地址,找不到返回NULL
SLNode* SListFind(SLNode* plist, SLDateType x)
{
	SLNode* cur = plist;
	while (cur)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}


插入

插入 :我这定义的是传链表pos节点的指针,然后再它后面插入(当然你也可以自行定义成其他方式)

我这样传指针定义是为了能与查找功能搭配使用,把查找出来的pos位置可以直接传给插入函数,搭配性比较高

【详细图解加源代码和测试截图】单链表的一系列操作合集_第10张图片

//在pos位置后面插入
void SListInsertAfter(SLNode* pos, SLDateType x)
{
	assert(pos != NULL);
	//创建新节点
	SLNode* newnode = (SLNode*)malloc(sizeof(SLNode));
	newnode->data = x;
	newnode->next = NULL;
	//在pos后插入
	SLNode* tem = pos->next;
		pos->next = newnode;
		newnode->next = tem;

}

打印链表

打印链表没有坑,单链表的尾标志就是NULL,直接看代码

//打印链表
void SListPrint(SLNode* phead)
{
	SLNode* cur = phead;
	while (cur)
	{
		printf("%d->", cur->data);
		cur = cur->next;
	}
	printf("NULL\n");
}


删除和销毁链表

删除要注意的是要删pos下一个结点, 所以要把pos下一个节点的下一个节点存储起来防止链表断开

//删除pos位置后一个
void SListEraseAfter(SLNode* pos)
{
	//判断要删的节点是否为空,为空就报错
	assert(pos->next != NULL);
	//存储下一个节点地址
	SLNode* latterTwo = pos->next->next;
	free(pos->next);
	pos->next = latterTwo;

}

销毁就迭代往后逐个释放内存即可
代码实现>

销毁链表
void SListDestroy(SLNode** pphead)
{
	SLNode* cur = *pphead;
	while (cur)
	{
		cur = cur->next;
		free(*pphead);
		*pphead = cur;
	}
}



在这里插入图片描述


SList.h头文件

SList.h为工程头文件(头文件和函数声明)
#pragma once
#include
#include
#include
#include

typedef int SLDateType;
typedef struct SList
{
	SLDateType data;
	struct SList* next;
}SLNode;


//打印链表
void SListPrint(SLNode* phead);

//销毁链表
void SListDestroy(SLNode** pphead);

//尾插
void SListPushBack(SLNode** pphead,SLDateType x);

//尾删
void SListPopBack(SLNode** pphead);

//头插
void SListPushFront(SLNode** pphead, SLDateType x);

//头删
void SListPopFront(SLNode** pphead);

//查找,找到了返回x地址,找不到返回NULL
SLNode* SListFind(SLNode* plist, SLDateType x);

//在pos位置后面插入
void SListInsertAfter(SLNode* pos, SLDateType x);

//删除pos位置后一个
void SListEraseAfter(SLNode* pos);

SList.c

SList.c为工程的函数实现文件
#define _CRT_SECURE_NO_WARNINGS
#include"SList.h"

//打印链表
void SListPrint(SLNode* phead)
{
	SLNode* cur = phead;
	while (cur)
	{
		printf("%d->", cur->data);
		cur = cur->next;
	}
	printf("NULL\n");
}


//销毁链表
void SListDestroy(SLNode** pphead)
{
	SLNode* cur = *pphead;
	while (cur)
	{
		cur = cur->next;
		free(*pphead);
		*pphead = cur;
	}
}


//尾插
void SListPushBack(SLNode** pphead,SLDateType x)
{
	SLNode* newnode = (SLNode*)malloc(sizeof(SLNode));
	newnode->data = x;
	newnode->next = NULL;

	SLNode* tail = *pphead;
	//如果链表为空,则直接把新的节点给pphead
	if (*pphead==NULL)
	{
		*pphead = newnode;
	}
	else
	{	
		while (tail->next)
		{
			tail = tail->next;
		}
		tail->next = newnode;
	}

}


//尾删
void SListPopBack(SLNode** pphead)
{
	SLNode* tail = *pphead;
	//prev用来记录tail的前一个节点
	SLNode* prev = NULL;
	//只有一个节点的时候
	if (tail->next==NULL)
	{
		free(tail);
		*pphead = prev;
	}
	//两个及两个以上的节点
	else
	{
		//找尾节点
		while (tail->next)
		{
			prev = tail;
			tail = tail->next;
		}
		//释放尾节点
		free(tail);
		//尾节点前一个节点的next置空
		prev->next = NULL;
	}
}


//头插
void SListPushFront(SLNode** pphead, SLDateType x)
{
	//cur记录头节点位置
	SLNode* cur = *pphead;
	SLNode* newnode = (SLNode*)malloc(sizeof(SLNode));
	newnode->data = x;
	//新节点的next指向头节点
	newnode->next = cur;
	//newnode作为新的头节点
	*pphead = newnode;
}


//头删
void SListPopFront(SLNode** pphead)
{
	//链表为空就报错
	assert((*pphead) != NULL);
	SLNode* cur = *pphead;
	SLNode* latter = cur->next;	//用latter存储头结点的后一个节点
	free(cur);
	*pphead = latter;	//设置新头节点
}


//查找,找到返回x地址,找不到返回NULL
SLNode* SListFind(SLNode* plist, SLDateType x)
{
	SLNode* cur = plist;
	while (cur)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}

//在pos位置后面插入
void SListInsertAfter(SLNode* pos, SLDateType x)
{
	assert(pos != NULL);
	//创建新节点
	SLNode* newnode = (SLNode*)malloc(sizeof(SLNode));
	newnode->data = x;
	newnode->next = NULL;
	//在pos后插入
	SLNode* tem = pos->next;
		pos->next = newnode;
		newnode->next = tem;

}


//删除pos位置后一个
void SListEraseAfter(SLNode* pos)
{
	//判断要删的节点是否为空,为空就报错
	assert(pos->next != NULL);
	//因为要删pos下一个结点,所以要把pos下一个节点的下一个节点存储起来防止链表断开
	SLNode* latterTwo = pos->next->next;
	free(pos->next);
	pos->next = latterTwo;

}

text.c测试文件

text.c为工程测试代码文件
#define _CRT_SECURE_NO_WARNINGS

#include"SList.h"

void menu()
{
	printf("************************************\n");
	printf("    1.头插   2.头删\n");
	printf("    3.尾插   4.尾删\n");
	printf("    5.打印   6.插入\n");
	printf("    7.查找   8.删除指定位置后一个\n");
	printf("    -1.退出\n");
	printf("************************************\n");
	printf(" 请选择>:   \n");

}

void menuText()
{
	SLNode* list = NULL;
	int input = 0;
	SLDateType x1 = 0;
	while (input != -1)
	{
		menu();
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			printf("请输入你要头插入的数,以-1为结束\n");
			scanf("%d", &x1);
			while (x1 != -1)
			{
				SListPushFront(&list, x1);
				scanf("%d", &x1);
			}
			printf("头插成功\n");
			break;
		case 2:
			SListPopBack(&list);
			printf("头删成功\n");
			break;
		case 3:
			printf("请输入你要尾插入的数,以-1为结束\n");
			scanf("%d", &x1);
			while (x1 != -1)
			{
				SListPushBack(&list, x1);
				scanf("%d", &x1);
			}
			printf("尾插成功\n");
			break;
		case 4:
			SListPopBack(&list);
			printf("尾删成功\n");
			break;
		case 5:
			SListPrint(list);
			break;
		case 6:
			printf("在pos位置后面插入,请输入你在哪个位置后插入\n");
				scanf("%d", &x1);
				SLNode* pos = SListFind(list, x1);
				if (pos != NULL)
				{
					printf("OK,请输入你要插入的数据\n");
					scanf("%d", &x1);
					SListInsertAfter(pos,x1);
					printf("插入成功\n");
				}
				else
				{
					printf("无此数据\n");
				}
			break;
		case 7:
			printf("你要查找哪个数据(pos)>\n");
			scanf("%d", &x1);
			int i = 1;
			pos=SListFind(list, x1);
			if (pos == NULL)
			{
				printf("无此数据\n");
			}
			while (pos)
			{
				printf("第%d个pos节点:%p->%d\n", i++, pos, pos->data);
				pos = SListFind(pos->next, x1);

			}
			break;
		case 8:
			printf("你要删除哪个位置后的数据>\n");
			scanf("%d", &x1);
			pos = SListFind(list, x1);
			if (pos != NULL)
			{
				SListEraseAfter(pos);
				printf("删除成功\n");
			}
			else
			{
				printf("无此数据\n");
			}
			break;
		case -1:
			printf("退出成功\n");
			break;
		default:
			printf("无此选项,请重新输入\n");
			break;
		}
	}
}
int main()
{
	menuText();
}

部分测试截图

尾插和打印>

【详细图解加源代码和测试截图】单链表的一系列操作合集_第11张图片
其他功能请自测!

在这里插入图片描述


你可能感兴趣的:(数据结构系列,数据结构,c语言,visual,studio,链表,后端)