链表基础3——单链表的逆置
链表的定义
#include <stdio.h>
#include <stdlib.h> typedef struct Node { int data; struct Node* next;
} Node; Node* createNode(int data) { Node* newNode = (Node*)malloc(sizeof(Node)); if (!newNode) { return NULL; } newNode->data = data; newNode->next = NULL; return newNode;
} void printList(Node* head) { Node* current = head; while (current != NULL) { printf("%d ", current->data); current = current->next; } printf("\n");
}
不带头结点的单向不循环链表的逆置
不带头结点的单向不循环链表的逆置方式主要有以下几种:
-
迭代法:
这种方法使用一个指针遍历链表,同时使用另一个指针指向当前节点的前一个节点。在遍历过程中,每次都将当前节点的next指针指向前一个节点,从而实现链表的逆置。需要注意的是,在逆置过程中,还需要保存下一个要处理的节点,因为逆置后当前节点的next指针会指向前一个节点,从而失去对下一个节点的引用。 -
递归法:
递归法通过递归调用实现链表的逆置。递归的基本思想是:先递归到链表的最后一个节点,然后将该节点的next指针指向它的前一个节点,接着返回前一个节点,继续逆置前面的部分。递归法的好处是代码简洁,但递归深度较大时可能会导致栈溢出。 -
栈辅助法:
这种方法利用栈的后进先出特性来实现链表的逆置。首先将链表中的每个节点依次入栈,然后再依次出栈,并将出栈节点的next指针指向它的前一个出栈节点,从而实现链表的逆置。这种方法需要额外的空间来存储栈中的节点。 -
三指针法:
这种方法使用三个指针,分别指向当前节点、前一个节点和下一个节点。在遍历过程中,先将当前节点的next指针指向前一个节点,然后移动三个指针,继续处理下一个节点。这种方法与迭代法类似,但使用了更多的指针来简化操作。
每种方法都有其特点和适用场景,可以根据具体需求选择合适的方法来实现链表的逆置。在实际应用中,还需要考虑代码的可读性、健壮性和性能等因素。
迭代法
// 迭代法逆置链表
void reverseListIterative(Node** head) { Node* prev = NULL; // prev指向前一个节点,初始化为NULL Node* current = *head; // current指向当前节点,初始化为头节点 while (current != NULL) { Node* next = current->next; // next指向下一个节点 current->next = prev; // 将当前节点的next指针指向前一个节点,实现逆置 prev = current; // 将prev移动到当前节点 current = next; // 将current移动到下一个节点 } *head = prev; // 更新头指针,指向新的头节点(原链表的尾节点)
}
逐步分解
到这里循环就结束了
到这里彻底完成链表的逆置
递归法
// 递归法逆置链表
Node* reverseListRecursive(Node* head) { // 递归终止条件:当前节点为空或当前节点的下一个节点为空 if (head == NULL || head->next == NULL) { return head; } // 递归调用,逆置剩余部分链表,并返回新的头节点 Node* newHead = reverseListRecursive(head->next); // 处理当前节点,将其指向原来的前一个节点,实现逆置 head->next->next = head; head->next = NULL; // 返回新的头节点 return newHead;
}// main 函数和其他辅助函数与迭代法相同
递归的思想是将一个大问题分解为若干个更小的子问题来解决。在链表逆置的场景中,我们可以将问题分解为“逆置除头节点外的剩余链表,并将头节点放到逆置后链表的尾部”。
递归函数reverseListRecursive
的工作过程如下:
- 基本情况(递归终止条件):
- 如果链表为空(
head == NULL
),或者链表只有一个节点(head->next == NULL
),那么链表已经逆置完成(或者说,无需逆置),直接返回当前头节点。
- 如果链表为空(
- 递归步骤:
- 假设当前头节点
head
的下一个节点开始的链表部分(即剩余链表)已经通过递归调用reverseListRecursive(head->next)
逆置完成,并返回了新的头节点newHead
。
- 假设当前头节点
- 处理当前节点:
- 在递归调用返回后,
newHead
指向逆置后链表的头节点。此时,我们需要将当前节点head
放到逆置后链表的尾部。 - 由于
head->next
现在指向逆置后的链表,我们将head->next->next
指向head
,这样就将head
节点添加到了逆置后链表的尾部。 - 然后,我们需要将
head->next
设置为NULL
,因为head
现在是新链表的最后一个节点。
- 在递归调用返回后,
- 返回新头节点:
- 最后,递归函数返回
newHead
,即逆置后链表的头节点。
- 最后,递归函数返回
这个过程是一个典型的“自底向上”的递归:我们先递归地逆置剩余部分链表,然后再处理当前节点。递归的每一次调用都处理一个更小的子问题,直到到达基本情况(链表为空或只有一个节点)。
以一个简单的例子来说明:
假设链表为
A -> B -> C -> D
。
- 递归调用
reverseListRecursive(D)
,D没有下一个节点,返回D。- 递归调用
reverseListRecursive(C)
,它首先递归调用reverseListRecursive(D)
得到D,然后将C添加到D的前面,得到D -> C
,并返回D作为新头节点。- 递归调用
reverseListRecursive(B)
,它首先递归调用reverseListRecursive(C)
得到D -> C
,然后将B添加到D -> C
的前面,得到C -> D -> B
,并返回C作为新头节点。- 最后,
reverseListRecursive(A)
调用reverseListRecursive(B)
得到C -> D -> B
,然后将A添加到C -> D -> B
的前面,得到B -> C -> D -> A
,并返回B作为新头节点。最终,
A -> B -> C -> D
被逆置为B -> C -> D -> A
。
通过这种方式,递归能够逐步地将整个链表逆置。希望这个解释能够更清楚地说明递归是如何实现链表逆置的。
栈辅助法
typedef struct Node { int data; struct Node* next;
} Node; typedef struct Stack { Node* top;
} Stack; void push(Stack* stack, Node* node) { node->next = stack->top; stack->top = node;
} Node* pop(Stack* stack) { if (stack->top == NULL) { return NULL; } Node* node = stack->top; stack->top = stack->top->next; node->next = NULL; // 避免悬挂指针 return node;
} bool isEmpty(Stack* stack) { return stack->top == NULL;
} // 栈辅助法逆置链表
Node* reverseListWithStack(Node* head) { StackNode* stack = createStack(); Node* dummy = createNode(0); // 创建一个哑节点作为新链表的头节点 Node* tail = dummy; // tail指向新链表的尾节点 // 将原链表的节点依次入栈 while (head != NULL) { push(&stack, head); head = head->next; } // 将栈中的节点依次出栈并连接到新链表上 while ((head = pop(&stack)) != NULL) { tail->next = head; tail = head; } // 新链表的尾节点指向NULL tail->next = NULL; // 返回新链表的头节点(哑节点的下一个节点) return dummy->next;
} // 辅助函数和main函数与之前相同
三指针法
// 三指针法逆置链表
Node* reverseListWithThreePointers(Node* head) { if (head == NULL || head->next == NULL) { return head; } Node* prev = NULL; Node* current = head; Node* nextTemp = NULL; while (current != NULL) { nextTemp = current->next; // 保存下一个节点 current->next = prev; // 反转当前节点的指针 prev = current; // prev移动到当前节点 current = nextTemp; // current移动到下一个节点 } return prev; // prev现在指向新的头节点
} // ...(其他函数和main函数保持不变)
逐步分解
测验代码
int main() { Node* head = createNode(1); head->next = createNode(2); head->next->next = createNode(3); head->next->next->next = createNode(4); printf("Original List: "); printList(head); reverseListIterative(&head); printf("Reversed List: "); printList(head); // 释放链表内存 Node* temp; while (head != NULL) { temp = head; head = head->next; free(temp); } return 0;
}
带头结点的单向不循环链表的逆置
对于带头结点的单向不循环链表的逆置,我们依然可以使用递归或迭代的方法。
由于头结点通常不存储数据,而是作为链表的起始标识和方便操作,逆置链表时通常不会涉及头结点的变动。
以下,我将分别解释递归和迭代如何对带头结点的单向不循环链表进行逆置。
递归法
在递归法中,我们关注的是链表的当前节点和它的下一个节点。递归的基本思想是:先递归处理子问题(逆置剩余链表),然后处理当前节点。
typedef struct ListNode { int val; struct ListNode *next; } ListNode; ListNode* reverseListRecursive(ListNode* head) { // 如果链表为空或只有一个节点(即头结点后面没有节点),则无需逆置 if (head == NULL || head->next == NULL) { return head; } // 递归调用,逆置头结点后面的链表部分,并返回新的头节点 ListNode* newHead = reverseListRecursive(head->next); // 处理当前头结点,将其next指针指向它的前一个节点 head->next->next = head; // 将当前头结点的next指针置为NULL,因为它现在是新链表的最后一个节点 head->next = NULL; // 返回新的头节点 return newHead; }
在上面的代码中,递归函数reverseListRecursive接收一个指向头结点的指针head,并返回逆置后链表的新的头结点。注意,由于带头结点,我们不会改变头结点的位置,只会改变头结点后面链表的逆置。
迭代法
迭代法则是通过循环和指针操作来逐步逆置链表。对于带头结点的链表,迭代法通常更加直观和易于理解。
ListNode* reverseListIterative(ListNode* head) { if (head == NULL || head->next == NULL) { return head; } ListNode* prev = head; // prev初始指向头结点 ListNode* curr = head->next; // curr初始指向头结点后的第一个节点 // 当curr不为空时,持续进行逆置操作 while (curr != NULL) { ListNode* nextTemp = curr->next; // 保存curr的下一个节点 curr->next = prev; // 将curr的next指针指向前一个节点,实现逆置 prev = curr; // prev向后移动一位 curr = nextTemp; // curr向后移动一位 } // 最后将头结点的next指向新的头节点 head->next = prev; // 返回新的头节点 return prev; }
在这个迭代法中,我们使用三个指针:prev始终指向当前逆置部分的最后一个节点,curr指向待逆置的当前节点,nextTemp用于临时存储curr的下一个节点,以便在逆置当前节点后能够继续迭代。
无论是递归还是迭代法,带头结点的单向不循环链表的逆置操作的关键都在于逐步改变节点的next指针的指向,从而实现链表的逆置。
迭代法通常比递归法在空间效率上更优,因为它不需要递归调用栈的空间。而在时间效率上,两者在平均和最坏情况下通常都是O(n),其中n是链表的长度。
相关文章:

链表基础3——单链表的逆置
链表的定义 #include <stdio.h> #include <stdlib.h> typedef struct Node { int data; struct Node* next; } Node; Node* createNode(int data) { Node* newNode (Node*)malloc(sizeof(Node)); if (!newNode) { return NULL; } newNode->data …...

Fiddler:网络调试利器
目录 第1章:Fiddler简介和安装 1.1 Fiddler概述 1.2 Fiddler的安装步骤 步骤1:下载Fiddler 步骤2:运行安装程序 步骤3:启动Fiddler 1.3 配置Fiddler代理 配置操作系统代理 配置浏览器代理 Google Chrome Mozilla Firefox 第2章:Fiddler界面和基本操作 2.1 Fi…...

【笔记】mysql版本6以上时区问题
前言 最近在项目中发现数据库某个表的createTime字段的时间比中国时间少了13个小时,只是在数据库中查看显示时间不对,但是在页面,又是正常显示中国时区的时间。 排查 项目中数据库的驱动使用的是8.0.19,驱动类使用的是com.mysq…...

Scala实战:打印九九表
本次实战的目标是使用不同的方法实现打印九九表的功能。我们将通过四种不同的方法来实现这个目标,并在day02子包中创建相应的对象。 方法一:双重循环 我们将使用双重循环来实现九九表的打印。在NineNineTable01对象中,我们使用两个嵌套的fo…...

Excel文件解析
在此模块的学习中,我们需要一个新的开源类库---Apahche POI开源类库。这个类库的用途是:解析并生成Excel文件(Word、ppt)。Apahche POI基于DOM方式进行解析,将文件直接加载到内存,所以速度比较快,适合Excel文件数据量不…...

纯css实现switch开关
代码比较简单,有需要直接在下边粘贴使用吧~ html: <div class"switch-box"><input id"switch" type"checkbox"><label></label></div> css: .switch-box {position: relative;height: 25px…...

Unity3d 微信小游戏 AB资源问题
简介 最近在做微信小游戏,因为对unity比较熟悉,而且微信也支持了用unity3d直接导出到小游戏的工具,所以就记录下这期间遇到的问题 微信小游戏启动时间主要受以下三点影响: 下载小游戏首包数据文件下载和编译wasm代码引擎初始化…...

Leetcode二叉树刷题
给你一个二叉树的根节点 root , 检查它是否轴对称。 示例 1: 输入:root [1,2,2,3,4,4,3] 输出:true public boolean isSymmetric(TreeNode root) {if(rootnull)return true;return compare(root.left,root.right);}public boole…...

如何给自己的网站添加 https ssl 证书
文章目录 一、简介二、申请 ssl 证书三、下载 ssl 证书四、配置 nginx五、开放 443 端口六、常见问题解决(一)、配置后,访问 https 无法连接成功(二) 证书配置成功,但是访问 https 还是报不安全 总结参考资料 一、简介 相信大家都知道 https 是更加安全…...

Vue路由跳转及路由传参
跳转 跳转使用 router vue 的路由跳转有 3 个方法: go 、 push 、 replace go :接收数字, 0 刷新,正数前进,负数后退 push :添加,向页面栈中添加一条记录,可以后退 replace &#…...

计算机网络常见面试总结
文章目录 1. 计算机网络基础1.1 网络分层模型1. OSI 七层模型是什么?每一层的作用是什么?2.TCP/IP 四层模型是什么?每一层的作用是什么?3. 为什么网络要分层? 1.2 常见网络协议1. 应用层有哪些常见的协议?2…...

时隔一年,再次讨论下AutoGPT-安装篇
AutoGPT是23年3月份推出的,距今已经1年多的时间了。刚推出时,我们还只能通过命令行使用AutoGPT的能力,但现在,我们不仅可以基于AutoGPT创建自己的Agent,我们还可以通过Web页面与我们创建的Agent进行聊天。这次的AutoGP…...

项目三:学会如何使用python爬虫请求库(小白入门级)
根据上一篇文章我们学会的如何使用请求库和编写请求函数,这一次我们来学习一下爬虫常用的小技巧。 自定义Headers Headers是请求的一部分,包含了关于请求的元信息。我们可以在requests调用中传递一个字典来自定义Headers。代码如下 import requests h…...

【热门话题】PyTorch:深度学习领域的强大工具
🌈个人主页: 鑫宝Code 🔥热门专栏: 闲话杂谈| 炫酷HTML | JavaScript基础 💫个人格言: "如无必要,勿增实体" 文章目录 PyTorch:深度学习领域的强大工具一、PyTorch概述二、PyTorch核心特性…...

SQL注入sqli_libs靶场第一题
第一题 联合查询 1)思路: 有回显值 1.判断有无注入点 2.猜解列名数量 3.判断回显点 4.利用注入点进行信息收集 爆用户权限,爆库,爆版本号 爆表,爆列,爆账号密码 2)解题过程࿱…...

QT_day3
完善对话框,点击登录对话框,如果账号和密码匹配,则弹出信息对话框,给出提示”登录成功“,提供一个Ok按钮,用户点击Ok后,关闭登录界面,跳转到其他界面 如果账号和密码不匹配…...

使用ADO.NET访问数据库
目录 访问数据库的步骤 1、建立数据库 2、设置链接参数 (1)web网页和数据库连接的方法一 (2)web网页和数据库连接的方法二 3、建立链接对象 4、显示数据库 5、数…...

SpringBoot的旅游管理系统+论文+ppt+免费远程调试
项目介绍: 基于SpringBoot旅游网站 旅游管理系统 本旅游管理系统采用的数据库是Mysql,使用SpringBoot框架开发。在设计过程中,充分保证了系统代码的良好可读性、实用性、易扩展性、通用性、便于后期维护、操作方便以及页面简洁等特点。 (1&…...

数据结构---线性表
1,顺序表实现---动态分配 #include<stdlib.h> #define InitSize 10 typedef struct {int *data;//静态分配int length;int MaxSize; }SqList; void InitList(SqList& L) {L.data (int*)malloc(InitSize * sizeof(int));//分配空间L.length 0;L.MaxSize…...

MySQL 8.0 字符集问题导致报错
报错: ### Error querying database. Cause: java.sql.SQLException: Illegal mix of collations (utf8_general_ci,IMPLICIT) and (utf8mb4_0900_ai_ci,COERCIBLE) MySQL 8.0引入了一些新的字符集和排序规则,并对现有的进行了改进。在MySQL 8.0中&#…...

单路高清HDMI编码器JR-3211HD
产品简介: JR-3211HD单路高清HDMI编码器是专业的高清音视频编码产品,该产品具有支持1路高清HDMI音视频采集功能, 1路3.5MM独立外接音频输入,编码输出双码流H.264格式,音频MP3/AAC格式。编码码率可调,画面质…...

分库,分表,分区,分片
MySQL: 是一个开源的关系型数据库管理系统,主要用于存储和管理数据。它提供了命令行接口, SQLyog: 是一个图形化的客户端软件,专门用于管理和操作MySQL数据库。 它提供了一个直观的用户界面,简化了MySQL数据…...

【详解算法流程+程序】DBSCAN基于密度的聚类算法+源码-用K-means和DBSCAN算法对银行数据进行聚类并完成用户画像数据分析课设源码资料包
DBSCAN(Density-Based Spatial Clustering of Applications with Noise)是一个比较有代表性的基于密度的聚类算法。 与划分和层次聚类方法不同,它将簇定义为密度相连的点的最大集合,能够把具有足够高密度的区域划分为簇, 并可在噪声的空间数据…...

java es相关操作
一.es 后期修改分片数量 在Elasticsearch中一旦索引创建后,分片的数量就不能直接更改。如果需要更改分片的数量,你需要按照以下步骤操作: 创建一个新的索引,并指定所需的分片数量。 将旧索引的数据复制到新索引中。 关闭旧索引…...

腾讯EdgeOne产品测评体验——开启安全防护,保障数据无忧
当今时代数字化经济蓬勃发展人们的生活逐渐便利,类似线上购物、线上娱乐、线上会议等数字化的服务如雨后春笋般在全国遍地生长,在人们享受这些服务的同时也面临着各式各样的挑战,如网络数据会不稳定、个人隐私容易暴露、资产信息会被攻击等。…...

机器视觉图形处理软件介绍
机器视觉图形处理软件介绍 一.VisionPro 康耐视公司推出的 系统,具有快速而强大的应用系统开发能力。可快速建立原型和易于集成 。具有高可靠性、硬件灵活性。VisionPro 提供了易于应用的原型、发展和应用。VisionProQuickStart 原型环境加速了强大机器视觉系统的…...

C# WinForm简介
Winform是什么? .Net开发平台中对Windows Form的简称,基于.Net Framework平台 的客户端开发技术,一般使用C#编程。Windows 风格的控件,以及事件,直接使用,开发快速。Windows Form:Windows窗体Windows应用程…...

概念:CPU、内存、磁盘、Android内存分配
cpu CPU的全称是Central Processing Unit,中文名称为中央处理单元。它是计算机硬件的核心部件,负责解释计算机程序指令并处理计算机软件中的数据。简言之,CPU执行计算机程序中的操作指令,包括基本算术、逻辑、控制和输入/输出&am…...

Vue 图片加载失败显示默认图片
方法一:通过onerror属性加载默认图片 <img :src"img" :onerror"defaultImg" /><script> export default {data() {return {img: , // 访问图片的ip地址defaultImg: this.src ${require(/assets/images/right/default-person.png)…...

【Sentinel的限流使用】⭐️SpringBoot整合Sentinel实现Api的限流
目录 前言 一、Sentinel下载 二、SpringBoot 整合 Sentinel 三、流控规则 章末 前言 小伙伴们大家好,上次使用OpenFeign时用到了 Hystrix实现熔断和限流的功能,但是发现该工具已经停止维护了,于是想到了Spring Cloud Alibaba开发的Sentin…...