(超详细!)【C语言】单链表的增删查改(附图解,源码)

单链表学习导航

一.前言

二.准备工作

1.对单链表运行原理的简单理解

2.区域化编辑

 三.SList.h头文件引用区

1.单链表节点的创建

2.单链表功能函数的声明

四.SListTest.c测试区

五.SList.c功能实现区

1.动态申请一个节点

2.单链表打印

3.单链表尾插

4.单链表头插

5.单链表尾删

6.单链表头删

7.单链表查找

8.单链表在pos(指定)位置之后插入

9.单链表在pos(指定)位置之后删除

10.销毁单链表

六.源码

1.SList.h源码

2.SList.c源码

 七.结语


一.前言

本文为我对链表学习的过程与理解,适合新手学习交流。内容如题,我将结合画图和截图带领大家更好的理解单链表结构,学习和使用它增删查改的功能。(VS2019编译器所展示)

二.准备工作

1.对单链表运行原理的简单理解

 如上图,单链表是通过结构体里面放置一个新的结构体指针的形式,使用下一个结构体的地址来找到该结构体中的数据的,链表相比于顺序表具有存储数据不连续,方便修改的特点。但单链表的缺点也很明显,只能从第一个节点开始遍历,比较局限。

2.区域化编辑

此目的是为了更好的理解代码,在前文中我也提到过,但模块化看起来清晰更有利于我们理解以及使用、修改代码。(还有美观...)

 三.SList.h头文件引用区

1.单链表节点的创建

在该文件中,我们将链表结构体定义,设置int为链表中数据的存储形式(方便展示),同时节点中增加下一个节点的地址。重定义链表方便使用该结构体。

#define SLTDateType int

typedef struct SListNode
{
	SLTDateType Data;
	struct SListNode* Next;
}SListNode;

2.单链表功能函数的声明

在头文件中声明这些函数后直接使用即可,先列出来目标,后面咱们一一实现。

// 动态申请一个节点
SListNode* BuySListNode(SLTDateType x);

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

// 单链表尾插
void SListPushBack(SListNode** pplist, SLTDateType x);

// 单链表的头插
void SListPushFront(SListNode** pplist, SLTDateType x);

// 单链表的尾删
void SListPopBack(SListNode** pplist);

// 单链表头删
void SListPopFront(SListNode** pplist);

// 单链表查找
SListNode* SListFind(SListNode* plist, SLTDateType x);

// 单链表在pos位置之后插入x
void SListInsertAfter(SListNode* pos, SLTDateType x);

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

// 单链表的销毁
void SListDestroy(SListNode* plist);

其中,有的函数功能传入的参数为二级指针,为什么呢?因为函数传入的参数是实参的一份临时拷贝,如果传入的是一级指针,那么我们可以改变这个指针指向的数据,而如果我们想要改变这个指针的地址呢?同样的道理,我们需要一个指向这个指针的地址的指针,也就是二级指针。

四.SListTest.c测试区

该区域主要为所写功能函数的测试模块,以此来判断函数的正确性、是否完善。

int main()
{
	SListNode* List = NULL;

	SListTest1(List);
	SListTest2(List);
	SListTest3(List);
	SListTest4(List);
	SListTest5(List);
	SListTest6(List);
	SListTest7(List);

	return 0;
}

类似于菜单,不过少了些富丽堂皇的界面,因为写了菜单之后很可能会影响到我们调试的效率,将功能函数实现并调试完成之后,再完成菜单界面是个更好的选择。测试区域可以自行定义,我也将在下文中结合该区域来展示函数的实现效果。

五.SList.c功能实现区

1.动态申请一个节点

链表节点的申请可以使用malloc和calloc函数,此处我们使用malloc,记得判断返回值是否为空。

//动态申请链表节点
SListNode* BuySListNode(SLTDateType x)
{
	SListNode* newnode = (SListNode*)malloc(sizeof(SListNode));
	if (newnode == NULL)
	{
		perror("BuySListNode::malloc");
		return;
	}
	newnode->Data = x;
	return newnode;
}

此处还可以在返回节点之前将该节点的下一个地址置空,初始化也是种不错的选择。可以看个人需求选择。

2.单链表打印

将第一个节点传入函数,通过节点中的下一个指针一一遍历每个节点中存储的数据,并打印出来,最后节点为空时停止即可。

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

3.单链表尾插

尾插需要考虑两种情况,第一种则是链表为空的情况,这种情况直接让该链表指向开辟的节点上即可;第二种情况为非空的情况,这种情况比较复杂,我们来画图解决:

 地址都是瞎编的,原理如此,代码实现如下:

//尾插
void SListPushBack(SListNode** pplist, SLTDateType x)
{
    //判断是否为空链表
	if (*pplist==NULL)
	{
		*pplist = BuySListNode(x);
		(*pplist)->Next = NULL;
	}
	else
	{
		//查找尾部
		SListNode* ptr = *pplist;
		while (ptr->Next)
		{
			ptr = ptr->Next;
		}
		//插入
		ptr->Next = BuySListNode(x);
		ptr->Next->Next = NULL;
	}
}

结合打印链表函数效果如图:

4.单链表头插

头插的实现和尾插一样,分空链表和非空链表两种情况。二者区别在于非空情况,头插演示如图:

原来没有头插的非空链表,pplist指向头节点的地址。

插入头节点的非空链表演示如上图,代码实现如下图。

//头插
void SListPushFront(SListNode** pplist, SLTDateType x)
{
	if (*pplist == NULL)
	{
		*pplist = BuySListNode(x);
		(*pplist)->Next = NULL;
	}
	else
	{
		SListNode* ptr = BuySListNode(x);
		ptr->Next = *pplist;
		*pplist = ptr;
	}
}

实现效果如图:

5.单链表尾删

尾删分为三种情况:

第一种为链表是否为空,若为空,可以选择报错也可以选择直接return(没有内容咋删...);

第二种为链表不为空,但只有一个节点的情况,这种情况我们需要直接将链表置空;

第三中为多节点的情况,这时我们要将NULL节点之前的节点置空,其中需要NULL节点前前个节点来判断,因为还要free掉不要用的内存,该情况如下图所示。

具体实现如下:

//尾删
void SListPopBack(SListNode** pplist)
{
	assert(pplist);
	assert(*pplist);
	
	if ((*pplist)->Next == NULL)
	{
		//单个链表块
		free(*pplist);
		*pplist = NULL;
	}
	else
	{
		//2个及以上链表块
		SListNode* ptr = *pplist;
		while (ptr->Next->Next)
		{
			ptr = ptr->Next;
		}
		free(ptr->Next);
		ptr->Next = NULL;
	}
}

效果如下:

6.单链表头删

头删只需要考虑两种情况就行了。

第一种为判断链表是否为空;

第二种不论是否为多节点,指向头节点地址的指针都会向后移动到下一个节点的地址上,因此不需要考虑这下一个节点是否为空。

 代码实现如下:

//头删
void SListPopFront(SListNode** pplist)
{
	assert(pplist);
	assert(*pplist);

	SListNode* Del = *pplist;
	*pplist = (*pplist)->Next;
	free(Del);
	Del = NULL;
}

具体效果如图:

7.单链表查找

查找目标节点并返回该节点的地址,传值选用一级指针就行了,因为有返回值为一级指针,这类函数的方法也可以代替上面的二级指针的使用(个人觉得这样不好看)。

查找需要遍历,除非找到目标节点,否则到NULL为止停下,直接返回查找节点即可。

//查找
SListNode* SListFind(SListNode* plist, SLTDateType x)
{
	SListNode* find = plist;
	while (find && find->Data != x)
		find = find->Next;
	return find;
}

实现效果如图:

8.单链表在pos(指定)位置之后插入

这个函数和头插函数多节点的情况很像,不过该函数不需要改变头节点指针的位置,因此传入一级指针即可。

插入前需判断是否传入的为空指针,然后插入就行啦,不过写入的顺序要正确,否则可能会丢失原先的地址哦?

//在pos位置之后插入x
void SListInsertAfter(SListNode* pos, SLTDateType x)
{
	assert(pos);
	SListNode* next = pos->Next;

	SListNode* newnode = BuySListNode(x);
	pos->Next = newnode;
	newnode->Next = next;
}

效果如下:

9.单链表在pos(指定)位置之后删除

这个函数还比较简单,注意传值是否为空指针和指向的下一节点是否为空就行了。

没有判断后者的话可是会非法访问的...不得不说链表对于初学者而言坑太多了。

//删除pos位置之后的值
void SListEraseAfter(SListNode* pos)
{
	assert(pos);
	if (pos->Next == NULL)
		return;
	else
	{
		SListNode* NNext = pos->Next->Next;
		free(pos->Next);
		pos->Next = NNext;
	}
}

实现效果如下:

10.销毁单链表

类似于顺序表的销毁,不同的是需要free掉前者,且遍历的是链表节点的指针。

//销毁
void SListDestroy(SListNode* plist)
{
	assert(plist);
	SListNode* Del = plist;
	while (Del)
	{
		plist = plist->Next;
		free(Del);
		Del = plist;
	}
}

六.源码

1.SList.h源码

#pragma once

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

#define int SLTDateType 

typedef struct SListNode
{
	SLTDateType Data;
	struct SListNode* Next;
}SListNode;

// 动态申请一个节点
SListNode* BuySListNode(SLTDateType x);

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

// 单链表尾插
void SListPushBack(SListNode** pplist, SLTDateType x);

// 单链表的头插
void SListPushFront(SListNode** pplist, SLTDateType x);

// 单链表的尾删
void SListPopBack(SListNode** pplist);

// 单链表头删
void SListPopFront(SListNode** pplist);

// 单链表查找
SListNode* SListFind(SListNode* plist, SLTDateType x);

// 单链表在pos位置之后插入x
void SListInsertAfter(SListNode* pos, SLTDateType x);

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

// 单链表的销毁
void SListDestroy(SListNode* plist);

2.SList.c源码

#include "SList.h"

//动态申请链表节点
SListNode* BuySListNode(SLTDateType x)
{
	SListNode* newnode = (SListNode*)malloc(sizeof(SListNode));
	if (newnode == NULL)
	{
		perror("BuySListNode::malloc");
		return;
	}
	newnode->Data = x;
	return newnode;
}

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

//尾插
void SListPushBack(SListNode** pplist, SLTDateType x)
{
	if (*pplist==NULL)
	{
		*pplist = BuySListNode(x);
		(*pplist)->Next = NULL;
	}
	else
	{
		//查找尾部
		SListNode* ptr = *pplist;
		while (ptr->Next)
		{
			ptr = ptr->Next;
		}
		//插入
		ptr->Next = BuySListNode(x);
		ptr->Next->Next = NULL;
	}
}

//头插
void SListPushFront(SListNode** pplist, SLTDateType x)
{
	assert(pplist);
	if (*pplist == NULL)
	{
		*pplist = BuySListNode(x);
		(*pplist)->Next = NULL;
	}
	else
	{
		SListNode* ptr = BuySListNode(x);
		ptr->Next = *pplist;
		*pplist = ptr;
	}
}

//尾删
void SListPopBack(SListNode** pplist)
{
	assert(pplist);
	assert(*pplist);
	
	if ((*pplist)->Next == NULL)
	{
		//单个链表块
		free(*pplist);
		*pplist = NULL;
	}
	else
	{
		//2个及以上链表块
		SListNode* ptr = *pplist;
		while (ptr->Next->Next)
		{
			ptr = ptr->Next;
		}
		free(ptr->Next);
		ptr->Next = NULL;
	}
}

//头删
void SListPopFront(SListNode** pplist)
{
	assert(pplist);
	assert(*pplist);

	SListNode* Del = *pplist;
	*pplist = (*pplist)->Next;
	free(Del);
	Del = NULL;
}

//查找
SListNode* SListFind(SListNode* plist, SLTDateType x)
{
	SListNode* find = plist;
	while (find && find->Data != x)
		find = find->Next;
	return find;
}

//在pos位置之后插入x
void SListInsertAfter(SListNode* pos, SLTDateType x)
{
	assert(pos);
	SListNode* next = pos->Next;

	SListNode* newnode = BuySListNode(x);
	pos->Next = newnode;
	newnode->Next = next;
}

//删除pos位置之后的值
void SListEraseAfter(SListNode* pos)
{
	assert(pos);
	if (pos->Next == NULL)
		return;
	else
	{
		SListNode* NNext = pos->Next->Next;
		free(pos->Next);
		pos->Next = NNext;
	}
}

//销毁
void SListDestroy(SListNode* plist)
{
	assert(plist);
	SListNode* Del = plist;
	while (Del)
	{
		plist = plist->Next;
		free(Del);
		Del = plist;
	}
}

 七.结语

链表内容对于新手而言还是比较多的,坑也是无处不在,需要诸位的细心与耐心,除了单链表还有很多其他形式的,不过都大同小异,自己多手搓几遍就能弄清楚了,总之,我会和各位一起加油的!

猜你喜欢

转载自blog.csdn.net/qq_74641564/article/details/129331143
今日推荐