数据结构--链表和单链表详解及实现

一.前言

数据结构思维导图如下,灰色标记的是之前讲过的,本文将带你走近单链表(红色标记部分),希望大家有所收获🌹🌹

二.链表的定义和概念

在讲单链表之前,我们先学习一下链表

2.1 链表的定义

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

  • 逻辑结构:线性
  • 物理结构:非线性

2.2 节点的构成要素

链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。

每个结点包括两个部分:一个是存储数据元素的数据域 ,另一个是存储下一个结点地址的指针域。即数据+指向下一个结点的指针,节点常用结构体实现。

c 复制代码
struct ListNode
{
LTDataType data;
struct ListNode* next;
};

2.3 与数组结构的对比差异

数组 链表
存储方式 连续存储 需要预先分配固定大小的内存空间 非连续存储,通过指针连接 动态分配内存,不需要预先确定大小
内存使用 空间利用率高 (因为所有元素都紧密排列) 可能存在未使用的空间(e.g.数组大小固定但实际使用元素少) 空间利用率相对较低 每个节点需要额外的内存来存储指针
插入和删除操作 可能需要移动大量元素以保持连续性 时间复杂度为O(n),因为可能需要移动插入点之后的所有元素 通常只需要改变指针,不需要移动元素 时间复杂度为O(1) (已知插入或删除位置的节点)
随机访问 支持高效的随机访问,可通过索引快速访问任意元素 访问时间复杂度为O(1) 不支持高效的随机访问,必须从头节点开始遍历链表 访问时间复杂度为O(n)。
空间局部性 有很好的空间局部性,因为元素连续存储,适合缓存优化 空间局部性差,因为元素分散存储,可能导致缓存未命中
实现复杂度 实现简单,大多数编程语言内置支持 实现相对复杂,需要手动管理节点和指针
适用场景 适合需要频繁随机访问的场景 适合元素数量已知且变化不大的场景 适合插入和删除操作频繁的场景 适合元素数量频繁变化的场景

三.链表的分类

链表的结构非常多样,以下情况组合起来就有8种(2*2*2)链表结构

链表说明:

虽然有这么多的链表结构,但是我们实际中最常用的还是两种结构:

  1. 单链表:不带头单向不循环链表
  2. 双向链表:带头双向循环链表

四.单链表

前期准备

创建三个文件:

  1. SList.h: 存放各种头文件和函数声明
  2. SLIst.c : 各种函数的实现
  3. test.c: 测试和执行代码 最好写完一部分测试一部分 防止后期调试一堆bug

实现

首先在头文件中写上

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

接下来我们一步步实现单链表各种操作

1.定义链表结构

c 复制代码
typedef int SLTDataType;//方便之后一键替换类型

typedef struct SListNode
{
	SLTDataType data; //结点数据
	struct SListNode* next; //指针保存下⼀个结点的地址
}SLTNode;

2.申请新节点和打印函数

因为后面会多次用到申请新节点和打印函数,所以我们把它们都各自封装成函数方便调用

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

	return node;
}
//打印函数
void SLTPrint(SLTNode* phead)
{
	SLTNode* pcur = phead;
	while (pcur)
	{
		printf("%d->", pcur->data);
		pcur = pcur->next;
	}
	printf("NULL\n");
}

3.查找数据

c 复制代码
SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{
	assert(phead);
	SLTNode* pcur = phead;
	while (pcur)
	{
		if (pcur->data == x)
		{
			return pcur;
		}
		pcur = pcur->next;
	}
	//没有找到
	return NULL;
}

4.插入操作

包括尾插、头插、在指定位置之前插入数据、在指定位置之后插入数据

(1)尾插

c 复制代码
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);
	//pphead --> &plist
	// *pphead --> plist
	//申请新节点
	SLTNode* newnode = SLTBuyNode(x);
	if (*pphead == NULL)
	{
		*pphead = newnode;
	}
	else
	{
		//找尾结点
		SLTNode* pcur = *pphead;
		while (pcur->next)
		{
			pcur = pcur->next;
		}
		//pcur  newnode
		pcur->next = newnode;
	}
}

(2)头插

c 复制代码
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);

	SLTNode* newnode = SLTBuyNode(x);
	//newnode *pphead
	newnode->next = *pphead;
	*pphead = newnode;
}

(3)在指定位置之前插入数据

c 复制代码
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
	assert(pphead);
	assert(pos);

	if (pos == *pphead)
	{
		SLTPushFront(pphead, x);
	}
	else
	{
		SLTNode* newnode = SLTBuyNode(x);
		//找prev :pos的前一个结点
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		//prev newnode --> pos
		newnode->next = pos;
		prev->next = newnode;
	}
}

(4)在指定位置之后插入数据

c 复制代码
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{
	assert(pos);
	SLTNode* newnode = SLTBuyNode(x);
	//pos newnode --> pos->next
	newnode->next = pos->next;
	pos->next = newnode;
}

测试如下

5.删除操作

包括尾删、头删、删除指定节点、删除指定位置之后的结点、删除指定位置之前的结点(该操作是给你们留的作业😜 学完这篇自己独立实现一下 答案可以让我发给你或者其实调试通过了应该没什么问题跟前面的实现逻辑一样)

(1)尾删

c 复制代码
void SLTPopBack(SLTNode** pphead)
{
	//链表为空:不可以删除
	assert(pphead && *pphead);
	//处理只有一个结点的情况:要删除的就是头结点	
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		//找 prev ptail
		SLTNode* ptail = *pphead;
		SLTNode* prev = NULL;
		while (ptail->next)
		{
			prev = ptail;
			ptail = ptail->next;
		}
		prev->next = NULL;
		free(ptail);
		ptail = NULL;
	}
}

测试如下

(2)头删

c 复制代码
void SLTPopFront(SLTNode** pphead)
{
	assert(pphead && *pphead);

	SLTNode* next = (*pphead)->next;
	//*pphead --> next
	free(*pphead);
	*pphead = next;
}

(3)删除指定节点

c 复制代码
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
	assert(pphead && *pphead);
	assert(pos);

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

(4)删除指定位置之后的结点

c 复制代码
void SLTEraseAfter(SLTNode* pos)
{
	assert(pos && pos->next);

	//pos pos->next pos->next->next
	SLTNode* del = pos->next;
	pos->next = pos->next->next;
	free(del);
	del = NULL;
}

6.销毁链表

c 复制代码
void SListDestroy(SLTNode** pphead)
{
	assert(pphead && *pphead);

	SLTNode* pcur = *pphead;
	while (pcur)
	{
		SLTNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	*pphead = NULL;
}

五.总结

单链表的详细实现代码已经上传到我的资源了,大家点进主页或者在本文最上方可以下载查看,自己去写一下,边写边调试会更容易理解和掌握。

接下来会逐步介绍上述思维导图数据结构剩下的部分,创作不易,希望大家多多支持,有什么想法欢迎讨论🌹🌹

相关推荐
梭七y15 分钟前
【力扣hot100题】(032)排序链表
算法·leetcode·链表
编程绿豆侠19 分钟前
力扣HOT100之链表:206. 反转链表
算法·leetcode·链表
ylfhpy4 小时前
Java面试黄金宝典33
java·开发语言·数据结构·面试·职场和发展·排序算法
神里流~霜灭6 小时前
数据结构:二叉树(三)·(重点)
c语言·数据结构·c++·算法·二叉树·红黑树·完全二叉树
zh_xuan7 小时前
LeeCode 57. 插入区间
c语言·开发语言·数据结构·算法
巷北夜未央7 小时前
数据结构之二叉树Python版
开发语言·数据结构·python
手握风云-7 小时前
优选算法的妙思之流:分治——快排专题
数据结构·算法
G皮T8 小时前
【Python Cookbook】字符串和文本(五):递归下降分析器
数据结构·python·正则表达式·字符串·编译原理·词法分析·语法解析
柯ran9 小时前
数据结构|排序算法(一)快速排序
数据结构·算法·排序算法
pipip.9 小时前
搜索二维矩阵
数据结构·算法·矩阵