【数据结构】单链表(带图详解)

article/2025/9/20 8:38:25

文章目录

  • 一、单链表的定义及其结构
    • 1.1.概念
    • 1.2.单链表的结构
    • 1.3.单链表的特点
  • 二、单链表的实现
    • 2.1.定义结点
    • 2.2.创建单链表
    • 2.3.打印单链表
    • 2.4. 单链表尾插与尾删
    • 2.4. 单链表头插与头删
    • 2.4.查找某个结点
    • 2.5.插入
    • 2.6.删除\
  • 总代码


一、单链表的定义及其结构

1.1.概念

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

1.2.单链表的结构

单链表(Singly Linked List)是一种常用的数据结构,它由若干个节点组成,每个节点包含两部分:数据域指针域。数据域用于存储数据,而指针域则用于指向下一个节点的地址。单链表中每个节点只有一个指针域,指向下一个节点,最后一个节点的指针域指向 NULL,表示链表的结尾。
在这里插入图片描述
一看到这种结构有就会问,顺序表的存储方式和单链表哪里不同呢?
在这里插入图片描述
顺序表是一种基于数组实现的线性数据结构,其元素在内存中是连续存储的,其实就是数组的原理。而单链表是一种逻辑连续,物理不一定连续的线性表,实际上在内存中,每个结点可能会隔得很远,只是通过指针的方式将他们像绳子一样穿起来,也是每个结点都指向下一个结点地址空间。

1.3.单链表的特点

与顺序表不同,单链表的元素不是连续存储的,而是通过指针相连形成链式结构。因此,单链表具有以下优缺点。
优点:

  • 支持动态内存分配。由于单链表不需要预先分配一段连续的空间,因此可以根据实际需求动态地申请、释放节点空间,避免浪费内存。
  • 支持高效的插入、删除操作。由于单链表中的节点是通过指针相连的,因此在插入、删除一个节点时,只需要修改其前驱节点或后继节点的指针即可,时间复杂度为 O ( 1 ) O(1) O(1)

缺点:

  • 不支持随机访问。由于单链表中的节点不是连续存储的,因此不能像数组一样通过下标来直接访问一个元素,需要从头节点开始遍历整个链表才能访问任意位置的元素。

二、单链表的实现

2.1.定义结点

typedef int SLTDataType;typedef struct SListNode
{SLTDataType data;//数据域struct SListNode* next;//指针域
}SLTNode;

2.2.创建单链表

为一个新结点创建空间以及输入值

SLTNode* BuySLTNode(SLTDataType x) {SLTNode* newnode = new SLTNode;//申请空间,等同于malloc函数if (!newnode) {perror("fail");exit(-1);}newnode->data = x;newnode->next = NULL;return newnode;
}

根据上述函数,我们可以构造一个多个结点的单链表生成函数。

SLTNode* CreateSList(int n) {SLTNode* phead, * p; // 头节点phead = p = NULL;    // 指向当前节点的指针for (int i = 0; i < n; i++) {SLTDataType x;cin >> x;SLTNode* newnode = BuySLTNode(x);// 创建新节点if (!phead)phead = p = newnode;// 插入到链表中else {p->next = newnode;p = p->next;}}return phead;
}

2.3.打印单链表

遍历链表,当链表中没有元素时,头指针所指向的就是NULL,也是停止循环的条件

void SLTPrint(SLTNode* phead)
{SLTNode* p=phead;//当前指针不为空就继续走while(!p){printf("%d->",p->data);p=p->next;}printf("NULL\n");}

2.4. 单链表尾插与尾删

尾插:
通过遍历链表找到尾节点,并将新节点链接到尾节点之后,实现了新元素的添加。
在这里插入图片描述

void SLTPushBack(SLTNode** pphead, SLTDataType x) {assert(pphead);//判断是否为空SLTNode* newnode = BuySLTNode(x);//创建一个新的结点//如果头为空的话就将新结点赋值给头结点if (*pphead == NULL) {*pphead = newnode;}//else{SLTNode* p = *pphead;//遍历到尾结点while (p->next) {p = p->next;}//尾结点指向新结点p->next = newnode;}
}

尾删:
在链表为空或只有一个节点时,直接释放相应内存空间即可;否则通过遍历找到尾节点,并释放其空间,然后将前一个节点的 next 指针指向 NULL
在这里插入图片描述

void SLTPopBack(SLTNode** pphead) {//链表为空就直接退出if (*pphead == NULL) {return;}//链表只有头结点的话,pphead->next=null等同于只有头结点if ((*pphead)->next == NULL) {//删除free(*pphead);*pphead = NULL;}else{SLTNode* p = *pphead;//找到尾结点的前一个结点while (p->next->next) {p = p->next;}//删除free(p->next);p->next = NULL;}
}

2.4. 单链表头插与头删

头插:
在这里插入图片描述

void SLTPushFront(SLTNode** pphead, SLTDataType x) {SLTNode* newnode = BuySLTNode(x);newnode->next = *pphead;//新结点后面连接旧的头结点*pphead = newnode;//头结点更新为新节点
}

头删:
在这里插入图片描述

void SLTPopFront(SLTNode** pphead) {assert(pphead);//if (*pphead == NULL)return;SLTNode* p = (*pphead)->next;//头结点的下一个结点free(*pphead);*pphead = p;//头结点更新为p}

2.4.查找某个结点

查找其实就和遍历单链表差不多,不过需要加一个数值是否相等的if判断语句

SLTNode* SListFind(SLTNode* plist, SLTDataType x) {SLTNode* p = plist;while (p) {if (p->data == x)return p;p = p->next;}return NULL;
}

2.5.插入

该函数通过指针操作在单链表的指定位置插入一个值为x的新结点,同时也考虑了插入位置是头结点的情况。需要注意的是,此函数没有考虑插入位置为空的情况,即需要确保 pos 非空指针。同时,该函数只能在某个结点之前插入新的结点,如果需要在链表尾部插入,则需要先找到链表尾结点,并将其 next 指针指向新结点。

void SListInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x) {assert(pos);//一个值为x的新结点SLTNode* newnode = BuySLTNode(x);//头结点需要特殊处理if (*pphead == pos) {newnode->next = *pphead;*pphead = newnode;}else {SLTNode* p = *pphead;//p为前一个结点//遍历停止在pos结点之前while (p->next != pos) {p = p->next;}p->next = newnode;newnode->next = pos;}
}

2.6.删除\

若要删除的结点是头结点,则通过记录头指针的下一个结点来更新头结点,并释放原结点空间。

否则,需要寻找删除结点的前一个结点 pre,从而可以断开 pos 与 pre->next 之间的连接,同时释放 pos 的空间。因此,通过 while 循环遍历单链表找到前一个结点 pre。找到之后将 pre->next 指向待删除结点的下一个结点,并释放待删除结点空间。

void SListErase(SLTNode** pphead, SLTNode* pos) {assert(pos);assert(*pphead);//头结点需要特殊处理if (*pphead == pos){SLTNode* p = (*pphead)->next;//记录头结点下一个结点free(*pphead);//更新头结点*pphead = p;}else {SLTNode* pre = *pphead;//pre为前一个结点while (pre->next!=pos) {pre = pre->next;}pre->next = pos->next;free(pos);}
}

总代码

#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include<bits/stdc++.h>
using namespace std;
//struct SListNode
//{
//	SLTDataType data;
//	struct SListNode* next;
//};
//typedef struct SListNode SLTNode;//void SLTPushBack(SLTDataType x)
//{
//	SLTNode node;
//}typedef int SLTDataType;typedef struct SListNode
{SLTDataType data;struct SListNode* next;
}SLTNode;SLTNode* BuySLTNode(SLTDataType x);
SLTNode* CreateSList(int n);
void SLTPrint(SLTNode* phead);void SLTPushBack(SLTNode** pphead, SLTDataType x);
void SLTPopBack(SLTNode** pphead);
void SLTPushFront(SLTNode** pphead, SLTDataType x);
void SLTPopFront(SLTNode** pphead);SLTNode* SListFind(SLTNode* plist, SLTDataType x);// 单链表在pos位置之后插入x
// 分析思考为什么不在pos位置之前插入?
void SListInsertAfter(SLTNode* pos, SLTDataType x);
// 单链表删除pos位置之后的值
// 分析思考为什么不删除pos位置?
void SListEraseAfter(SLTNode* pos);// 在pos之前插入x
void SListInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);
// 删除pos位置
void SListErase(SLTNode** pphead, SLTNode* pos, SLTDataType x);SLTNode* BuySLTNode(SLTDataType x) {SLTNode* newnode = new SLTNode;if (!newnode) {perror("fail");exit(-1);}newnode->data = x;newnode->next = NULL;return newnode;}
SLTNode* CreateSList(int n) {SLTNode* phead, * p;phead = p = NULL;for (int i = 0; i < n; i++) {SLTDataType x;cin >> x;SLTNode* newnode = BuySLTNode(x);if (!phead)phead = p = newnode;else {p->next = newnode;p = p->next;}}return phead;}void SLTPrint(SLTNode* phead)
{SLTNode* p = phead;while (p) {cout << p->data << ">";p = p->next;}cout << "NULL" << endl;
}//
//void SLTPushBack(SLTNode** pphead, SLTDataType x) {
//	assert(pphead);
//	SLTNode* newnode = BuySLTNode(x);
//	if (*pphead == NULL) {
//		*pphead = newnode;
//	}
//	else {
//		SLTNode* p = *pphead;
//		while (p->next) {
//			p = p->next;
//		}
//		
//		p->next = newnode;
//	}
//}
//
//
//void SLTPopBack(SLTNode** pphead) {
//
//	//链表为空就直接退出
//	if (*pphead == NULL) {
//		return;
//	}
//
//	//
//	if ((*pphead)->next == NULL) {
//		free(*pphead);
//		*pphead = NULL;
//	}
//	else 
//	{
//		SLTNode* p = *pphead;
//		while (p->next->next) {
//			p = p->next;
//		}
//		free(p->next);
//		p->next = NULL;
//	}
//}
//
//
//
//void SLTPushFront(SLTNode** pphead, SLTDataType x) {
//	SLTNode* newnode = BuySLTNode(x);
//	newnode->next = *pphead;
//	*pphead = newnode;
//}
//
//
//void SLTPopFront(SLTNode** pphead) {
//	assert(pphead);
//
//	if (*pphead == NULL)return;
//	
//	SLTNode* p = (*pphead)->next;
//	free(*pphead);
//	*pphead = p;
//	
//}SLTNode* SListFind(SLTNode* plist, SLTDataType x) {SLTNode* p = plist;while (p) {if (p->data == x)return p;p = p->next;}return NULL;
}
//
//void SListInsertAfter(SLTNode* pos, SLTDataType x) {
//	assert(pos);
//	
//	
//	SLTNode* p = pos->next;
//	SLTNode* newnode = BuySLTNode(x);
//	pos->next = newnode;
//	newnode->next = p;
//
//	/*
//	 SLTNode* newnode = BuySLTNode(x);
//	 newnode->next=pos->next;
//	 pos->next=newnode;
//	*/
//}void SListInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x) {assert(pos);SLTNode* newnode = BuySLTNode(x);if (*pphead == pos) {newnode->next = *pphead;*pphead = newnode;}else {SLTNode* p = *pphead;while (p->next != pos) {p = p->next;}p->next = newnode;newnode->next = pos;}
}//void SListEraseAfter(SLTNode* pos) 
//{
//	assert(pos);
//	if (pos->next)return;
//	else {
//		SLTNode* q = pos->next;
//		pos->next = q->next;
//		free(q);
//
//	}
//
//}void SListErase(SLTNode** pphead, SLTNode* pos) {assert(pos);assert(*pphead);if (*pphead == pos){SLTNode* p = (*pphead)->next;free(*pphead);*pphead = p;}else {SLTNode* pre = *pphead;while (pre->next!=pos) {pre = pre->next;}pre->next = pos->next;free(pos);}
}void SLTDestroy(SLTNode** pphead) {SLTNode* p = *pphead;while (p) {SLTNode* next = p->next;free(p);p = next;}*pphead = NULL;
}

http://chatgpt.dhexx.cn/article/0S53Enrc.shtml

相关文章

单链表的常用算法

目录 一、判断链表是否为空 二、单链表的销毁&#xff1a;链表销毁后不存在 三、清空单链表&#xff1a;链表仍存在&#xff0c;但链表中无元素&#xff0c;成为空链表(头指针和头结点仍然在) 四、求单链表的表长 五、单链表的取值 六、单链表的按值查找 七、单链表的插…

线性表的链式存储:单链表的相关介绍(插入、删除、查找等)头节点和头指针的区别

一、链式存储 用一组地址任意的存储单元&#xff08;地址可以连续也可以不连续&#xff09;&#xff0c;依次存储线性表中的各数据元素。 链式存储结构中的每个存储单元称为“结点”&#xff0c;结点包含一个数据域和一个指针域。 数据元素之间的逻辑关系通过结点中的指针表示 …

单链表的简单讲解

注意&#xff1a;本人也是小白&#xff0c;如果出现错误希望各位读者能够包容 文章目录 前言一、单链表的结构定义二、单链表的基本操作1.单链表的初始化2.单链表的创建1.头插法头插法图片讲解 2.尾插法尾插法图片讲解 3.头插法和尾插法的对比 3.单链表的输出4.单链表的插入图片…

线性表之单链表~

这次来介绍一下数据结构里面的单链表。 这是一个简单的链表&#xff0c;它是单向的&#xff0c;没有(哨兵位置)头结点&#xff0c;不循环&#xff0c;有一个头指针指向第一个节点。 和上次介绍线性表一样&#xff0c;我们同样研究它的 增&#xff0c;删&#xff0c;查&#x…

链表(Linked List)----单链表

链表是有序的列表,但是它在内存中是存储如下的: 链表是以节点的方式存储的,链式存储每个节点包含data域,next域指向下一个节点如上图:链表的各个节点不一定是连续存放的.链表分带头节点的链表和没有头节点的链表(根据实际的需求来确定) 使用带head头的单项链表实现-水浒英雄排行…

单链表的插入与删除

链表是数据结构中的一种线性结构&#xff0c;表示数据运算之间的一种抽象关系。 1、单链表的结构如下&#xff1a; typedef struct node{datatype data; //数据struct node *next; //指向下一个节点的指针 }linklist_s; 其中包括一个数据域和一个指针域&#xff0c;向单链表…

详解单链表(内有精美图示哦)

全文目录 引言链表链表的定义与结构链表的分类 单链表的实现及对数据的操作单链表的创建与销毁创建销毁 单链表的打印单链表的头插与头删头插头删 单链表的尾插与尾删尾插尾删 单链表的查找单链表在pos位置后插入/删除插入删除 单链表在pos位置插入/删除插入删除 总结 引言 在…

【数据结构】单链表(超全)

目录 一、什么是链表&#xff1f;1.1 定义1.2 链表的分类 二、无头单向非循环链表2.1 结构2.2 如何遍历链表数据2.3 尾插2.4 创建新节点2.5 头插2.6 尾删2.7 头删2.8 单链表查找2.9 在pos位置之前插入2.10 删除pos位置数据2.11 在pos位置的后面插入2.12 删除pos位置后面的数据 …

数据结构之单链表的插入

单链表的完整的代码在这篇文章下面&#xff0c;链接&#xff1a; https://blog.csdn.net/six_teen/article/details/113253545 什么是链表&#xff1f; 如上图&#xff0c;链表和生活中的链条很相似&#xff0c;链表在逻辑结构上是以链条的形式连接而成的&#xff0c;但物理结构…

单链表的定义,插入与删除,查找,建立。

链表分为&#xff1a;单链表&#xff0c;双链表&#xff0c;循环链表&#xff0c;静态链表 一&#xff0c;单链表的定义 在内存空间中&#xff0c;各个节点在逻辑上相邻&#xff0c;但在物理上不相邻。 在单个的结点内部需要存放 数据域 和 指针域&#xff08;存放指向下一个…

单链表的定义

前言 在前面的文章中&#xff0c;我们系统的介绍了线性表的顺序存储实现——顺序表。紧接着我们要介绍线性表的链式存储实现——链表。而链表中又有许多的链表&#xff1a; 单链表双链表循环链表静态链表 这一篇文章中&#xff0c;我们先来介绍单链表。 单链表的定义 什么…

(c语言)详解单链表

1&#xff1a;什么是单链表。 我们知道顺序表底层原理其实就是一块可以自由控制大小的数组&#xff0c;顺序表可以实现在任何地方进行插入一个数据&#xff0c;如果顺序表的缺点在于如果要在起始位置插入一个数据就要把后面的每一个数据都往后挪&#xff0c;这样会大量消耗我们…

双向链表中插入元素的几种方式

dnode的结构如下&#xff1a;由前驱prior指针、后继next指针以及数据data&#xff0c;现需要在A、B节点中间插入C节点&#xff0c;给出了A的地址&#xff0c;以及C的地址。 1、利用将链表拆分然后插入方式进行&#xff1a; 先将节点C完全插入到B的前面&#xff0c;再将A指向C&…

单链表的使用方法.数据结构(三)[上]

前言 提示&#xff1a;文本为数据解构(三)单链表&#xff1a; 本文具体讲解单链表的具体使用方法 提示&#xff1a;以下是本篇文 系列文章目录 第一章 数据解构(一) 第二章 顺序表的具体使用方法.数据解构(二) 文章目录 前言 系列文章目录 文章目录 一、单链表视图 二、…

Linux设置ssh免密登录

目录 1.在/root目录下输入命令 2.进入.ssh目录 3.将公钥id_rsa.pub写入到一个认证文件夹中 4.开启远程免密登录配置 5.免密远程登录本机 1.在/root目录下输入命令 [rootlocalhost ~]# ssh-keygen -t rsa -P "" Generating public/private rsa key pair. Enter …

Linux SSH 免密登录

Linux SSH 免密登录 本篇我们来 看看 Linux 的免密登录的原理 以及实际操作一番 概述 什么是 Linux SSH 免密登录&#xff0c;我觉得大家应该都 多少听过 或者操作过&#xff0c;那你真的理解整个免密登录的过程吗&#xff1f; Linux SSH 免密登录 就是 可以不输入密码 就可以…

SSH登录和SSH免密登录

在了解ssh的时候产生了概念混淆&#xff0c;发现ssh登录和ssh免密登录是两码事。 可以从目的和过程对比这两个概念&#xff1a; 1.目的 1.1 SSH登录 简单来说就是&#xff1a;建立客户端和服务器之间安全的远程连接&#xff0c;登录远程服务器&#xff0c;以访问文件系统 。…

VSCode——SSH免密登录

文章目录 本地PC端&#xff08;一般为Windows&#xff09;1. 检查自己是否已经生成公钥2. 配置VScode的SSH config 远程服务器端1. 服务器新建授权文件2. 赋权限3. 重启远程服务器的ssh服务 最全步骤&#xff1a;【设置ssh免密不起作用&#xff1f;彻底搞懂密钥】vscode在remot…

SSH免密登录功能配置

文章目录 一、SSH免密登录功能配置&#xff08;1&#xff09;master虚拟机免密登录master虚拟机&#xff08;2&#xff09;将公钥拷贝到所创建的虚拟机上 一、SSH免密登录功能配置 ssh密钥登录比密码登录安全&#xff0c;主要是因为他使用了非对称加密&#xff0c;登录过程中需…

CentOS开启SSH免密登录

CentOS开启SSH免密登录 要实现SSH免密登录&#xff0c;首先需要准备一组公钥和私钥。将公钥放到服务器上&#xff0c;将私钥放到客户机上。当客户机连接服务器时&#xff0c;服务器会根据自身的公钥校验客户机的私钥&#xff0c;如果校验通过则允许连接。 一、创建密钥 在客…