链表基础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");  
}  

不带头结点的单向不循环链表的逆置

不带头结点的单向不循环链表的逆置方式主要有以下几种:

  1. 迭代法
    这种方法使用一个指针遍历链表,同时使用另一个指针指向当前节点的前一个节点。在遍历过程中,每次都将当前节点的next指针指向前一个节点,从而实现链表的逆置。需要注意的是,在逆置过程中,还需要保存下一个要处理的节点,因为逆置后当前节点的next指针会指向前一个节点,从而失去对下一个节点的引用。

  2. 递归法
    递归法通过递归调用实现链表的逆置。递归的基本思想是:先递归到链表的最后一个节点,然后将该节点的next指针指向它的前一个节点,接着返回前一个节点,继续逆置前面的部分。递归法的好处是代码简洁,但递归深度较大时可能会导致栈溢出。

  3. 栈辅助法
    这种方法利用栈的后进先出特性来实现链表的逆置。首先将链表中的每个节点依次入栈,然后再依次出栈,并将出栈节点的next指针指向它的前一个出栈节点,从而实现链表的逆置。这种方法需要额外的空间来存储栈中的节点。

  4. 三指针法
    这种方法使用三个指针,分别指向当前节点、前一个节点和下一个节点。在遍历过程中,先将当前节点的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的工作过程如下:

  1. 基本情况(递归终止条件)
    • 如果链表为空(head == NULL),或者链表只有一个节点(head->next == NULL),那么链表已经逆置完成(或者说,无需逆置),直接返回当前头节点。
  2. 递归步骤
    • 假设当前头节点head的下一个节点开始的链表部分(即剩余链表)已经通过递归调用reverseListRecursive(head->next)逆置完成,并返回了新的头节点newHead
  3. 处理当前节点
    • 在递归调用返回后,newHead指向逆置后链表的头节点。此时,我们需要将当前节点head放到逆置后链表的尾部。
    • 由于head->next现在指向逆置后的链表,我们将head->next->next指向head,这样就将head节点添加到了逆置后链表的尾部。
    • 然后,我们需要将head->next设置为NULL,因为head现在是新链表的最后一个节点。
  4. 返回新头节点
    • 最后,递归函数返回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是链表的长度。

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

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

相关文章

逆向IDA中Dword,数据提取

我们可以看见数据是这样的&#xff0c;第一个是1cc 但是我们shifte就是 这个因为他的数据太大了&#xff0c;导致高位跑后面去了 这个时候&#xff0c;我们右键——convert——dword 这样就可以提取到争取的数据了 比如第一个数据 0x1cc a0xcc b0x1 print(hex((b<<8…

HarmonyOS开发实例:【事件的订阅和发布】

介绍 本示例主要展示了公共事件相关的功能&#xff0c;实现了一个检测用户部分行为的应用。具体而言实现了如下几点功能&#xff1a; 1.通过订阅系统公共事件&#xff0c;实现对用户操作行为&#xff08;亮灭屏、锁屏和解锁屏幕、断联网&#xff09;的监测&#xff1b; 2.通…

vmware安装ubuntu-18.04系统

一、软件下载 百度网盘&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1fK2kygRdSux1Sr1sOKOtJQ 提取码&#xff1a;twsb 二、安装ubuntu系统 1、把ubuntu-18.04的压缩包下载下来&#xff0c;并且解压 2、打开vmware软件&#xff0c;点击文件-打开 3、选择我们刚刚解…

6. Django 深入模板

6. 深入模板 6.1 Django模板引擎 Django内置的模板引擎包含模板上下文(亦可称为模板变量), 标签和过滤器, 各个功能说明如下: ● 模板上下文是以变量的形式写入模板文件里面, 变量值由视图函数或视图类传递所得. ● 标签是对模板上下文进行控制输出, 比如模板上下文的判断和循…

项目7-音乐播放器1+BCrypt加密

1.创建项目 1.1 引入依赖 1.2 yml相关配置 application.yml spring:profiles:active: prod mybatis:mapper-locations: classpath:mapper/**Mapper.xmlconfiguration:map-underscore-to-camel-case: true #配置驼峰⾃动转换log-impl: org.apache.ibatis.logging.stdout.StdO…

openGauss学习笔记-261 openGauss性能调优-使用Plan Hint进行调优-将部分Error降级为Warning的Hint

文章目录 openGauss学习笔记-261 openGauss性能调优-使用Plan Hint进行调优-将部分Error降级为Warning的Hint261.1 功能描述261.2 语法格式261.3 示例261.3.1 忽略非空约束261.3.2 忽略唯一约束261.3.3 忽略分区表无法匹配到合法分区261.3.4 更新/插入值向目标列类型转换失败 o…

【算法】数组元素循环右移k位,并要求只用一个元素大小的附加存储,元素移动或交换次数为O(n)

两种写法思路&#xff1a; 思路一&#xff1a;三次倒置 前言&#xff1a;C/C函数 reverse 是 左闭右开区间的&#xff0c;作用是将指定范围数组元素全部倒置&#xff0c;数组从 0 开始&#xff0c;这里主要讲解思路&#xff0c;就直接用 函数 reverse 简化过程 这个方法 实现 …

vue3第十八节(diff算法)

引言&#xff1a; 上一节说了key的用途&#xff0c;而这个key属性&#xff0c;在vue的vnode 中至关重要&#xff0c;直接影响了虚拟DOM的更新机制&#xff1b; 什么场景中会用到diff算法 如&#xff1a;修改响应式属性需要重新渲染页面&#xff0c;会重新执行render渲染函数返…

为了执行SQL语句,MySQL的架构是怎样设计的

1. 把MySQL当个黑盒子一样执行SQL语句 上一讲我们已经说到&#xff0c;我们的系统采用数据库连接池的方式去并发访问数据库&#xff0c;然后数据库自己其实也会维护一个连 接池&#xff0c;其中管理了各种系统跟这台数据库服务器建立的所有连接 我们先看下图回顾一下 当我们的…

数据可视化-ECharts Html项目实战(12)

在之前的文章中&#xff0c;我们深入学习ECharts特殊图表中的矩形树图以及Echarts中高级功能的多图表联动。想了解的朋友可以查看这篇文章。同时&#xff0c;希望我的文章能帮助到你&#xff0c;如果觉得我的文章写的不错&#xff0c;请留下你宝贵的点赞&#xff0c;谢谢。 数…

Directory Monitor:全方位监控文件系统变动的专业利器

目录 一、软件介绍 二、软件功能 三、软件特点 四、安装说明 五、使用说明 一、软件介绍 Directory Monitor是一款强大易用的实时文件系统监视工具&#xff0c;它由Michael Humpa开发&#xff0c;专为满足用户监控特定目录下文件和子目录变化的需求。无论是为了保障系统安…

python与设计模式之工厂模式的那些事儿

一、工厂模式 工厂模式实现了按需创建的最佳模式&#xff0c;其目的是为了隐藏创建类的细节与过程&#xff0c;通过一个统一的接口来创建所需的对象。 话说没了皇位争夺权的皇三接到了一个外征的工作&#xff0c;始皇给了5个亿的经费让皇三组建一个军队。打权总是要进行武器采…

【Java开发指南 | 第二篇】标识符、Java关键字及注释

读者可订阅专栏&#xff1a;Java开发指南 |【CSDN秋说】 文章目录 标识符Java关键字Java注释 标识符 Java 所有的组成部分都需要名字。类名、变量名以及方法名都被称为标识符。 所有的标识符都应该以字母&#xff08;A-Z 或者 a-z&#xff09;,美元符&#xff08;$&#xff0…

CentOS 7安装、卸载MySQL数据库

说明&#xff1a;本文介绍如何在CentOS 7操作系统下使用yum方式安装MySQL数据库&#xff0c;及卸载&#xff1b; 安装 Step1&#xff1a;卸载mariadb 敲下面的命令&#xff0c;查看系统mariadb软件包 rpm -qa|grep mariadb跳出mariadb软件包信息后&#xff0c;敲下面的命令…

【Qt】:事件的处理

系统相关 一.鼠标事件二.键盘事件三.定时器 事件是应用程序内部或者外部产生的事情或者动作的统称。在Qt中使用一个对象来表示一个事件。所有的Qt事件均继承于抽象类QEvent。事件是由系统或者Qt平台本身在个同的的刻友出的。当用广投下鼠标、敲下键盘&#xff0c;或者是窗口需要…

第四百六十二回

文章目录 1. 概念介绍2. 实现方法3. 示例代码4. 内容总结 我们在上一章回中介绍了"关于MediaQuery的优化"相关的内容&#xff0c;本章回中将介绍readMore这个三方包.闲话休提&#xff0c;让我们一起Talk Flutter吧。 1. 概念介绍 我们在本章回中介绍的readMore是一个…

【团体程序设计天梯赛 往年关键真题 25分题合集 详细分析完整AC代码】(L2-001 - L2-024)搞懂了赛场上拿下就稳了

L2-001 紧急救援 最短路路径打印 样例 输入1 4 5 0 3 20 30 40 10 0 1 1 1 3 2 0 3 3 0 2 2 2 3 2输出1 2 60 0 1 3分析 用一遍dijkstra算法。设立 n u m [ i ] num[i] num[i]和 w [ i ] w[i] w[i]表示从出发点到i结点拥有的路的条数&#xff0c;以及能够找到的救援队的数目…

Websocket (帧格式, 握手过程, Spring 中使用 WebScoket 协议)

什么是 WebSocket 客户端 A 和客户端 B 的消息传播需要借助服务器的中转 (原因是内网不能给另一个局域网的内网直接联通, 需要借助服务器的外网做 “中介”) (NAT 地址转换) Http 协议 不支持实时通讯 (或者说不支持服务器主动推送数据给客户端) TCP 本身是具有服务器推送数据这…

【verilog】 reg与寄存器的关系

一、前言 在Verilog中经常用reg定义具有数据寄存功能的单元&#xff0c;但在verilog的使用中&#xff0c;并不代表其一定就是寄存单元&#xff0c;reg还能进行组合逻辑描述&#xff0c;并且在一些场景下&#xff0c;只能使用reg来申明变量。 二、reg型变量生成组合逻辑 在Ve…

linux shell脚本编写(2)

Shell: 命令转换器&#xff0c;高级语言转换成二进制语言。是Linux的一个外壳&#xff0c;它包在Lniux内核的外面&#xff0c;用户和内核之间的交互提供了一个接口。 内置命令&#xff1a;在shell内部不需要shell编辑 外置命令&#xff1a;高级语言要用shell转换成二进制语言 …