【数据结构】单链表

文章目录


前言

在笔者之前的文章中提到过顺序表,但是顺序表存在许多缺陷,比如说在中间或头部插入低下(要进行循环,将后面元素后移),一旦增容会降低运行效率、增容造成空间浪费等,因此,链表应运而生,可以刚好填补上上述三个缺点。

链表的概念和结构

在笔者之前的文章中提到过顺序表,但是顺序表存在许多缺陷,比如说在中间或头部插入低下(要进行循环,将后面元素后移),一旦增容会降低运行效率、增容造成空间浪费等,因此,链表应运而生

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

链表也是线性表的一种,线性表 在物理结构上 不一定 是线性的,但在逻辑结构上 一定 是线性的

链表 在物理结构上 不是 线性的

链表的结构跟火车车厢相似,淡季时车次的车厢会相应减少,旺季时车次的车厢会额外增加几节。只需要将火车里的某节车厢去掉/加上,不会影响其他车厢,每节车厢都是独立存在的。

车相是独立存在的,且每节车厢都有车门,假设每节车厢的车门都是锁上的状态,需要不同的钥匙才能解锁,每次只能携带一把钥匙的情况下如何从车头走到车尾?最简单的做法:每节车厢里都放一把下一节车厢的钥匙。

那么 ,在链表中车厢是什么样的呢?

与顺序表不同的是,链表里的每节"车厢"都是独立申请下来的空间,我们称之为"结点/节点"(一个意思),链表就是由一个一个节点组成的

节点的组成主要有两个部分 :当前节点要保存的数据 和保存下一个节点的地址(指针变量)。

图中指针变量 plist保存的是第一个节点的地址,我们称plist此时"指向"第一个节点,

如果我们希望plist"指向"第二个节点时,只需要修改plist保存的内容为0x0012FFA0。

下面这是一个基本的链表结构

bash 复制代码
struct SListNode
{
	int data;
	struct SListNode* next;//指向下一个节点的指针
};

用C语言实现单链表

接下来我们以方法的实现来对链表进行更加深入的了解
注意看代码里的注释

我们先创建如下一个头文件和两个源文件

我们在头文件 "List.h" 中写入如下代码

c 复制代码
#pragma once
#define _CRT_SECURE_NO_WARNINGS  1
#pragma warning(disable:6031)
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>

//定义节点的结构
//数据+指向下一个节点的指针
typedef int SLTDataType;

typedef struct SListNode
{
	SLTDataType data;//存储的数据
	struct SListNode* next;//指向下一个节点的指针
}SLTNode;//重命名简化为SLTNOde

接下来我们在测试文件 "test.c" 创建如下几个变量,使其存储数字,和下一个节点的地址

c 复制代码
#include"List.h"

void SListTest01()
{
	//链表是由一个一个的节点组成的
	//创建几个节点
	SLTNode* node1 = (SLTNode*)malloc(sizeof(SLTNode));
	node1->data = 1;

	SLTNode* node2 = (SLTNode*)malloc(sizeof(SLTNode));
	node2->data = 2;

	SLTNode* node3 = (SLTNode*)malloc(sizeof(SLTNode));
	node3->data = 3;

	SLTNode* node4 = (SLTNode*)malloc(sizeof(SLTNode));
	node4->data = 4;

	//将4个节点联系起来
	node1->next = node2;
	node2->next = node3;
	node3->next = node4;
	node4->next = NULL;

}

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

了解链表运行 - 以链表的打印为例

为了使我们理解这里的链表运用,我们自定义一个打印的功能。

c 复制代码
#include"List.h"

void SLTprint(SLTNode* phead)//传入头节点地址
{
	SLTNode* pcur = phead;
	while (pcur)
	{
		printf("%d -> ", pcur->data);
		pcur = pcur->next;
	}
	printf("NULL\n");
}

打印如下

这个的本质就是没打印出当前节点的内容后,就将这个指针 pcur 变成下一个节点的地址,方便打印下一个节点的内容,还是比较好理解的。

这与数组的区别就是,链表使没有索引的,没法像数组一样一个一个往下打印,而是需要不断获得下一个节点的指针

实现链表数据改变 - 以尾插为例

在知晓了链表的基本运行方式后,我们着手尾插方式的实现,基本原理就是找到尾巴,然后把新创建的节点接上

但是很快我们会遇到第一个问题就是如果这个链表为空,直接去寻找链表的尾巴,必然要对空指针进行解引用,这就会直接导致报错,所以我们必须进行分类,于是就写出了如下代码

c 复制代码
void SLTPushBack(SLTNode* phead, SLTDataType x)
{
	SLTNode* newnode = SLTBuyNode(x);

	//空链表和非空链表
	if (phead == NULL)//如果对空链表找尾巴,会解引用空指针导致报错
	{
		phead = newnode;
	}
	else
	{
		//找尾巴
		SLTNode* ptail = phead;
		while (ptail->next)
		{
			ptail = ptail->next;
		}
		//ptail此时指向的是尾节点
		ptail->next = newnode;
	}
}

可是我们一运行,发现上面都没有改变,于是我们通过调试知道,这里我们只改变了形参,那么想要改变链表的各个节点我们就需要运用二级指针,使链表的值也能跟着改变,这也是为什么打印只需值,但改变数据需要地址

如果大家对这种二级指针的概念还不是很熟悉,欢迎看看笔者C语言专栏里的指针详解 link

c 复制代码
//尾插
SLTNode* SLTBuyNode(SLTDataType x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	if (newnode == NULL)
	{
		perror("malloc fail!");
		exit(1);//给上一个非0的退出码表示错误
	}
	newnode->data = x;
	newnode->next = NULL;

	return newnode;
}

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此时指向的是尾节点
		ptail->next = newnode;
	}
}

其他还有一些代码的实现,大差不大,我将部分可能会用到的代码放在下面了,有需要的小伙伴自己看看哈

c 复制代码
//头插
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* prev = *pphead;
		SLTNode* ptail = *pphead;
		while (ptail->next != NULL)
		{
			prev = ptail;
			ptail = ptail->next;
		}
		free(ptail);
		ptail = NULL;
		prev->next = NULL;
	}
	
}
相关推荐
手握风云-1 分钟前
数据结构(Java版)第二期:包装类和泛型
java·开发语言·数据结构
熬夜学编程的小王2 小时前
【C++篇】深度解析 C++ List 容器:底层设计与实现揭秘
开发语言·数据结构·c++·stl·list
阿史大杯茶2 小时前
AtCoder Beginner Contest 381(ABCDEF 题)视频讲解
数据结构·c++·算法
Chris _data2 小时前
二叉树oj题解析
java·数据结构
Lenyiin3 小时前
02.06、回文链表
数据结构·leetcode·链表
爪哇学长3 小时前
双指针算法详解:原理、应用场景及代码示例
java·数据结构·算法
爱摸鱼的孔乙己3 小时前
【数据结构】链表(leetcode)
c语言·数据结构·c++·链表·csdn
烦躁的大鼻嘎4 小时前
模拟算法实例讲解:从理论到实践的编程之旅
数据结构·c++·算法·leetcode
C++忠实粉丝4 小时前
计算机网络socket编程(4)_TCP socket API 详解
网络·数据结构·c++·网络协议·tcp/ip·计算机网络·算法
daiyang123...5 小时前
测试岗位应该学什么
数据结构