初阶数据结构之单链表详解

目录

一:单链表概念

二:单链表的基本操作

1.定义结点

2.创建链表(初始化链表)

3:新增结点

4.单链表尾插

 5.单链表头插

6.单链表尾删

7:单链表头删

8.打印单链表

9.查找单链表结点

10.单链表删除指定结点

11.单链表结点修改

12.单链表结点前插入结点

三:代码总结

SL List.h

SL Lish.c


开门见山,直接开始讲解。(如有错误请指出,一定改正)

如果对你有所帮助的话,不妨点个赞再走。

                      

一:单链表概念

单链表 是一种线性数据结构,其特点是数据元素以节点的形式存储在内存中,这些节点在物理上可能是不连续的(他们的地址可能离的很远)。每个节点由一个数据域和一个指针域组成,数据域用于存储数据,而指针域包含了指向下一个节点的指针(即他们在逻辑上是连续的)。

图示:

同时,单链表可以分为带头结点和不带头结点两种。带头结点的单链表通常有一个头结点,它的指针域指向链表的第一个有效节点(首元节点),这样的设计便于进行一些特殊操作,例如删除所有节点同时保留链表。不带头结点的单链表直接从第一个有效节点开始,没有额外的头结点。

如上图的plist就是链表的头节点。

二:单链表的基本操作

1.定义结点

在实现单链表的操作之前,我们应该把结点定义出来,已知结点内存储结点数据(data)和下一个结点的地址(即一个指向下一个结点的指针),因此我们可以用结构体来定义结点。

//定义结点
typedef int SLTDataType;
typedef struct SListNode
{SLTDataType data;//数据域struct SListNode *next;//指针域}SLNode;  

2.创建链表(初始化链表)

可以把链表初始化为两种形式。一种是带头结点的,一种是不带头节点的。

带头结点:

void initList(SLNode*head)//head为单链表头节点的指针
{assert(head);head = (SLNode*)malloc(sizeof(SLNode));为头节点开辟空间head->next = NULL;
}

不带头结点:

void InitList(SLNode * head)
{assert(head);head=NULL;
}

3:新增结点

当我们在进行单链表的尾插和头插操作时,总要新增一个结点(即把新增加的数据转换成单链表结点类型),因此我们可以单独写一个新增结点的代码实现,当我们进行插入操作时直接引用即可。

//单链表的新增节点SLNode* AddList(SLTDataType x)//x为新增的结点数据
{SLNode* newnode = (SLNode*)malloc(sizeof(SLNode));为新节点开辟空间if (newnode == NULL){perror("开辟结点空间失败");return NULL;}newnode->data = x;newnode->next = NULL;return newnode;
}

4.单链表尾插

单链表尾插入结点之前,最后的一个结点是指向NULL的,因此只需要把最后一个结点由原来的指向NULL改为指向新插入的节点即可。

//单链表尾插结点
void PushBackList(SLNode**head,SLTDataType x)
{assert(head);SLNode* newnode = AddList(x);if (*head == NULL){*head= newnode;}else{SLNode* tail = *head;while (tail->next){tail = tail->next;}tail->next = newnode;}
}

关于这里为什么不传 SLNode*head 而是传 SLNode**head ?在此做一个解释。

首先,在主函数中创建一个指向头节点地址的结构体指针head:SLNode*head。

注意:地址就是指针,指针就是地址。

例如:head既是头结点head的地址,也是指向头节点head的指针。

那么此时我们如果传 head ,实际传给函数的就是head的地址,然后函数用SLNode*head这个一级指针来接收,并且只有当我们要对head的实际值进行修改等操作的时候才需要传给函数他的地址。

而如果我们传的是 &head(对指针取地址),那么我们实际传过去的就是 (指向head的指针的指针)也可以叫做(指向head地址的指针的地址),然后函数需要用SLNode **head这个二级指针来接收,并且只有当我们想要对(head的地址)或者说(指向head的指针)进行修改等操作的时候才需要传给函数&head,在函数中对地址进行操作不影响实际数据。

那么我们的插入操作实际上是要对谁进行操作的呢?

插入操作实际上是对指针(地址)进行操作,例如上面的代码中的 *head=newnode,就是把(新建立结点的地址)赋给了(指向head地址的指针),此时head指向的是newnode的地址。

因此,总的来说,是因为我们想要对指针(地址)进行操作,所以才用二级指针来接收。

希望你们可以理解哈哈。

 5.单链表头插

单链表头插就是创建一个新的链表结点,然后把让链表结点指向原来的头节点即可。

//单链表头插结点
void PushFrontList(SLNode** head, SLTDataType x)
{assert(head);SLNode*newnode=AddList(x);SLNode*newhead = newnode;newnode->next =*head;*head= newhead;
}

6.单链表尾删

单链表尾删就是把单链表最后一个结点空间free掉,然后让单链表倒数第二个结点指向NULL。

但是需要注意两点。

(一):assert进行断言时,不仅需要断言head还要断言*head。

 断言*head是为了确保指向head地址不为空,即断言指向头结点的的指针是否存在(链表是否为空)。

(二):尾删结点时要区分情况,一种是原链表只有1个结点的情况,一种是链表有多个结点的情况,因为如果链表只有一个结点,那么 while(tail->next)为空会导致进不去循环,导致头节点不能被删除。

//单链表尾删结点
void DelBackList(SLNode** head)
{assert(head&&*head);if ((*head)->next == NULL){free(*head);*head = NULL;}else{SLNode* tail = *head;SLNode* prev = NULL;//创建prev用来保存倒数第二个结点的指针while (tail->next){prev = tail;tail = tail->next;}free(tail);tail=NULL;prev->next = NULL;}
}

7:单链表头删

单链表头删其实就是把指向头节点的指针指向第二个结点,然后把原本的头节点的空间释放掉,那么第二个结点就成为了头节点。

//单链表头删
void DelFrontList(SLNode** head)
{ assert(head && *head);SLNode* agohead = *head;*head = (*head)->next;free(agohead);agohead = NULL;}

8.打印单链表

打印单链表十分简单,只需传单链表头节点的地址然后写个循环即可。

//打印单链表
void PrintList(SLNode* head)
{SLNode* p = head;while (p!=NULL){printf("%d ", p->data);p= p->next;}
}

9.查找单链表结点

查找单链表结点就是输入结点的地址,然后遍历链表查找结点即可。

注意:因为我们只是对链表结点的地址进行比较,此时只需传head,用SLNode*head接收即可。

如果找到就返回结点的地址,如果没有找到就返回NULL。

//单链表查找数据
SLNode* FindList(SLNode* head, SLTDataType x)
{SLNode* find = head;while (find){if (find->data == x){printf("找到了");return find;}find = find->next;}printf("没找到");return NULL;
}

10.单链表删除指定结点

既然是要删除指定结点,那实际上是对结点地址进行删除,然后free空间,因此也是传&head,用SLNode**head接收。

//单链表删除指定位置结点
void EraseList(SLNode** head, SLNode* pos)
{assert(head && pos);if (pos == *head)//如果链表只有一个结点,则直接引入头删函数{DelFrontList(head);}else{SLNode* prev = *head;SLNode* phead= *head;while (phead != NULL){ if(phead==pos){phead->next=prev->next;}prev=phead;phead=phead->next;}}
}

11.单链表结点修改

结点修改十分简单,只需找到结点然后修改他的data即可。

//单链表结点修改
void ModifyList(SLNode* head, SLNode* pos, SLTDataType x)
{assert(head && pos);SLNode* phead = head;while (phead != pos){phead = phead->next;}phead->data=x;
}

12.单链表结点前插入结点

需要分情况讨论。

一种为链表中的头节点,这个结点刚好就是想要插入的那个结点,那么只需直接调用单链表头插函数即可。

另一种情况就是要寻找的结点在其他的位置。

//单链表结点前插
void InsertHeadList(SLNode** head, SLNode* pos, SLTDataType x)
{assert(head && pos);SLNode* prev = *head;SLNode* phead = *head;if (phead == pos){PushFrontList(phead,x);}else{while (phead != pos){prev = phead;phead = phead->next;}prev->next = AddList(x);AddList(x)->next = phead;}
}

13.单链表结点后插入结点

//单链表结点后插
void InsertBackList(SLNode** head, SLNode* pos, SLTDataType x)
{assert(head && pos);SLNode* phead = *head;SLNode* newnode = AddList(x);while (phead != pos){phead = phead->next;}newnode->next = phead->next->next;newnode = phead->next;}

14.单链表销毁

因为链表在逻辑结构上并不是连续的,因此我们需要一个节点一个结点地去销毁。

//销毁单链表
void DesTructList(SLNode** head)
{assert(head);SLNode* phead = *head;while (phead != NULL){SLNode* next = phead->next;free(phead);phead=next;}*head = NULL;}

三:代码总结

SL List.h

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>//定义结点
typedef int SLTDataType;
typedef struct SListNode
{SLTDataType data;//数据域struct SListNode *next;//指针域}SLNode;  //单链表初始化
void InitList(SLNode* head);//单链表的新增节点(把新增的数据转换为结点结构体类型)
SLNode* AddList(SLTDataType x);//单链表尾插结点
void PushBackList(SLNode**head,SLTDataType x);//单链表头插结点
void PushFrontList(SLNode**head, SLTDataType x);//打印单链表
void PrintList(SLNode* head);//单链表尾删结点
void DelBackList(SLNode** head);//单链表头删
void DelFrontList(SLNode** head);//单链表查找数据
SLNode* FindList(SLNode* head,SLTDataType x);//单链表删除指定位置结点
void EraseList(SLNode** head, SLNode* pos);//单链表结点前插
void InsertHeadList(SLNode** head, SLNode* pos,SLTDataType x);//单链表结点后插
void InsertBackList(SLNode**head,SLNode*pos,SLTDataType x);//单链表结点修改
void ModifyList(SLNode** head, SLNode* pos, SLTDataType x);//销毁单链表
void DesTructList(SLNode** head);

SL Lish.c

#include"SL List.h"//单链表初始化
void InitList(SLNode* head)//head为单链表头节点的指针
{assert(head);head = NULL;
}//单链表的新增节点
SLNode* AddList(SLTDataType x)//x为新增的结点数据
{SLNode* newnode = (SLNode*)malloc(sizeof(SLNode));if (newnode == NULL){perror("开辟结点空间失败");return NULL;}newnode->data = x;newnode->next = NULL;return newnode;
}//单链表尾插结点
void PushBackList(SLNode** head, SLTDataType x)
{assert(head);SLNode* newnode = AddList(x);if (*head == NULL){*head = newnode;}else{SLNode* tail = *head;while (tail->next){tail = tail->next;}tail->next = newnode;}
}//单链表头插结点
void PushFrontList(SLNode** head, SLTDataType x)
{assert(head);SLNode* newnode = AddList(x);SLNode* newhead = newnode;newnode->next = *head;*head = newhead;
}//打印单链表
void PrintList(SLNode* phead)
{SLNode* p = phead;while (p!=NULL){printf("%d ", p->data);p= p->next;}
}//单链表尾删结点
void DelBackList(SLNode** head)
{assert(head && *head);if ((*head)->next == NULL){free(*head);*head = NULL;}else{SLNode* tail = *head;SLNode* prev = NULL;//创建prev用来保存倒数第二个结点的指针while (tail->next){prev = tail;tail = tail->next;}free(tail);tail = NULL;prev->next = NULL;}
}//单链表头删
void DelFrontList(SLNode** head)
{assert(head && *head);SLNode* agohead = *head;*head = (*head)->next;free(agohead);agohead = NULL;}//单链表查找数据
SLNode* FindList(SLNode* head, SLTDataType x)
{SLNode* find = head;while (find){if (find->data == x){printf("找到了");return find;}find = find->next;}printf("没找到");return NULL;
}//单链表删除指定位置结点
void EraseList(SLNode** head, SLNode* pos)
{assert(head && pos);if (pos == *head)//如果链表只有一个结点,则直接引入头删函数{DelFrontList(head);}else{SLNode* prev = *head;SLNode* phead = *head;while (phead != NULL){if (phead == pos){phead->next = prev->next;}prev = phead;phead = phead->next;}}
}//单链表结点前插
void InsertHeadList(SLNode** head, SLNode* pos, SLTDataType x)
{assert(head && pos);SLNode* prev = *head;SLNode* phead = *head;if (*head == pos){PushFrontList(head, x);}else{while (phead != pos){prev = phead;phead = phead->next;}prev->next = AddList(x);AddList(x)->next = phead;}
}//单链表结点后插
void InsertBackList(SLNode** head, SLNode* pos, SLTDataType x)
{assert(head && pos);SLNode* phead = *head;SLNode* newnode = AddList(x);while (phead != pos){phead = phead->next;}newnode->next = phead->next->next;newnode = phead->next;}//单链表结点修改
void ModifyList(SLNode** phead, SLNode* pos, SLTDataType x)
{assert(phead && pos);SLNode* pa = *phead;while (pa != pos){pa = pa->next;}pa = AddList(x);
}//销毁单链表
void DesTructList(SLNode** head)
{assert(head);SLNode* phead = *head;while (phead != NULL){SLNode* next = phead->next;free(phead);phead=next;}*head = NULL;}

至此,初阶数据结构之单链表已讲解完毕。

完结撒花。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/8743.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Centos7 安装 MySQL5.7 使用 RPM 方式

1 访问网站 https://downloads.mysql.com/archives/community/ 选择合适的版本&#xff0c;点击 Download。 2 上传下载好的 mysql-5.7.44-1.el7.x86_64.rpm-bundle.tar 文件到 Centos7 机器&#xff0c;这里放到了 下载 目录。 3 解压 mysql-5.7.44-1.el7.x86_64.rpm-bundle.…

JS笔试手撕题

数据劫持 Vue2的Object.defineProperty() Vue2的响应式是通过Object.defineProperty()拦截数据&#xff0c;将数据转换成getter/setter的形式&#xff0c;在访问数据的时候调用getter函数&#xff0c;在修改数据的时候调用setter函数。然后利用发布-订阅模式&#xff0c;在数…

基于PSO粒子群优化的配电网可靠性指标matlab仿真

目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.本算法原理 4.1 PSO算法应用于配电网优化的基本原理 5.完整程序 1.程序功能描述 基于PSO粒子群优化的配电网可靠性指标matlab仿真&#xff0c;指标包括saifi, saidi, caidi, aens四个。 2.测试软件版本…

深度学习--DCGAN

代码之后的注释和GAN的一样&#xff0c;大家如果已经掌握GAN&#xff0c;可以忽略掉哦&#xff01;&#xff01;&#xff01; 在学习DCGAN之前&#xff0c;我们要先掌握GAN&#xff0c;深度学习--生成对抗网络GAN-CSDN博客 这篇博客讲的就是GAN的相关知识&#xff0c;还是很详…

POST请求

1、代码 import urllib.request import urllib.parse# 指定 URL url https://fanyi.baidu.com/sug# POST 请求携带的参数进行处理流程&#xff1a; # 1. 将 POST 请求参数封装到字典 data {kw: 西瓜 }# 2. 使用 parse 模块中的 urlencode 进行编码处理 data urllib.parse.u…

【JAVA进阶篇教学】第十篇:Java中线程安全、锁讲解

博主打算从0-1讲解下java进阶篇教学&#xff0c;今天教学第十篇&#xff1a;Java中线程安全、锁讲解。 当涉及到多线程编程时&#xff0c;保证线程安全是至关重要的。线程安全意味着在多个线程访问共享资源时&#xff0c;不会发生数据错乱或不一致的情况。为了实现线程安全&am…

JavaScript异步编程——05-回调函数

我们在前面的文章《JavaScript 基础&#xff1a;异步编程/单线程和异步》中讲过&#xff0c;Javascript 是⼀⻔单线程语⾔。早期我们解决异步场景时&#xff0c;⼤部分情况都是通过回调函数来进⾏。 &#xff08;如果你还不了解单线程和异步的概念&#xff0c;可以先去回顾上一…

【Redis7】10大数据类型之Zset类型

文章目录 1.Zset类型2.常用命令3.示例3.1 ZADD,ZRANGE和ZREVRANGE3.2 ZSCORE,ZCARD和ZREM3.3 ZRANGEBYSCORE和ZCOUNT3.4 ZRANK和ZREVRANK3.5 Redis7新命令ZMPOP 1.Zset类型 Redis的Zset&#xff08;Sorted Set&#xff0c;有序集合&#xff09;是一种特殊的数据结构&#xff0…

Encoder——Decoder工作原理与代码支撑

神经网络算法 &#xff1a;一文搞懂 Encoder-Decoder&#xff08;编码器-解码器&#xff09;_有编码器和解码器的神经网络-CSDN博客这篇文章写的不错&#xff0c;从定性的角度解释了一下&#xff0c;什么是编码器与解码器&#xff0c;我再学习笔记补充的时候&#xff0c;讲一下…

TMS320F28335学习笔记-时钟系统

第一次使用38225使用了普中的clocksystem例程进行编译&#xff0c;总是编译失败。 问题一&#xff1a;提示找不到文件 因为工程的头文件路径没有包含&#xff0c;下图的路径需要添加自己电脑的路径。 问题二 找不到库文件 例程种的header文件夹和common文件夹不知道从何而来…

【Alluxio】文件系统锁模型之InodeLockList

InodeLockList接口,表示在inode tree里一个加了锁的路径。 沿着path,inodes和edges都被加锁了。path可能从edge或inode任意一个开始。 锁列表总是包含了一定数量的读锁(0个或多个),随后跟随着一些数量的写锁(0个或多个)。 举个例子: 对 /a/b/c/d 进行加锁,c->d这…

【深度学习】网络安全,SQL注入识别,SQL注入检测,基于深度学习的sql注入语句识别,数据集,代码

文章目录 一、 什么是sql注入二、 sql注入的例子三、 深度学习模型3.1. SQL注入识别任务3.2. 使用全连接神经网络来做分类3.3. 使用bert来做sql语句分类 四、 深度学习模型的算法推理和部署五、代码获取 一、 什么是sql注入 SQL注入是一种常见的网络安全漏洞&#xff0c;它允许…

【进程间通信】共享内存

文章目录 共享内存常用的接口指令利用命名管道实现同步机制总结 System V的IPC资源的生命周期都是随内核的。 共享内存 共享内存也是为了进程间进行通信的&#xff0c;因为进程间具有独立性&#xff0c;通信的本质是两个不同的进程看到同一份公共资源&#xff0c;所以共享内存…

Java 11 到 Java 8 的兼容性转换

Java 11 到 Java 8 的兼容性转换 欲倚绿窗伴卿卿&#xff0c;颇悔今生误道行。有心持钵丛林去&#xff0c;又负美人一片情。 静坐修观法眼开&#xff0c;祈求三宝降灵台&#xff0c;观中诸圣何曾见&#xff1f;不请情人却自来。 入山投谒得道僧&#xff0c;求教上师说因明。争奈…

WordPress MasterStudy LMS插件 SQL注入漏洞复现(CVE-2024-1512)

0x01 产品简介 WordPress和WordPress plugin都是WordPress基金会的产品。WordPress是一套使用PHP语言开发的博客平台。该平台支持在PHP和MySQL的服务器上架设个人博客网站。WordPress plugin是一个应用插件。 0x02 漏洞概述 WordPress Plugin MasterStudy LMS 3.2.5 版本及之…

java项目之在线课程管理系统源码(springboot+vue+mysql)

风定落花生&#xff0c;歌声逐流水&#xff0c;大家好我是风歌&#xff0c;混迹在java圈的辛苦码农。今天要和大家聊的是一款基于springboot的在线课程管理系统。项目源码以及部署相关请联系风歌&#xff0c;文末附上联系信息 。 项目简介&#xff1a; 在线课程管理系统的主要…

Nginx配置/.well-known/pki-validation/

当你需要在Nginx上配置.well-known/pki-validation/时&#xff0c;这通常是为了支持SSL证书的自动续订或其他验证目的。以下是配置步骤&#xff1a; 创建目录结构&#xff1a; 在你的网站根目录下创建一个名为.well-known的目录&#xff08;SSL证书申请之如何创建/.well-known/…

Linux环境Redis部署

Redis部署 Redis是一个高性能的开源键值存储系统&#xff0c;它主要基于内存操作&#xff0c;但也支持数据的持久化。与其他数据库相比&#xff0c;Redis的主要优势在于它的高性能、丰富的数据结构和原生的持久化能力。Redis不仅提供了类似的功能&#xff0c;还增加了持久化和…

[初阶数据结构】单链表

前言 &#x1f4da;作者简介&#xff1a;爱编程的小马&#xff0c;正在学习C/C&#xff0c;Linux及MySQL。 &#x1f4da;本文收录于初阶数据结构系列&#xff0c;本专栏主要是针对时间、空间复杂度&#xff0c;顺序表和链表、栈和队列、二叉树以及各类排序算法&#xff0c;持…

如何使用client-go构建pod web shell

代码示例及原理 原理是利用websocket协议实现对pod的exec登录&#xff0c;利用client-go构造与远程apiserver的长连接&#xff0c;将对pod容器的输入和pod容器的输出重定向到我们的io方法中&#xff0c;从而实现浏览器端的虚拟终端的效果消息体结构如下 type Connection stru…