【数据结构入坑指南(三.1)】--《面试必看:单链表与顺序表之争,读懂“不连续”之美背后的算法思想》

🔥@晨非辰Tong:个人主页

👀专栏:《C语言》《数据结构与算法》

💪学习阶段:C语言、数据结构与算法初学者

⏳"人理解迭代,神理解递归。"


引言: 学完顺序表,深受其扩容和插入删除低效之苦?

是时候认识单链表了。它用指针串联数据,以"不连续"的物理结构,完美解决了顺序表的痛点。

接下来,让我们一起探索这种更优雅的数据结构。


目录

一、单链表特性全解

[1.1 单链表:数据的"一线牵"](#1.1 单链表:数据的"一线牵")

[1.2 单链表结构:数据的"小火车"](#1.2 单链表结构:数据的“小火车”)

二、单链表的实现(初)

[2.1 铺垫](#2.1 铺垫)

[2.2 单链表尾插](#2.2 单链表尾插)

[2.3 单链表头插](#2.3 单链表头插)

[2.4 单链表尾删](#2.4 单链表尾删)


一、单链表特性全解

1.1单链表:数据的"一线牵"

--在学习顺序表时,发现有几个缺陷:

  • 中间/头部的插入、删除,时间复杂度为O(N);
  • 增容要申请新空间,拷贝数据,释放旧空间,有消耗;
  • 增容一般呈两倍增长,会空间浪费;

--由于对上面的思考(改善),引入单链表:

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

1.2 单链表结构:数据的"小火车"

--链表的逻辑结构是线性的,可想象成一列火车拉着一节一节的车厢:

--对于这种结构,每个车厢都是独立空间,且将车厢称为节点,由两部分组成:

  • 存储的数据;
  • 下一节点的地址;

-- plist 保存第一个节点的地址(指向第一个节点),通过修改 plist 保存的内容可以实现指向不同的节点。

--注意:需要插入数据时才会****申请一块节点大小的空间

--而在堆空间申请空间,节点不一定时整齐排放**(物理结构不一定连续)**,但每个节点都存者下一节点的地址,就通过这种方式进行连接。


二、单链表的实现(初)

2.1 铺垫

--先简单实现以下单链表的打印:

cpp 复制代码
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;//重命名_节点

	//或者------> typedef struct SListNode SLTNode;
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

//声明_打印函数
void SLTPrint(SLTNode* phead);//phead指向第一个节点,后续通过地址调换节点
cpp 复制代码
SList.c
#include "SList.h"

//定义_打印函数
void SLTPrint(SLTNode* phead)
{
	SLTNode* pcur = phead;//临时赋值
	//循环打印
	while (pcur != NULL)
	{
		printf("%d -> ", pcur->data);//输出数据
		pcur = pcur->next;//指向下一节点
	}
	printf("NULL\n");
}
//对于循环条件,pcur存储首节点地址,当经过循环内的地址跳转,最终指向NULL结束
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
test.c
int test01()
{

	//创建链表--->节点之间的链接
	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;
		//链接地址
	node1->next = node2;//node存储的是地址
	node2->next = node3;
	node3->next = node4;
	node4->next = NULL;

	//打印链表
	SLTNode* phead = node1;
	SLTPrint(phead);
}

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

2.2 单链表尾插

--在后续进行的头插等都需要申请新的节点空间,先封装函数:

cpp 复制代码
SList.c
//定义_申请节点
SLTNode* SLTBuyNode(SLTDataType x)
{
	//申请一个节点大小的空间
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));

	if (newnode == NULL)
	{
		perror("malloc fail!");
		exit(1);
	}

	newnode->data = x;
	newnode->next = NULL;

	return newnode;
}

--实现尾插

cpp 复制代码
SList.c
//定义_尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x)//应用时传地址,二级指针接收
{

	//申请新节点空间->插入数据

	SLTNode* newnode = SLTBuyNode(x);//node->存储着新空间的地址

	//链表一开始为空
		//pphead->二级指针变量--->存储一级指针变量的地址
	if (*pphead == NULL)//解引用(phead本身)判断链表是否为空
	{

		*pphead = newnode;//测试端的指针(phead)指向新空间

	}

	//链表不为空
	else
	{

		//遍历找到尾节点(空地址),插入新节点
		SLTNode* ptail = *pphead;//防止*pphead指向发生变化

		while (ptail->next != NULL)
		{
			ptail = ptail->next;//向后遍历
		}

		//循环外找到地址为空节点->插入新节点
		ptail->next = newnode;
	}
}
  • 首先判断为空,将申请节点作首节点;
  • 非空,遍历找到尾节点--->next=NULL,将newnode给next,完成地址链接。

--注意:尾插函数参数是二级指针,进行传址,接收指针(plist)地址。

cpp 复制代码
test.c
void test02()
{

	//创建空链表
	SLTNode* phead = NULL;//首节点为空
	
	//尾插
	SLTPushBack(&phead, 1);//为了使形参的变化影响实参,传地址
	SLTPushBack(&phead, 2);
	SLTPushBack(&phead, 3);
	SLTPushBack(&phead, 4);
	SLTPrint(phead);

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

2.3 单链表头插

cpp 复制代码
SList.c
//定义_头插
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);
	//申请节点空间
	SLTNode* newnode = SLTBuyNode(x);

	//*pphead 相当于 phead(存储首节点地址,指向首节点)

	newnode->next = *pphead;//新节点存储首节点得地址

	*pphead = newnode;//指向首节点指针改变指向对象

}

--头插的实现很简单,因为头指针*pphead相当于phead(始终指向首节点),先将新节点存上首节点地址,将改变首节点的指向(phead),就完成了插入。

cpp 复制代码
test.c
void test02()
{
	SLTNode* phead = NULL;//空链表
	SLTPushFront(&phead, 1);
	SLTPrint(phead);
	SLTPushFront(&phead, 2);
	SLTPrint(phead);
	SLTPushFront(&phead, 3);
	SLTPrint(phead);
	SLTPushFront(&phead, 4);
	SLTPrint(phead);

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

2.4 单链表尾删

cs 复制代码
SList.c
//定义_尾删
void SLTPopBack(SLTNode** pphead)
{
	//链表为空不能删除
	assert(pphead && *pphead);//pphead为空--->解引用错误;*pphead为空->首节点为空(链表为空),无法删除

	//链表只有一个节点,直接删除
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else 
	{
		SLTNode* prev = NULL;

		SLTNode* ptail = *pphead;

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

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

--prev 指针始终指向 ptail 前一个节点,当 ptail -> next 指向 NULL然后释放,prev指向的节点为尾节点,此时将prev->next置为NULL,完成尾删。

--特殊的是,链表只有一个节点,就要特殊处理。不需要 prev 的指向作用,直接将 ptail 释放置空。

cpp 复制代码
test.c
void test02()
{

	//创建空链表
	SLTNode* phead = NULL;//phead头指针,指向的首节点为空,代表链表没有节点
	
	//尾插
	SLTPushBack(&phead, 1);//为了使形参的变化影响实参,传地址
	SLTPushBack(&phead, 2);
	SLTPushBack(&phead, 3);
	SLTPushBack(&phead, 4);
	SLTPrint(phead);
	//尾删
	SLTPopBack(&phead);
	SLTPrint(phead);
	SLTPopBack(&phead);
	SLTPrint(phead);
	SLTPopBack(&phead);
	SLTPrint(phead);
	SLTPopBack(&phead);
	SLTPrint(phead);

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

回顾:

《数据结构与算法精讲:从数组到顺序表,如何让数据管理变得强大而优雅?》​​

《从数组到动态顺序表:数据结构与算法如何优化内存管理?》
结语:至此,单链表的基础骨架已然搭建。我们实现了增删,也领略了其"一线牵"的灵活。

然而,这趟旅程远未结束。单链表还有更多形态等待探索:如何更高效地查找?环状链表有何妙用?双链表又如何实现双向奔赴?

掌握基础,方能构筑大厦。下一站,我们将继续深入,解锁单链表的更多潜能。

相关推荐
醉颜凉2 小时前
深入理解【插入排序】:原理、实现与优化
算法·排序算法·插入排序·sort
我是小疯子662 小时前
HybridA*算法:高效路径规划核心解析
人工智能·算法·机器学习
旖旎夜光2 小时前
哈希(14)(下)
数据结构·c++·学习
kamisama_zhu2 小时前
LeetCode 热题100快速通关指南(附模板) (优化完整版,真人心得版,持续更新)
算法·leetcode·职场和发展
h×32 小时前
LIO-SAM算法仿真实现教程(基于ubuntu22.04和ROS2-humble系统+GTSAM4.1.1)
算法
拾光Ծ2 小时前
哈希表进阶:挑战用哈希桶封装unordered_set和unordered_map,帮你深度理解迭代器!!!
数据结构·c++·哈希算法·散列表
给我一瓶哇哈哈呀2 小时前
[ROS2] CMU团队的Autonomous Exploration算法+MID-360部署到实车记录
c++·自动驾驶
草莓熊Lotso2 小时前
《算法闯关指南:优选算法--滑动窗口》--15.串联所有单词的子串,16.最小覆盖子串
开发语言·c++·人工智能·算法
阿里-于怀2 小时前
Dify 官方上架 Higress 插件,轻松接入 AI 网关访问模型服务
网络·人工智能·ai·dify·higress