将单链表反转【数据结构练习题】
- 第 98 篇 -
Date: 2025 - 05 - 16
Author: 郑龙浩/仟墨
反转单链表(出现频率非常的高)
文章目录
- 反转单链表(出现频率非常的高)
- 题目:反转一个链表
- 思路:
- 代码实现(第3种思路):
题目:反转一个链表
将 1->2->3->4->5->NULL
反转变为 5->4->3->2->1->NULL
思路:
三种思路
- 新建一个新的链表,不断头插
- 利用递归
- 将NULL放到1的前边,然后再将指向反转(比如先
NULL<-1->2->3->4->5
再NULL<-1<-2->3->4->5
,直到NULL<-1<-2<-3<-4<-5
)
代码实现(第3种思路):
-
test.c
// 反转链表 #define _CRT_SECURE_NO_WARNINGS #include "SListNode.h" // 反转链表 void reversal(SListNode** pphead, int len) {// 三个 “指针”,分别指向三个连着的结点 初始如下:SListNode *previous = NULL, *current = *pphead, *next = (*pphead)->next;// 情况:1 无结点或只有一个结点 2有大于1个的结点 // 无结点 / 只有一个结点 就直接退出if (current == NULL || next == NULL) {return;}// 链表反转 while (next != NULL) {next = current->next; // **重要!!** 提前存储current的后驱结点(因为当前结点指向前原-来的前驱结点以后,当前结点会丢失原来的后驱结点,也就是原后驱结点的地址会丢失,所以要提前储存,当前结点指向前驱结点以后,让next指针存储)current->next = previous; // 当前结点指向前一个结点// 指针向后移previous = current;current = next;}*pphead = previous; // 头节点指向原来的最后一个结点 } int main(void) {SListNode* L = NULL;int len = 0; // 链表长度printf("请输入链表的内容:\n");scanf("%d", &len);// 插入 链表内容for (int i = 1; i <= len; i++) {ElemType n;scanf("%d", &n);SListPushBack(&L, n);}SListPrint(L); // 打印链表// 反转链表reversal(&L, len);SListPrint(L);return 0; }
-
SListNode.c
#define _CRT_SECURE_NO_WARNINGS #include "stdio.h" #include "SListNode.h" #include "stdlib.h" SListNode* BuySListNode(ElemType x) {// 申请一个新的结点SListNode* NewNode = (SListNode*)malloc(sizeof(SListNode)); // 为新节点申请内存空间// 养成习惯:判断申请内存是否成功 虽然大概是成功的,但是还是要养成这个习惯if (NewNode == NULL) {printf("申请内存失败!\n");exit(-1);}NewNode->data = x; // 新节点 存入数据xNewNode->next = NULL; // 新节点 指向NULL(初始化),避免越界访问到其他地址return NewNode; } // 打印 void SListPrint(SListNode* phead) {SListNode* current = phead; // 当前结点的“地址”while (current != NULL) {printf("%d -> ", current->data); // 打印当前结点中的‘data’current = current->next; // 指向下一个“结点”}printf("NULL\n"); } // 获取链表宽度(结点数量) size_t SlistSize(SListNode* phead) {size_t len = 0; // 长度初始化为0SListNode* current = phead; // 遍历链表while (current != NULL) { // 当前节点不为空时计数len++; // 结点数量++current = current->next; // 当前结点指向下一个结点}return len; // 返回链表长度 } // 头插 void SListPushFront(SListNode** pphead, ElemType x) {SListNode* NewNode = BuySListNode(x); // 创建新的结点NewNode->next = *pphead; // 让新的结点指向“原来的第一个结点”*pphead = NewNode; // 让存放第一个结点地址的*pphead变成存放新结点的地址,也就是让第一个结点变为新的结点 NewNode } // 头删 void SListPopFront(SListNode** pphead) {// 三种情况: 1 链表为空 2 链表只有一个结点 3 链表有大于1个的结点if (*pphead == NULL) { // 链表为空,表示无任何结点,则不进行删除操作return;}else if ((*pphead)->next == NULL) { // 链表只有一个结点free(*pphead); // 释放第一个结点的内存空间,将内存还给操作系统*pphead = NULL; // *pphead = NULL; // 将头指针置空,避免指向已释放的内存(野指针)}else {SListNode* next = (*pphead)->next; // 保护第二个结点,避免释放第一个结点内存后找不到第二个结点free(*pphead); // 释放第一个结点*pphead = next; // 让头指针指向第二个结点} } // 尾插 void SListPushBack(SListNode** pphead, ElemType x) {SListNode* NewNode = BuySListNode(x); // 先开辟一个新的结点// 判断第一个结点是否为空,若为空,则开辟空间(也就是该链表没有任何数据)if (*pphead == NULL) { // 若节点为空*pphead = NewNode; // 让第一个结点指向新的结点}else {// 找到最后一个结点SListNode* tail = *pphead; // tail 翻译:尾// 找尾结点while (tail->next != NULL) { // 只要当前结点指向NULL,就表示没有下一个结点了,就表示了当前结点就是最后一个结点tail = tail->next;}tail->next = NewNode; // 将原链表的最后一个结点指向新的节点} } // 尾删 void SListPopBack(SListNode** pphead) {// 三种情况:1 链表为空(头结点指向 NULL,也就是无结点) 2 只有一个结点 3 有 多于 1 个的结点if (*pphead == NULL) {return;}else if ((*pphead)->next == NULL) {free(*pphead); // 释放第一个结点的内存*pphead = NULL; // *pphead = NULL; // 将头指针置空,避免指向已释放的内存(野指针)}else {SListNode* previous = NULL; // taile的前驱结点 最终找到的是倒数第二个结点SListNode* tail = *pphead; // previous的后驱结点 最终找到的是最后一个结点while (tail->next != NULL) { // 寻找 最后结点previous = tail; // 指向后指针tail = tail->next; // 指向下一个结点}free(tail); // 释放最后一个结点的内存空间previous->next = NULL; // 让倒数第二个结点指向NULL,而非最后一个结点,因为最后一个结点已经是“野指针”} } // 查找 返回存放x的结点的地址(一级指针) SListNode* SListFind(SListNode* phead, ElemType x) {SListNode* current = phead; // 遍历链表while (current != NULL && current->data != x) { // 如果结点非空且当前结点的data不是x,,则找下一个current = current->next; // 找到下一个结点}return current; // current 会有两种情况,一种是存放x的结点,一种是没找到存放NULL空地址 } // 在pos位置之前插入x void SListInsert(SListNode** pphead, SListNode* pos, ElemType x) {// 1 pos 是否合法 2 pos 是否指向第一个结点 3 pos 指向第一个以后的结点if (pos == NULL) { // 地址不对printf("pos地址为空地址,不合法\n");return;}if (*pphead == pos) { // pos 指向第一个结点SListPushFront(pphead, x); // 直接尾插一个xreturn;}SListNode* previous = *pphead; // pos// 找到前驱结点while (previous != NULL && previous->next != pos) {previous = previous->next; // 指向下一个结点} // previous 在退出循环的时候的两种情况:1 找到前驱 2 没找到,存NULL指针// 插入xif (previous != NULL) { // 找到了SListNode* NewNode = BuySListNode(x); // 申请一个新的结点NewNode->next = pos; // 新的结点指向 pos 结点previous->next = NewNode; // pos的前驱结点指向新的结点}else {printf("没找到!\n");return;} } // 删除pos位置的值 void SListErase(SListNode** pphead, SListNode* pos) {// 情况:1 链表为空且pos指向第一个结点 2 pos 是否合法 3 pos 是否指向第一个结点 4 pos 指向第一个以后的结点 if (*pphead == NULL && pos == *pphead) { // 若没结点 且 pos 指向空则不删除return;}// 如果pos指向空,则结点的地址不合法if (pos == NULL) {printf("pos地址为空,不合法!");return;}// 如果pos指向第一个结点if (pos == *pphead) {SListPopFront(pphead); // 直接头删return;}SListNode* previous = *pphead; // pos 的前驱结点// 找到 pos 的前驱结点while (previous != NULL && previous->next != pos) {previous = previous->next; // 指向下一个结点}if (previous == NULL) {printf("未找到要删除的结点\n");return;}previous->next = pos->next; // 让 pos 前驱节点指向 pos 的后驱结点free(pos); // 释放pos结点的内存空间 } // pos 后面插入的话就无需知道pos前一个结点了,也就无需传输“第一个结点的地址去查找pos前一个结点了” void SListInsertAfter(SListNode* pos, ElemType x) {if (pos == NULL) {printf("第一个参数错误,pos的地址为空地址NULL!");return;}SListNode* NewNode = BuySListNode(x); // 申请一个新的结点NewNode->next = pos->next; // 让新结点指向原pos的后驱结点pos->next = NewNode; // 让 pos 指向新的结点 }void SListEraseAfter(SListNode* pos) {// 如果pos地址为空if (pos == NULL || pos->next == NULL) {printf("参数错误:pos为空或没有后继节点\n");return;}SListNode* toDelete = pos->next; // 保存待删除节点(保护删除结点的指针不被丢失)pos->next = toDelete->next; // pos指向删除结点后边的结点free(toDelete); // 释放内存空间 }
-
SListNode.h
#pragma once #include "stdio.h" #include "SListNode.h" #include "stdlib.h" typedef int ElemType; // 将 int 重命名为 ElemType,element意思元素 // 单链表结点 typedef struct SListNode {int data; // 数据域struct SListNode* next; // 指针域(指向下一个节点) } SListNode; // 动态申请一个新的结点 因为插入当中申请新结点的代码太多了,为了避免冗余代码,将重复代码部分重新封装成了一个新的函数 SListNode* BuySListNode(ElemType x); // 打印 void SListPrint(SListNode* phead); // 获取链表宽度(结点数量) size_t SlistSize(SListNode* phead); // 头插 void SListPushFront(SListNode** pphead, ElemType x); // 头删 void SListPopBack(SListNode** pphead); // 尾插 void SListPushBack(SListNode** pphead, ElemType x); // 尾删 void SListPopBack(SListNode** pphead); // 查找 SListNode* SListFind(SListNode* phead, ElemType x); // 在pos位置之前插入x void SListInsert(SListNode** pphead, SListNode* pos, ElemType x); // 删除pos位置的值 void SListErase(SListNode** pphead, SListNode* pos); // 在pos位置之后插入x void SListInsertAfter(SListNode* pos, ElemType x); // 删除pos位置之后的值 void SListEraseAfter(SListNode* pos);
相关文章:
将单链表反转【数据结构练习题】
- 第 98 篇 - Date: 2025 - 05 - 16 Author: 郑龙浩/仟墨 反转单链表(出现频率非常的高) 文章目录 反转单链表(出现频率非常的高)题目:反转一个链表思路:代码实现(第3种思路): 题目:反转一个链表 将 1->2->3->4->5->NULL反转…...

DeepSearch:WebThinker开启AI搜索研究新纪元!
1,项目简介 WebThinker 是一个深度研究智能体,使 LRMs 能够在推理过程中自主搜索网络、导航网页,并撰写研究报告。这种技术的目标是革命性的:让用户通过简单的查询就能在互联网的海量信息中进行深度搜索、挖掘和整合,从…...

springCloud/Alibaba常用中间件之Setinel实现熔断降级
文章目录 SpringCloud Alibaba:依赖版本补充Sentinel:1、下载-运行:Sentinel(1.8.6)下载sentinel:运行:Sentinel <br> 2、流控规则① 公共的测试代码以及需要使用的测试Jmeter①、流控模式1. 直接:2. 并联:3. 链路: ②、流控效果1. 快速…...
从裸机开发到实时操作系统:FreeRTOS详解与实战指南
从裸机开发到实时操作系统:FreeRTOS详解与实战指南 本文将带你从零开始,深入理解嵌入式系统中的裸机开发与实时操作系统,以FreeRTOS为例,全面剖析其核心概念、工作原理及应用场景。无论你是嵌入式新手还是希望提升技能的开发者&am…...

Deeper and Wider Siamese Networks for Real-Time Visual Tracking
现象: the backbone networks used in Siamese trackers are relatively shallow, such as AlexNet , which does not fully take advantage of the capability of modern deep neural networks. direct replacement of backbones with existing powerful archite…...
简单介绍C++中线性代数运算库Eigen
Eigen 是一个高性能的 C 模板库,专注于线性代数、矩阵和向量运算,广泛应用于科学计算、机器学习和计算机视觉等领域。以下是对 Eigen 库的详细介绍: 1. 概述 核心功能:支持矩阵、向量运算,包括基本算术、矩阵分解&…...
Python爬虫实战:研究decrypt()方法解密
1. 引言 1.1 研究背景与意义 在当今数字化时代,网络数据蕴含着巨大的价值。然而,许多网站为了保护其数据安全和商业利益,会采用各种加密手段对传输的数据进行处理。这些加密措施给数据采集工作带来了巨大挑战。网络爬虫逆向解密技术应运而生,它通过分析和破解网站的加密机…...

黑马程序员C++2024版笔记 第0章 C++入门
1.C代码的基础结构 以hello_world代码为例: 预处理指令 #include<iostream> using namespace std; 代码前2行是预处理指令,即代码编译前的准备工作。(编译是将源代码转化为可执行程序.exe文件的过程) 主函数 主函数是…...
c#定义占用固定字节长度的结构体字段
在c中,经常类似这样定义结构体: struct DEMO_STRUCT {int a;int b;char c[128]; }; 定义这个结构体,占用了136个字节的内存空间,关键的是,它的内存块是连续的,其中c占用了128个字节 然后如果想在c#中定义…...

foxmail - foxmail 启用超大附件提示密码与帐号不匹配
foxmail 启用超大附件提示密码与帐号不匹配 问题描述 在 foxmail 客户端中,启用超大附件功能,输入了正确的账号(邮箱)与密码,但是提示密码与帐号不匹配 处理策略 找到 foxmail 客户端目录/Global 目录下的 domain.i…...

Crowdfund Insider聚焦:CertiK联创顾荣辉解析Web3.0创新与安全平衡之术
近日,权威金融科技媒体Crowdfund Insider发布报道,聚焦CertiK联合创始人兼CEO顾荣辉教授在Unchained Summit的主题演讲。报道指出,顾教授的观点揭示了Web3.0生态当前面临的挑战,以及合规与技术在推动行业可持续发展中的关键作用。…...
EDR与XDR如何选择适合您的网络安全解决方案
1. 什么是EDR? 端点检测与响应(EDR) 专注于保护端点设备(如电脑、服务器、移动设备)。通过在端点安装代理软件,EDR实时监控设备活动,检测威胁并快速响应。 EDR核心功能 实时监控:…...

PowerBI链接EXCEL实现自动化报表
PowerBI链接EXCEL实现自动化报表 曾经我将工作中一天的工作缩短至2个小时,其中最关键的一步就是使用PowerBI链接Excel做成一个自动化报表,PowerBI更新源数据,Excel更新报表并且保留报表格式。 以制作一个超市销售报表为例,简单叙…...

腾讯云MCP数据智能处理:简化数据探索与分析的全流程指南
引言 在当今数据驱动的商业环境中,企业面临着海量数据处理和分析的挑战。腾讯云MCP(Managed Cloud Platform)提供的数据智能处理解决方案,为数据科学家和分析师提供了强大的工具集,能够显著简化数据探索、分析流程,并增强数据科学…...

Android framework 中间件开发(一)
在Android开发中,经常会调用到一些系统服务,这些系统服务简化了上层应用的开发,这便是中间件的作用,中间件是介于系统和应用之间的桥梁,将复杂的底层逻辑进行一层封装,供上层APP直接调用,或者将一些APP没有权限一些操作放到中间件里面来实施. 假设一个需求,通过中间件调节系统亮…...
Lua中使用module时踩过的坑
在lua中设置某个全局对象(假如对象名为LDataUser)为nil时, LDataUser并不会变成nil, 但在有些情况下设置LDataUser nil时却真变成了nil,然后会导致后续再使用LDataUser时会抛nil异常, 后来发现是使用module搞的鬼,下面看看豆包AI给的解释,还…...

MATLAB中的概率分布生成:从理论到实践
MATLAB中的概率分布生成:从理论到实践 引言 MATLAB作为一款强大的科学计算软件,在统计分析、数据模拟和概率建模方面提供了丰富的功能。本文将介绍如何使用MATLAB生成各种常见的概率分布,包括均匀分布、正态分布、泊松分布等,并…...

C# 面向对象 构造函数带参无参细节解析
继承类构造时会先调用基类构造函数,不显式调用基类构造函数时,默认调用基类无参构造函数,但如果基类没有写无参构造函数,会无法调用从而报错;此时,要么显式的调用基类构造函数,并按其格式带上参…...
轨迹误差评估完整流程总结(使用 evo 工具)
roslaunch .launch rosbag play your_dataset.bag -r 2.0 ✅ 第二步:录制估计轨迹 bash 复制编辑 rosbag record -O traj_only.bag /aft_mapped_to_init 运行一段时间后 CtrlC 停止,生成 traj_only.bag 第三步:提取估计轨迹和真值轨迹为…...
Spring Boot 跨域问题全解:原理、解决方案与最佳实践
精心整理了最新的面试资料和简历模板,有需要的可以自行获取 点击前往百度网盘获取 点击前往夸克网盘获取 一、跨域问题的本质 1.1 什么是跨域? 跨域(Cross-Origin)问题源于浏览器的同源策略(Same-Origin Policy&…...
vhca_id 简介,以及同 pf, vf 的关系
vhca_id 指的是 Virtual Host Channel Adapter ID(虚拟主机通道适配器编号),它是 NVIDIA(Mellanox)网络设备虚拟化架构中的一个核心概念。 它与 PF(物理功能)、VF(虚拟功能ÿ…...
LlamaIndex 第九篇 Indexing索引
索引概述 数据加载完成后,您将获得一个文档对象(Document)列表(或节点(Node)列表)。接下来需要为这些对象构建索引(Index),以便开始执行查询。 索引(Index) 是一种数据结构,能够让我们快速检索…...
微信小程序原生swiper高度自适应图片,不同屏幕适配,正方形1:1等比例图片轮播
🤵 作者:coderYYY 🧑 个人简介:前端程序媛,目前主攻web前端,后端辅助,其他技术知识也会偶尔分享🍀欢迎和我一起交流!🚀(评论和私信一般会回!!) 👉 个人专栏推荐:《前端项目教程以及代码》 ✨一、前言分析 一开始只设了图片的mode="widthFix" st…...

在 C# 中将 DataGridView 数据导出为 CSV
在此代码示例中,我们将学习如何使用 C# 代码将 DataGridView 数据导出到 CSV 文件并将其保存在文件夹中。 在这个程序中,首先,我们必须连接到数据库并从中获取数据。然后,我们将在数据网格视图中显示该数据,…...
解锁 CPU 性能天花板:多维优化策略深度剖析
在数字世界的底层战场,CPU 如同指挥千军万马的将军,掌控着程序运行的节奏与效率。无论是大型服务器应用,还是手机端的轻量化程序,CPU 性能的优化都如同解锁隐藏力量的密码,能让程序在执行效率上实现质的飞跃。本文将深…...
Android SwitchButton 使用详解:一个实际项目的完美实践
Android SwitchButton 使用详解:一个实际项目的完美实践 引言 在最近开发的 Android 项目中,我遇到了一个需要自定义样式开关控件的需求。经过多方比较,最终选择了功能强大且高度可定制的 SwitchButton 控件。本文将基于实际项目中的使用案…...
Kafka如何实现高性能
Kafka如何实现高性能 Kafka之所以能成为高性能消息系统的标杆,是通过多层次的架构设计和优化实现的。 一、存储层优化 1. 顺序I/O设计 日志结构存储:所有消息追加写入,避免磁盘随机写分段日志:将日志分为多个Segment文件&…...

MySQL中表的增删改查(CRUD)
一.在表中增加数据(Create) INSERT [INTO] TB_NAME [(COLUMN1,COLUMN2,...)] VALUES (value_list1),(value_list2),...;into可以省略可仅选择部分列选择插入,column即选择的列, 如图例可以选择仅在valuelist中插入age和id如果不指…...

项目思维vs产品思维
大家好,我是大明同学。 这期内容,我们来聊一下项目思维和产品思维的区别。 项目是实施关键,力求每一步都精准到位;产品则是战略导向,确保所选之路正确无误。若缺乏优异成果,即便按时完成,也只…...

游戏引擎学习第285天:“Traversables 的事务性占用”
回顾并为当天的工作做准备 我们有一个关于玩家移动的概念,玩家可以在点之间移动,而且当这些点移动时,玩家会随之移动。现在这个部分基本上已经在工作了。我们本来想实现的一个功能是:当玩家移动到某个点时,这个点能“…...