【数据结构】单链表的实现

目录

[1 链表的概念及结构](#1 链表的概念及结构)

[2 链表的增删改查实现](#2 链表的增删改查实现)

[2.1 定义节点的结构](#2.1 定义节点的结构)

[2.2 申请一个新节点](#2.2 申请一个新节点)

[2.3 链表打印](#2.3 链表打印)

[2.4 链表的尾插](#2.4 链表的尾插)

[2.5 链表的头插](#2.5 链表的头插)

[2.6 链表的尾删](#2.6 链表的尾删)

[2.7 链表的头删](#2.7 链表的头删)

[2.8 链表数据的查找](#2.8 链表数据的查找)

[2.9 在指定位置之前插入数据](#2.9 在指定位置之前插入数据)

[2.10 在指定位置之后插入数据](#2.10 在指定位置之后插入数据)

[2.11 删除指定位置的节点](#2.11 删除指定位置的节点)

[2.12 删除pos之后的节点](#2.12 删除pos之后的节点)

[2.13 链表的销毁](#2.13 链表的销毁)

[3 链表实现完整代码](#3 链表实现完整代码)

[3.1 SList.h文件](#3.1 SList.h文件)

[3.2 SList.c文件](#3.2 SList.c文件)

[3.3 test.c文件](#3.3 test.c文件)


1 链表的概念及结构

链表是一种物理存储上非连续 、非顺序的存储结构。数据元素的逻辑顺序 是通过链表中的指针链接次序 实现的。链表是线性表的一种。链表在物理结构上(实际)不是线性的,逻辑结构上(逻辑层面)是线性的。

与顺序表不同的是,链表里的每个"模块"都是独立申请下来的空间,我们称之为"节点"。

节点有两个组成部分:当前节点要保存的数据(1、2、3...)和保存下一个节点的地址(指针变量)。图中指针变量plist保存的是第一个节点的地址,我们称plist此时指向第一个节点,如果我们希望plist指向第二个节点时,只需要修改plist保存的内容为0x0012FFA0。每一个节点都是独立申请的,我们需要通过指针变量来保存下一个节点的位置,才能从当前节点找到下一个节点。

2 链表的增删改查实现

2.1 定义节点的结构

根据结构体来定义节点的结构:

复制代码
//定义节点的结构
//数据+指向下一个节点的指针
typedef int SLTDataType;//方便修改数据类型

typedef struct SListNode//定义节点
{
	SLTDataType data;
	struct SListNode* next;
}SLTNode;

假设当我们想要通过链表来保存整型数据时,实际是先向操作系统申请一块内存,这块内存不仅要保存整型数据,也需要保存下一个节点的地址。

2.2 申请一个新节点

复制代码
//申请一个新节点
SLTNode* SLTBuyNode(SLTDataType x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	if (newnode == NULL)
	{
		perror("malloc");
		exit(1);
	}
	newnode->data = x;
	newnode->next = NULL;
	return newnode;
}

当我们申请一个新节点时,利用malloc去开辟一个节点大小的空间,然后将数据赋值为x,当前节点储存下一个节点的地址初始化为NULL。

2.3 链表打印

打印链表的所有数据时,定义一个临时变量pcur来储存链表头节点的地址,然后打印当前节点的数据 ,然后走到下一个节点,直到打印完为止,代码如下:

复制代码
//链表打印
void SLTPrint(SLTNode* phead)
{
	SLTNode* pcur = phead;
	while (pcur)
	{
		printf("%d->", pcur->data);
		pcur = pcur->next;
	}
	printf("NULL\n");
}

2.4 链表的尾插

链表的尾插就是在当前链表最后一个节点处插入一个新的节点。

尾插函数的参数要进行传址调用,原因在于要实际改变链表的数据时,就需要传地址。想要改变节点,就要创建一个结构体指针变量,例如SLTNode* plist = NULL;调用尾插函数时,就要传递一级指针变量的地址,函数的参数自然就需要二级指针来接收 。后续的不同插入和删除操作函数的参数都需要二级指针,代码如下:

复制代码
//链表尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);
	SLTNode* newnode = SLTBuyNode(x);//创建新节点
	if (*pphead == NULL)
	{
		*pphead = newnode;
	}
	else
	{
		//找尾
		SLTNode* ptail = *pphead;
		while (ptail->next)
		{
			ptail = ptail->next;
		}
		ptail->next = newnode;
	}
}

2.5 链表的头插

链表的头插就是在链表第一个节点前插入一个新的节点。

代码如下:

复制代码
//链表头插
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);
	SLTNode* newnode = SLTBuyNode(x);//创建新节点
	newnode->next = *pphead;
	*pphead = newnode;
}

2.6 链表的尾删

链表的尾删就是删除链表的最后一个节点,这里需要两种情况讨论:如果链表只有一个节点,直接释放这个节点的地址,如果有两个或两个以上的节点,释放掉最后一个节点后,倒数第二个节点因为存储着最后一个节点的地址,它存储的地址也要置为NULL。

代码如下:

复制代码
//链表尾删
void SLTPopBack(SLTNode** pphead)
{
	assert(pphead&&*pphead);
	if ((*pphead)->next == NULL)//链表如果只有一个节点
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		SLTNode* ptail = *pphead;
		SLTNode* prev = *pphead;
		while (ptail->next)
		{
			prev = ptail;
			ptail = ptail->next;
		}
		free(ptail);
		ptail = NULL;
		prev->next = NULL;
	}
}

2.7 链表的头删

链表的头删是删除链表的第一个节点,起始节点的地址要指向第二个节点的地址。

代码如下:

复制代码
//链表头删
void SLTPopFront(SLTNode** pphead)
{
	assert(pphead && *pphead);
	SLTNode* ptail = *pphead;
	SLTNode* next = (*pphead)->next;
	free(ptail);
	ptail = NULL;
	*pphead = next;
}

2.8 链表数据的查找

复制代码
//链表查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{
	SLTNode* pcur = phead;
	while (pcur)
	{
		if (pcur->data == x)
		{
			return pcur;
		}
		else
		{
			pcur = pcur->next;
		}
	}
	//找不到
	return NULL;
}

2.9 在指定位置之前插入数据

在指定节点之前插入一个新的节点,找到pos节点及其之前的节点prev,在两个节点之间插入新的节点。

代码如下:

复制代码
//在指定位置之前插入数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
	assert(pphead && *pphead);
	assert(pos);
	SLTNode* prev = *pphead;
	if (*pphead == pos)
	{
		SLTPushFront(pphead, x);
	}
	else
	{
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		SLTNode* newnode = SLTBuyNode(x);	
		prev->next = newnode;
		newnode->next = pos;

	}
}

2.10 在指定位置之后插入数据

在指定位置之后(pos)插入新的节点,新节点存储pos下一个节点的地址。

代码如下:

复制代码
//在指定位置之后插入数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{
	assert(pos);
	SLTNode* newnode = SLTBuyNode(x);
	newnode->next = pos->next;
	pos->next = newnode;
}

2.11 删除指定位置的节点

如果链表只有一个节点,直接调用头删函数;链表有多个节点,找到pos之前的节点和pos之后的节点,将它们连接起来。

代码如下:

复制代码
/删除pos节点
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
	assert(pphead && *pphead);
	SLTNode* prev = *pphead;
	if (pos = *pphead)
	{
		//头删
		SLTPopFront(pphead);
	}
	else
	{
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		prev->next = pos->next;
		free(pos);
		pos = NULL;
	}
}

2.12 删除pos之后的节点

创建一个临时标量del,提前存储好pos下一个节点的地址,然后先将pos存储好del后的地址,再释放del。

代码如下:

复制代码
//删除pos之后的节点
void SLTEraseAfter(SLTNode* pos)
{
	assert(pos && pos->next);
	SLTNode* del = pos->next;
	pos->next = pos->next->next;
	free(del);
	del = NULL;
}

2.13 链表的销毁

链表使用完后要进行销毁,将空间还给操作系统:

复制代码
//销毁链表
void SListDesTroy(SLTNode** pphead)
{
	assert(pphead && *pphead);
	SLTNode* pcur = *pphead;
	while (pcur)
	{
		SLTNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	*pphead = NULL;
}

3 链表实现完整代码

从头创建链表,并实现对链表的增删改查实现,我们需要创建一个头文件:SList.h,一个源文件SList.h,用来实现增删改查功能,一个测试文件test.c用来测试代码功能:

3.1 SList.h文件

复制代码
#pragma once

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

//定义节点的结构
//数据+指向下一个节点的指针
typedef int SLTDataType;//方便修改数据类型

typedef struct SListNode//定义节点
{
	SLTDataType data;
	struct SListNode* next;
}SLTNode;

//申请一个新节点
SLTNode* SLTBuyNode(SLTDataType x);
//打印链表
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 SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);
//删除pos节点
void SLTErase(SLTNode** pphead, SLTNode* pos);
//在指定位置之后插入数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x);
//删除pos之后的节点
void SLTEraseAfter(SLTNode* pos);
//销毁链表
void SListDesTroy(SLTNode** pphead);

3.2 SList.c文件

复制代码
#include "SList.h"

//申请一个新节点
SLTNode* SLTBuyNode(SLTDataType x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	if (newnode == NULL)
	{
		perror("malloc");
		exit(1);
	}
	newnode->data = x;
	newnode->next = NULL;
	return newnode;
}
//链表打印
void SLTPrint(SLTNode* phead)
{
	SLTNode* pcur = phead;
	while (pcur)
	{
		printf("%d->", pcur->data);
		pcur = pcur->next;
	}
	printf("NULL\n");
}
//链表尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);
	SLTNode* newnode = SLTBuyNode(x);//创建新节点
	if (*pphead == NULL)
	{
		*pphead = newnode;
	}
	else
	{
		//找尾
		SLTNode* ptail = *pphead;
		while (ptail->next)
		{
			ptail = ptail->next;
		}
		ptail->next = newnode;
	}
}
//链表头插
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);
	SLTNode* newnode = SLTBuyNode(x);//创建新节点
	newnode->next = *pphead;
	*pphead = newnode;
}
//链表尾删
void SLTPopBack(SLTNode** pphead)
{
	assert(pphead&&*pphead);
	if ((*pphead)->next == NULL)//链表如果只有一个节点
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		SLTNode* ptail = *pphead;
		SLTNode* prev = *pphead;
		while (ptail->next)
		{
			prev = ptail;
			ptail = ptail->next;
		}
		free(ptail);
		ptail = NULL;
		prev->next = NULL;
	}
}
//链表头删
void SLTPopFront(SLTNode** pphead)
{
	assert(pphead && *pphead);
	SLTNode* ptail = *pphead;
	SLTNode* next = (*pphead)->next;
	free(ptail);
	ptail = NULL;
	*pphead = next;
}
//链表查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{
	SLTNode* pcur = phead;
	while (pcur)
	{
		if (pcur->data == x)
		{
			return pcur;
		}
		else
		{
			pcur = pcur->next;
		}
	}
	//找不到
	return NULL;
}
//在指定位置之前插入数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
	assert(pphead && *pphead);
	assert(pos);
	SLTNode* prev = *pphead;
	if (*pphead == pos)
	{
		SLTPushFront(pphead, x);
	}
	else
	{
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		SLTNode* newnode = SLTBuyNode(x);	
		prev->next = newnode;
		newnode->next = pos;

	}
}
//在指定位置之后插入数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{
	assert(pos);
	SLTNode* newnode = SLTBuyNode(x);
	newnode->next = pos->next;
	pos->next = newnode;
}
//删除pos节点
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
	assert(pphead && *pphead);
	SLTNode* prev = *pphead;
	if (pos = *pphead)
	{
		//头删
		SLTPopFront(pphead);
	}
	else
	{
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		prev->next = pos->next;
		free(pos);
		pos = NULL;
	}
}

//删除pos之后的节点
void SLTEraseAfter(SLTNode* pos)
{
	assert(pos && pos->next);
	SLTNode* del = pos->next;
	pos->next = pos->next->next;
	free(del);
	del = NULL;
}

//销毁链表
void SListDesTroy(SLTNode** pphead)
{
	assert(pphead && *pphead);
	SLTNode* pcur = *pphead;
	while (pcur)
	{
		SLTNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	*pphead = NULL;
}

3.3 test.c文件

复制代码
#include "SList.h"
void SListTest01()
{
	//创建几个链表的节点测试
	SLTNode* node1=SLTBuyNode(1);
	SLTNode* node2=SLTBuyNode(2);
	SLTNode* node3=SLTBuyNode(3);
	SLTNode* node4=SLTBuyNode(4);

	//将节点链接起来
	node1->next = node2;
	node2->next = node3;
	node3->next = node4;
	node4->next = NULL;

	//链表打印测试
	/*SLTNode* plist = node1;
	SLTPrint(plist);*/

}
void SListTest02()
{
	//链表尾插测试
	SLTNode* plist = NULL;
	SLTPushBack(&plist, 1);
	SLTPushBack(&plist, 2);
	SLTPushBack(&plist, 3);
	SLTPushBack(&plist, 4);
	SLTPrint(plist);

	//链表头插测试
	//SLTPushFront(&plist, 5);
	//SLTPushFront(&plist, 6);
	//SLTPushFront(&plist, 7);
	//SLTPushFront(&plist, 8);
	//SLTPrint(plist);

	//链表尾删测试
	//SLTPopBack(&plist);
	//SLTPrint(plist);
	//SLTPopBack(&plist);
	//SLTPrint(plist);
	//SLTPopBack(&plist);
	//SLTPrint(plist);
	//SLTPopBack(&plist);
	//SLTPrint(plist);

	//链表头删测试
	//SLTPopFront(&plist);
	//SLTPrint(plist);
	//SLTPopFront(&plist);
	//SLTPrint(plist);
	//SLTPopFront(&plist);
	//SLTPrint(plist);
	//SLTPopFront(&plist);
	//SLTPrint(plist);


	//链表查找测试
	/*SLTNode* find=SLTFind(plist, 3);
	if (find == NULL)
	{
		printf("找不到\n");
	}
	else
	{
		printf("找到了\n");
	}*/

	//在指定位置之前插入数据测试
	//SLTNode* find = SLTFind(plist, 1);
	//SLTInsert(&plist,find, 11);
	//SLTPrint(plist);

	//在指定位置之后插入数据测试
	//SLTNode* find = SLTFind(plist, 1);
	//SLTInsertAfter(find, 11);
	//SLTPrint(plist);

	//删除pos节点测试
	//SLTNode* find = SLTFind(plist, 1);
	//SLTErase(&plist, find);
	//SLTPrint(plist);
	//find = SLTFind(plist, 2);
	//SLTErase(&plist, find);
	//SLTPrint(plist);
	//find = SLTFind(plist, 3);
	//SLTErase(&plist, find);
	//SLTPrint(plist);
	//find = SLTFind(plist, 4);
	//SLTErase(&plist, find);
	//SLTPrint(plist);
	//find = SLTFind(plist, 5);
	//SLTErase(&plist, find);
	//SLTPrint(plist);

	//删除pos之后的节点测试
	/*SLTNode* find = SLTFind(plist, 2);
	SLTEraseAfter(find);
	SLTPrint(plist);*/

	//销毁链表测试
	SListDesTroy(&plist);

}
int main()
{
	SListTest01();
	SListTest02();
	return 0;
}

以上就是有关单链表的所有内容了,如果这篇文章对你有用,可以点点赞哦,你的支持就是我写下去的动力,后续会不断地分享知识。

相关推荐
Ashlee_code5 小时前
裂变时刻:全球关税重构下的券商交易系统跃迁路线图(2025-2027)
java·大数据·数据结构·python·云原生·区块链·perl
闻缺陷则喜何志丹6 小时前
【带权的并集查找】 P9235 [蓝桥杯 2023 省 A] 网络稳定性|省选-
数据结构·c++·蓝桥杯·洛谷·并集查找
jie*6 小时前
python(one day)——春水碧于天,画船听雨眠。
开发语言·数据结构·python·算法·线性回归
草莓熊Lotso8 小时前
【LeetCode刷题指南】--数组串联,合并两个有序数组,删除有序数组中的重复项
c语言·数据结构·其他·刷题
weixin_419658318 小时前
数据结构之B-树
java·数据结构·b树
H_HX_xL_L8 小时前
数据结构的算法分析与线性表<1>
数据结构·算法
overFitBrain8 小时前
数据结构-2(链表)
数据结构
xienda8 小时前
数据结构排序算法总结(C语言实现)
数据结构·算法·排序算法
科大饭桶8 小时前
数据结构自学Day8: 堆的排序以及TopK问题
数据结构·c++·算法·leetcode·二叉树·c
拾光Ծ8 小时前
【数据结构】 链表 + 手动实现单链表和双链表的接口(图文并茂附完整源码)
数据结构