链表基础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.hqwc.cn/news/621047.html

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

相关文章

(弟弟14)递归•按顺序打印一个整数的每一位

这里是目录哦 题目代码运行截图递归思路递归停止条件如何实现“按顺序”悟了✨加油&#x1f389; 题目 按顺序打印一个整数的每一位。 代码 #include<stdio.h> void Print(int n) {if (n > 9)//递归停止条件{Print(n / 10);//不断趋近递归停止条件}printf("%d…

游戏实践:扫雷

一.游戏介绍 虽然很多人玩过这个游戏&#xff0c;但还是介绍一下。在下面的格子里&#xff0c;埋的有10颗雷&#xff0c;我们通过鼠标点击的方式&#xff0c;点出你认为不是雷的地方&#xff0c;等到把所有没有雷的格子点完之后&#xff0c;及视为游戏胜利。 上面的数字的意思…

python爬虫------- Selenium下篇(二十三天)

&#x1f388;&#x1f388;作者主页&#xff1a; 喔的嘛呀&#x1f388;&#x1f388; &#x1f388;&#x1f388;所属专栏&#xff1a;python爬虫学习&#x1f388;&#x1f388; ✨✨谢谢大家捧场&#xff0c;祝屏幕前的小伙伴们每天都有好运相伴左右&#xff0c;一定要天天…

【爬虫开发】爬虫从0到1全知识md笔记第5篇:Selenium课程概要,selenium的其它使用方法【附代码文档】

爬虫开发从0到1全知识教程完整教程&#xff08;附代码资料&#xff09;主要内容讲述&#xff1a;爬虫课程概要&#xff0c;爬虫基础爬虫概述,,http协议复习。requests模块&#xff0c;requests模块1. requests模块介绍,2. response响应对象,3. requests模块发送请求,4. request…

Python开源工具库使用之词云Wordcloud

文章目录 前言一、基本使用1.1 文本生成词云1.2 配置项 二、进阶用法2.1 自定义形状2.2 自定义着色2.3 自定义词频2.4 中文 三、实际案例3.1 工作报告词云3.2 周杰伦歌词词云 四、总结4.1 优点和局限性4.2 展望未来发展 参考 前言 当我们需要将大量文本数据可视化展示时&#…

【Entity Framework】你要知道EF中功能序列与值转换

【Entity Framework】你要知道EF中功能序列与值转换 文章目录 【Entity Framework】你要知道EF中功能序列与值转换一、序列1.1 基本用法1.2 配置序列设置 二、值转换2.1 配置值转换器2.2 批量配置值转换器2.3 预定义的转换2.4 ValueConverter类2.5 内置转换器 三、应用3.1 简单…

白盒测试详解

&#x1f345; 视频学习&#xff1a;文末有免费的配套视频可观看 &#x1f345; 关注公众号&#xff1a;互联网杂货铺&#xff0c;回复1 &#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 概念与定义 白盒测试&#xff1a;侧重于系统或部件内部机…

攻防世界1

阅读须知&#xff1a; 探索者安全团队技术文章仅供参考,未经授权请勿利用文章中的技术资料对任何计算机系统进行入侵操作,由于传播、利用本公众号所提供的技术和信息而造成的任何直接或者间接的后果及损失&#xff0c;均由使用者 本人负责&#xff0c;作者不为此承担任何责任,如…

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

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

苹果备忘录误删一段内容怎么恢复?iPhone备忘录恢复的4种方法!收藏!

在使用苹果设备时&#xff0c;备忘录是许多用户常用的工具之一。iPhone备忘录是一款功能强大、易于使用的应用&#xff0c;它能帮助你更好地管理生活和工作。无论你是记录待办事项、设置提醒还是与他人分享信息&#xff0c;备忘录都能满足你的需求。 然而&#xff0c;如果不小…

Mac下载的软件显示文件已损坏,如何解决文件已损坏问题

当在Mac上下载的软件显示文件已损坏时&#xff0c;这可能是因为多种原因导致的&#xff0c;包括网络问题、下载中断、软件未完整下载、文件传输错误等。解决这个问题需要采取一些步骤来排除可能的原因&#xff0c;并尝试修复文件。下面将详细介绍一些常见的解决方法&#xff1a…

单链表链表专题

1 链表的概念 概念&#xff1a;链表是⼀种物理存储结构上⾮连续、⾮顺序的存储结构&#xff0c;数据元素的逻辑顺序是通过链表中的指针链接次序实现的。 链表的结构跟⽕⻋⻋厢相似&#xff0c;淡季时⻋次的⻋厢会相应减少&#xff0c;旺季时⻋次的⻋厢会额外增加⼏节。只 需要…