数据结构(初阶)(三)----单链表

单链表

概念

概念:链表是⼀种物理存储结构上⾮连续、⾮顺序 的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。

结点

与顺序表不同的是,链表的结构类似于带车头的火车车厢,,链表的每个车厢都是独立申请下来的空间,每个车厢被叫做结点。

结点由当前结点要保存的数据保存下一个节点的地址(指针变量)。

图中指针变量 plist保存的是第⼀个结点的地址,我们称plist此时"指向"第⼀个结点,如果我们希望plist"指向"第⼆个结点时,只需要修改plist保存的内容为0x0012FFc8

链表中每个结点都是独⽴申请的(即需要插⼊数据时才去申请⼀块结点的空间),我们需要通过指针 变量来保存下⼀个结点位置才能从当前结点找到下⼀个结点

链表的性质

链表在逻辑上是连续的 ,在物理结构上不一定连续

结点一般是从堆上申请的

从堆上申请来的空间,是按照⼀定策略分配出来的,每次申请的空间可能连续,可能不连续

定义链表的结构实际上就是定义链表中结点结构

每个结点对应的结构体代码:

c 复制代码
typedef int SLTDataType;//因为我们在事先并不确定数据的类型,所以需要定义一下
typedef truct SListNode
{
    SLTDataType data;//结点要保存的数据
    struct SListNode* next;//指向下一个结点的指针,定义的数据类型是结点类型
}SLTNode;//定义全局变量

当我们想要保存一个数据时,实际上是向操作系统申请一块内存,这块内存不仅要保存数据,也要保存指向下一个结点的地址(当下一个结点为NULL时,地址为NULL)

当我们想要从第一个节点走向最后一个结点时,只需要在当前结点拿上下⼀个结点的地址就可以了。

链表的打印

c 复制代码
void SLTPrint(SLTNode* phead)
{
	SLTNode* pcur = phead;//pcur存储的是当前的结点
	while (pcur)//等价于pcur != NULL
	{
		printf("%d-> ",pcur->data);
		pcur = pcur->next;
	}
	//走到这里,说明pcur == NULL,那么直接打印
	printf("NULL\n");
}

单链表的实现

创建三个文件分别是SList.h和SList.c和test.c

SList.h用来包含所需头文件,声明函数

SList.c用来定义函数,实现方法

test.c用来测试我们想要实现的功能

这里使用的是VS2022 win11环境

手动构造链表

SList.h
c 复制代码
#pragma once
#include<stdio.h>
#include<stdlib.h>

typedef int SLTDataType;//定义数据类型
typedef struct SListNode
{
	SLTDataType data;
	//定义下一个结点的地址,类型是struct SListNode
	struct SListNode* next;
}SLTNode;//定义全局变量

//打印链表
void SListPrint(SLTNode* phead);
SList.c
c 复制代码
#define _CRT_SECURE_NO_WARNINGS
#include"SList.h"

//打印链表
void SListPrint(SLTNode* phead)
{
	//创建pcur存储phead的地址,直接用phead也可以
    //这里是因为再次使用第一个结点时,还可以找到,不会影响后续的使用
	SLTNode* pcur = phead;
	//判断当前结点是否为NULL,等价于pcur != NULL
	while (pcur)
	{
		//结点不为NULL,打印当前节点数据
		printf("%d -> ",pcur->data);
		//将下一个结点的地址赋值给pcur
		pcur = pcur->next;
	}
	//走到这里说明pcur == NULL,直接打印NULL
	printf("NULL\n");
}
test.c
c 复制代码
#define _CRT_SECURE_NO_WARNINGS
#include"SList.h"

//手动构造链表
void test1()
{
	//为结点申请内存,大小为结构体大小
	//node1来接收,类型为SLTNode*,其他同理
	SLTNode* node1 = (SLTNode*)malloc(sizeof(SLTNode));
	SLTNode* node2 = (SLTNode*)malloc(sizeof(SLTNode));
	SLTNode* node3 = (SLTNode*)malloc(sizeof(SLTNode));
	SLTNode* node4 = (SLTNode*)malloc(sizeof(SLTNode));

	//在结点中放入要存储的数据
	node1->data = 1;
	node2->data = 2;
	node3->data = 3;
	node4->data = 4;

	//在结点中放入指向下一个结点的地址,如果是尾结点,则为NULL
	node1->next = node2;
	node2->next = node3;
	node3->next = node4;
	node4->next = NULL;

	//打印链表看看效果
	//将首结点的地址给plist,传给实现打印的函数
	SLTNode* plist = node1;
	SListPrint(plist);
}

int main()
{
	test1();

	return 0;
}

单链表实现

尾插

c 复制代码
//向操作系统申请一个新结点
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;
}


//尾插
//其实在链表为NULL的情况下,不需要再改变头结点的内容,所以也可以使用SLTNode* pphead
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
	//向操作系统申请一个新结点
	SLTNode* newnode = SLTBuyNode(x);
	//链表为空,phead直接指向newNode
	if (*pphead == NULL)
	{
		*pphead = newnode;
	}
	//链表不为空,找尾节点,将尾节点和新节点连接起来
	else
	{
		SLTNode* ptail = *pphead;
		while (ptail->next != NULL)
		{
			ptail = ptail->next;
		}
		//此时有:ptail->next == NULL
		ptail->next = newnode;
	}
}

尾删

普遍情况是有多个结点,思路是:

1,找到尾结点

2,将尾结点的前驱结点置为空,再将尾结点释放并置空

特殊情况是,只有一个结点,思路是:

将唯一一个结点,也就是头结点释放并置空

c 复制代码
//尾删
void SLTPopBack(SLTNode** phead)
{
    //二级指针不为空,链表不为空
	assert(phead && *phead);
	//在只有一个结点的情况下,prev = NULL,所以在下面对空指针的操作就是不合法的,
	//所以此时要分离出来,特殊处理,之后我们再测试assert断言就发挥了作用
	//注意*的优先级是低于->的,所以需要加上括号
	if ((*phead)->next == NULL)
	{
		free(*phead);
		*phead = NULL;
	}
	//此时是结点个数大于一的情况
	else 
	{
		SLTNode* prev = NULL;
		SLTNode* ptail = *phead;

		while (ptail->next)
		{
			prev = ptail;
			ptail = ptail->next;
		}
		prev->next = NULL;
		free(ptail);
		ptail = NULL;
	}
}

头插

c 复制代码
//头插
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
	SLTNode* newnode = SLTBuyNode(x);

	newnode->next = *pphead;
	*pphead = newnode;
}

头删

头删时,只需要将头结点的next结点保存起来,然后将原来的头结点释放,最后将next结点变成新的头结点即可。

c 复制代码
//头删
void SLTPopFront(SLTNode** phead)
{
	assert(phead && *phead);
	//经过分析,在只有一个结点和多个结点的情况时,都是符合预期的,所以不必分离讨论
	SLTNode* next = (*phead)->next;
	free(*phead);
	*phead = next;

}

查找

如果只是在链表中查找数据,那么就不会修改,只需传一级指针和要查找的数据,

而返回值则是所查找到的结点

c 复制代码
//查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{
	//创建pcur来遍历链表
	SLTNode* pcur = phead;
	//当结点不为空,进入循环
	while (pcur)
	{
		if (pcur->data == x)
		{
			return pcur;
		}
		pcur = pcur->next;
	}
	//没有找到
	return NULL;
}

指定位置pos之前插入

既然是插入数据,那么就要对链表进行修改,需要传址调用,要传二级指针,结点pos,和数据x

c 复制代码
//指定位置pos之前插入
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
	assert(pphead && pos);
	//如果pos的位置就是头结点,那么就相当于头插
	if (pos == *pphead)
	{
		//头插
		SLTPushFront(pphead, x);
	}
	else
	{
		//创建新结点
		SLTNode* newnode = SLTBuyNode(x);
		//创建prev记录pos的前驱结点
		SLTNode* prev = *pphead;

		while (prev->next != pos)
		{
			prev = prev->next;
		}
		//此时找到了pos
		newnode->next = pos;
		prev->next = newnode;
	}
}

指定位置之后pos插入

c 复制代码
//指定位置之后pos插入
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{
	//在pos之后插入,就不需要知道头结点
	SLTNode* newnode = SLTBuyNode(x);
	
	newnode->next = pos->next;
	pos->next = newnode;
}

指定位置删除

c 复制代码
//指定位置删除
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
	assert(pphead && pos);

	if (pos == *pphead)
	{
		//头删
		SLTPopFront(pphead);
	}
	else 
	{
		SLTNode* del = *pphead;
		while (del->next != pos)
		{
			del = del->next;
		}
		del->next = pos->next;
		free(pos);
		pos == NULL;
	}	
}

指定位置之后删除

c 复制代码
//指定位置之后删除
void SLTEraseAfter(SLTNode* pos)
{
	assert(pos);
	SLTNode* del = pos->next;
	pos->next = del->next;
}

销毁链表

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

链表的分类


相关推荐
我不会编程5555 小时前
Python Cookbook-5.1 对字典排序
开发语言·数据结构·python
似水এ᭄往昔5 小时前
【C语言】文件操作
c语言·开发语言
owde6 小时前
顺序容器 -list双向链表
数据结构·c++·链表·list
第404块砖头6 小时前
分享宝藏之List转Markdown
数据结构·list
蒙奇D索大6 小时前
【数据结构】第六章启航:图论入门——从零掌握有向图、无向图与简单图
c语言·数据结构·考研·改行学it
A旧城以西6 小时前
数据结构(JAVA)单向,双向链表
java·开发语言·数据结构·学习·链表·intellij-idea·idea
烂蜻蜓7 小时前
C 语言中的递归:概念、应用与实例解析
c语言·数据结构·算法
守正出琦8 小时前
日期类的实现
数据结构·c++·算法
ゞ 正在缓冲99%…8 小时前
leetcode75.颜色分类
java·数据结构·算法·排序
javaisC9 小时前
c语言数据结构--------拓扑排序和逆拓扑排序(Kahn算法和DFS算法实现)
c语言·算法·深度优先