当前位置: 首页 > java >正文

数据结构——单链表

目录

  • 1 链表的成因
  • 2 链表的概念
  • 3 单链表的结构和操作实现
    • 3.1 单链表的结构体定义
    • 3.2 生成新结点
    • 3.3 单链表的尾插操作
    • 3.4 单链表的头插操作
    • 3.5 单链表的尾删操作
    • 3.6 链表的头删操作
    • 3.7 链表的输出操作
    • 3.8 在链表指定位置之前插入
    • 3.9 指定删除链表的某一个结点
    • 3.10 在链表的指定位置之后插入结点
    • 3.11 删除指定位置的下一个结点
    • 3.12 销毁链表
  • 4 单链表整体实现

1 链表的成因

在之前的文章中,我介绍过了顺序表,可以通过下面的链接查看

数据结构——顺序表

对于顺序表这种结构而言,它具有一定的缺点

  • 在顺序表的头部或中间位置插入,删除元素时,时间复杂度较高
  • 顺序表对内存空间较为浪费,比如说,只需要存储一个元素,但是却扩充100个空间的情况,剩下的99个空间直接被浪费了
  • 每次扩充空间,释放空间时,都需要额外的时间开销,效率低

为了解决这些顺序表的缺点,就出现了链表这种结构

2 链表的概念

链表是线性表的一种,它在逻辑结构上是连续的,但是在物理结构上是不一定连续的
链表有多种分类,在多种分类中,主要关心的是单链表双链表
在这篇文章中,先来介绍一下单链表

3 单链表的结构和操作实现

3.1 单链表的结构体定义

单链表一般指 不带虚拟头结点的单向不循环链表,它每个结点都保存了数据和下一个结点的地址
比如下图就是一个单链表:
在这里插入图片描述

对应的结构体定义如下:

typedef int SListDataType;
typedef struct SListNode
{SListDataType data; //数据struct SListNode* next; //指向下一个结点的指针
}SLTNode;

3.2 生成新结点

要生成新结点,只需要通过malloc动态开辟空间,将值保存进新空间内并设置next指针为空即可

SLTNode* SLTCreateNode(SListDataType x)
{SLTNode* node = (SLTNode*)malloc(sizeof(SLTNode));assert(node);node->data = x;node->next = NULL;return node;
}

3.3 单链表的尾插操作

尾插操作时,需要处理空链表和非空链表两种情况
空链表
在这里插入图片描述
空链表的情况下,先创建新结点,再使头指针head指向新结点即可

非空链表
在这里插入图片描述

非空链表在进行尾插时,需要先通过指针 tail 来寻找到链表尾结点,再创建新结点并插入到尾部
注:此处保存1的结点的next指针中,保存的应为0x13ff40

代码实现

void SLTPushBack(SLTNode** pHead, SListDataType x)
{assert(pHead);SLTNode* newNode = SLTCreateNode(x); //创建新结点if (*pHead == NULL) //是空链表{*pHead = newNode;}else //不是空链表{SLTNode* tail = *pHead;while (tail->next) //寻找尾结点{tail = tail->next;}tail->next = newNode; //插入新结点}}

3.4 单链表的头插操作

头插操作不需要分情况讨论,直接创建新结点,使新结点的 next 指针指向下一个结点,使 head 指针指向新结点即可
在这里插入图片描述

代码实现

void SLTPushFront(SLTNode** pHead, SListDataType x)
{assert(pHead);SLTNode* newNode = SLTCreateNode(x);newNode->next = *pHead;*pHead = newNode;
}

3.5 单链表的尾删操作

单链表在进行尾删时,需要考虑一个结点,多个结点两种情况

一个结点时
在这里插入图片描述
当只有一个结点时,只需要释放head所指向的空间并将head置为空即可

多个结点时
在这里插入图片描述

首先遍历链表,使用 prev 记录倒数第二个结点,使用 rear 来记录最后一个结点,更改 prev 指向的节点的 next 指针,释放 rear 指向的结点即可

代码实现

void SLTPopBack(SLTNode** pHead)
{assert(pHead && *pHead);if ((*pHead)->next == NULL) //只有一个结点{free(*pHead);*pHead = NULL;}else //不止一个节点{SLTNode* prev = *pHead;SLTNode* rear = prev->next;while (rear->next) //寻找最后一个结点和倒数第二个结点{prev = prev->next;rear = rear->next;}free(rear);rear = NULL;prev->next = NULL;}}

3.6 链表的头删操作

在进行头删时,先用指针 cur 记录下要删除的结点,再修改 head 指针的指向,释放要删除的结点,最后将 cur 置为空,防止野指针问题
在这里插入图片描述
代码实现

void SLTPopFront(SLTNode** pHead)
{assert(pHead && *pHead); //防止空指针解引用SLTNode* cur = *pHead;*pHead = cur->next;free(cur);cur = NULL;
}

3.7 链表的输出操作

要将链表每个结点保存的值进行输出,只需要使用一个指针来遍历链表并访问每一个结点保存的值就可以了
在这里插入图片描述
代码实现

void SLTPrint(SLTNode* head)
{SLTNode* cur = head;while (cur != NULL) //使用cur寻找每一个节点{printf("%d->", cur->data);cur = cur->next;}printf("\n");}

3.8 在链表指定位置之前插入

如果想在链表的某一个结点之前插入新的结点,就需要分在第一个结点前,不在第一个结点前的两类情况,pos 是指定的位置

在第一个结点前
在第一个结点之前插入新的结点,实际上就是头插,因此调用头插的函数即可
不在第一个结点前
在这里插入图片描述
这个时候就需要 prev 指针来寻找 pos 结点的前一个结点,找到后,创建新结点,使新结点的 next 指针指向 pos 结点,prev 的 next 指针指向新结点

代码实现

void SLTInsert(SLTNode** pHead, SLTNode* pos, SListDataType x)
{assert(pHead);if (pos == *pHead) //插入的位置在第一个结点前{SLTPushFront(pHead, x);}else //插入的位置不在第一个结点前{SLTNode* prev = *pHead;while (prev->next != pos) //寻找pos的前一个结点{prev = prev->next;}SLTNode* node = SLTCreateNode(x); //插入新结点node->next = pos;prev->next = node;}}

3.9 指定删除链表的某一个结点

在指定删除链表的某一结点时,要区分第一个结点和其他结点两个情况
pos 指向了要删除的结点
删除第一个结点
删除第一个结点时,实际上就是在进行头删的操作,直接调用头删的函数即可
删除其他结点
在这里插入图片描述
删除其他结点时,需要使用 prev 指针找到 pos 的前一个结点,更改 prev 指针指向结点的 next 指针,使其指向后一个结点,再释放 pos 结点

代码实现

void SLTErase(SLTNode** pHead, SLTNode* pos)
{assert(pHead && *pHead);assert(pos);if (pos == *pHead) //删除第一个结点{SLTPopFront(pHead);}else //删除其它结点{SLTNode* prev = *pHead;while (prev->next != pos) //找pos的前一个结点{prev = prev->next;}prev->next = pos->next;free(pos);pos = NULL;}}

3.10 在链表的指定位置之后插入结点

pos 为指定的位置,先创建新结点,更改新结点的 next 指针,使其指向 pos 的下一个结点,再使 pos 的 next 指针指向新结点即可
在这里插入图片描述
代码实现

void SLTInsertAfter(SLTNode* pos, SListDataType x)
{assert(pos);SLTNode* node = SLTCreateNode(x);node->next = pos->next;pos->next = node;}

3.11 删除指定位置的下一个结点

pos 为指定的位置,先记录 pos 的下一个结点为 cur,使 pos 指向结点的 next 指针指向后一个结点,再释放 cur 指向的结点即可
在这里插入图片描述
代码实现

void SLTEraseAfter(SLTNode* pos)
{assert(pos && pos->next);SLTNode* cur = pos->next;pos->next = cur->next;free(cur);cur = NULL;
}

3.12 销毁链表

销毁链表时,需要使用到两个指针 prev 和 rear,prev用来指向要销毁的结点,rear 用来记录下一个结点,防止销毁结点后找不到下一个结点的情况发生,然后遍历链表,释放每一个结点即可

在这里插入图片描述
代码实现

void SListDesTroy(SLTNode** pHead)
{assert(pHead && *pHead);SLTNode* prev = *pHead;while (prev) //销毁每一个节点{SLTNode* rear = prev->next;free(prev);prev = rear;}*pHead = NULL; //头指针置为空}

4 单链表整体实现

SList.c

//函数的实现
#include "SList.h"//打印链表节点
void SLTPrint(SLTNode* head)
{SLTNode* cur = head;while (cur != NULL) //使用cur寻找每一个节点{printf("%d->", cur->data);cur = cur->next;}printf("\n");}//产生新结点
SLTNode* SLTCreateNode(SListDataType x)
{SLTNode* node = (SLTNode*)malloc(sizeof(SLTNode));assert(node);node->data = x;node->next = NULL;return node;
}//尾插
void SLTPushBack(SLTNode** pHead, SListDataType x)
{assert(pHead);SLTNode* newNode = SLTCreateNode(x); //创建新结点if (*pHead == NULL) //是空链表{*pHead = newNode;}else //不是空链表{SLTNode* tail = *pHead;while (tail->next) //寻找尾结点{tail = tail->next;}tail->next = newNode; //插入新结点}}//头插
void SLTPushFront(SLTNode** pHead, SListDataType x)
{assert(pHead);SLTNode* newNode = SLTCreateNode(x);newNode->next = *pHead;*pHead = newNode;
}//尾删
void SLTPopBack(SLTNode** pHead)
{assert(pHead && *pHead);if ((*pHead)->next == NULL) //只有一个结点{free(*pHead);*pHead = NULL;}else //不止一个节点{SLTNode* prev = *pHead;SLTNode* rear = prev->next;while (rear->next) //寻找最后一个结点和倒数第二个结点{prev = prev->next;rear = rear->next;}free(rear);rear = NULL;prev->next = NULL;}}//头删
void SLTPopFront(SLTNode** pHead)
{assert(pHead && *pHead); //防止空指针解引用SLTNode* cur = *pHead;*pHead = cur->next;free(cur);cur = NULL;
}//查找
SLTNode* SLTFind(SLTNode* head, SListDataType x)
{SLTNode* cur = head;while (cur){if (cur->data == x) //cur指向的节点保存了xreturn cur;cur = cur->next;}return NULL;
}//指定位置之前插入
void SLTInsert(SLTNode** pHead, SLTNode* pos, SListDataType x)
{assert(pHead);if (pos == *pHead) //插入的位置在第一个结点前{SLTPushFront(pHead, x);}else //插入的位置不在第一个结点前{SLTNode* prev = *pHead;while (prev->next != pos) //寻找pos的前一个结点{prev = prev->next;}SLTNode* node = SLTCreateNode(x); //插入新结点node->next = pos;prev->next = node;}}//指定位置删除
void SLTErase(SLTNode** pHead, SLTNode* pos)
{assert(pHead && *pHead);assert(pos);if (pos == *pHead) //删除第一个结点{SLTPopFront(pHead);}else //删除其它结点{SLTNode* prev = *pHead;while (prev->next != pos) //找pos的前一个结点{prev = prev->next;}prev->next = pos->next;free(pos);pos = NULL;}}//指定位置之后插入
void SLTInsertAfter(SLTNode* pos, SListDataType x)
{assert(pos);SLTNode* node = SLTCreateNode(x);node->next = pos->next;pos->next = node;}//删除pos之后的结点
void SLTEraseAfter(SLTNode* pos)
{assert(pos && pos->next);SLTNode* cur = pos->next;pos->next = cur->next;free(cur);cur = NULL;
}//销毁链表
void SListDesTroy(SLTNode** pHead)
{assert(pHead && *pHead);SLTNode* prev = *pHead;while (prev) //销毁每一个节点{SLTNode* rear = prev->next;free(prev);prev = rear;}*pHead = NULL; //头指针置为空}

SList.h

//结构体定义,函数声明
#pragma once
#include <stdlib.h>
#include <stdio.h>
#include <assert.h>//单链表结构体定义
typedef int SListDataType;
typedef struct SListNode
{SListDataType data;struct SListNode* next;
}SLTNode;//打印链表节点
void SLTPrint(SLTNode* head);//尾插
void SLTPushBack(SLTNode** pHead, SListDataType x);//头插
void SLTPushFront(SLTNode** pHead, SListDataType x);//尾删
void SLTPopBack(SLTNode** pHead);//头删
void SLTPopFront(SLTNode** pHead);//查找
SLTNode* SLTFind(SLTNode* head, SListDataType x);//指定位置前插入
void SLTInsert(SLTNode** pHead, SLTNode* pos, SListDataType x);//指定位置删除
void SLTErase(SLTNode** pHead, SLTNode* pos);//指定位置后插入
void SLTInsertAfter(SLTNode* pos, SListDataType x);//删除pos之后的结点
void SLTEraseAfter(SLTNode* pos);//销毁链表
void SListDesTroy(SLTNode** pHead);
http://www.xdnf.cn/news/18174.html

相关文章:

  • STL库——string(类模拟实现)
  • 【PHP】模拟斗地主后端编写
  • Redis--day8--黑马点评--分布式锁(一)
  • electron 开发笔记
  • 拓扑排序详解:从力扣 207 题看有向图环检测
  • 第一阶段C#-14:委托,事件
  • 【牛客刷题】最大公约数与最小公倍数:算法详解与实现
  • 一个基于纯前端技术实现的五子棋游戏,无需后端服务,直接在浏览器中运行。
  • Leetcode 3649. Number of Perfect Pairs
  • 面向R语言用户的Highcharts
  • 浅谈 Python 正则表达式中的 groups()
  • SpringBoot3整合OpenAPI3(Swagger3)完整指南
  • 【Python】Python 多进程与多线程:从原理到实践
  • Nodejs学习
  • CPTS---Active 复现
  • 【matlab】考虑源荷不平衡的微电网鲁棒定价研究
  • 【每日一题】Day 7
  • C 语言数据结构与算法的复杂度分析:从理论到实战的效率衡量指南
  • Vue2篇——第五章 Vue.js 自定义指令与插槽核心
  • 【JavaEE】(16) Spring Boot 日志
  • Unity作为库导入Android原生工程
  • 【github-action 如何为github action设置secrets/environment】
  • SpringAI集成MCP
  • (Arxiv-2025)OPENS2V-NEXUS:一个面向主体到视频生成的详细基准与百万规模数据集
  • 【完整源码+数据集+部署教程】织物缺陷检测系统源码和数据集:改进yolo11-RevCol
  • [数据结构] ArrayList 与 顺序表
  • 【前端进阶】UI渲染优化 - 骨架屏技术详解与多框架实现方案
  • RH134 管理网络安全知识点
  • CMake指令:查找文件(find_file)、查找目录(find_path)、查找库文件(find_library)
  • ANSI终端色彩控制知识散播(I):语法封装(Python)——《彩色终端》诗评