当前位置: 首页 > news >正文

【数据结构与算法】之单链表反转

单链表反转是一个经典的算法问题,考察对指针操作的理解。本文将详细解释单链表反转的原理。注意:均为Java语言实现

相关教程:

数据结构之链表详解

递归详解

 

1. 问题描述

给定一个单链表的头节点,要求将其反转,并返回新的头节点。

例如:

原始链表: 1 -> 2 -> 3 -> 4 -> NULL反转后: 4 -> 3 -> 2 -> 1 -> NULL输入:head = [1,2,3,4,5]
输出:[5,4,3,2,1]输入:[1,2]
输出:[2,1]输入:[]
输出:[]

2. 思路讲解

反转单链表的核心思想是逐个改变节点的指向。我们可以使用三个指针:prev,curr 和 next 来完成这个操作。

  • prev: 指向前一个节点,初始值为 null。

  • curr: 指向当前节点,初始值为头节点。

  • next: 指向下一个节点,用于保存当前节点的下一个节点,防止在改变 curr 指向时丢失后续节点的信息。

反转的步骤如下:

  • 初始化 prev = null,curr = head。

  • 循环遍历链表,直到 curr 为 null。

  • 在循环内部,执行以下操作:

    • next = curr.next:保存当前节点的下一个节点。

    • curr.next = prev:将当前节点的 next 指针指向前一个节点,实现反转。

    • prev = curr:将 prev 指针移动到当前节点,为下一次循环做准备。

    • curr = next:将 curr 指针移动到下一个节点。

  • 循环结束后,prev 指针指向新的头节点。

3. Java代码实现

public class ReverseLinkedList {// 定义链表节点static class ListNode {int val;ListNode next;ListNode(int val) {this.val = val;}}public static ListNode reverseList(ListNode head) {// prev 指向前一个节点,curr 指向当前节点,next 指向下一个节点ListNode prev = null;ListNode curr = head;ListNode next = null;// 循环遍历链表while (curr != null) {// 保存下一个节点next = curr.next;// 反转当前节点的指向curr.next = prev;// 更新 prev 和 curr 指针prev = curr;curr = next;}// 循环结束后,prev 指向新的头节点return prev;}// 测试代码public static void main(String[] args) {ListNode head = new ListNode(1);head.next = new ListNode(2);head.next.next = new ListNode(3);head.next.next.next = new ListNode(4);ListNode reversedHead = reverseList(head);// 打印反转后的链表while (reversedHead != null) {System.out.print(reversedHead.val + " ");reversedHead = reversedHead.next;} // 输出:4 3 2 1 }
}

4. 其它方式

除了迭代方法,递归也是反转单链表的常用方法。下面提供迭代法、递归法以及头插法三种方式实现单链表的反转,并按照思路难度递增排序:

4.1 迭代法 (Iterative)

这是最直观且容易理解的方法,与之前的解释相同。

public static ListNode reverseListIterative(ListNode head) {ListNode prev = null;ListNode curr = head;ListNode next = null;while (curr != null) {next = curr.next;curr.next = prev;prev = curr;curr = next;}return prev;
}

其原理是:

  • 初始化三个指针:prev指向null,curr指向链表的头节点head,next用来暂存curr的下一个节点。
  • 在while循环中,只要curr不为null(即还有节点需要处理),就执行以下步骤:
    • 将next指向curr的下一个节点,以保存对下一个节点的引用。
    • 将curr.next指向prev,这样就把当前节点从原来的链表中摘下来,并反转它的指向。
    • 将prev和curr向前移动一位,即prev变成curr,curr变成next。
  • 当curr为null时,循环结束,此时prev指向了新的头节点,也就是原链表的尾节点。
  • 返回prev,即反转后的链表头节点。

通过这种方式,迭代地改变每个节点的指向,最终实现整个链表的反转。

4.2 递归法 (Recursive)

递归方法的思路是:先递归反转子链表,然后将当前节点连接到反转后的子链表的尾部。

public static ListNode reverseListRecursive(ListNode head) {if (head == null || head.next == null) {return head; // 递归终止条件:空链表或只有一个节点的链表}ListNode newHead = reverseListRecursive(head.next); // 递归反转子链表head.next.next = head; // 将当前节点连接到反转后的子链表的尾部head.next = null; // 将原链表尾节点的 next 指针置为 nullreturn newHead;
}

递归方法的理解难点在于递归调用的过程。可以想象一下,递归会一直深入到链表的最后一个节点,然后从最后一个节点开始逐层返回,并依次修改节点的指向。

4.3 头插法 (Head Insertion)

头插法是另一种迭代的思路,它模拟了构建新链表的过程。我们将原链表的节点逐个取下,然后插入到新链表的头部。

public static ListNode reverseListHeadInsertion(ListNode head) {ListNode newHead = null; // 新链表的头节点,初始为空ListNode curr = head;while (curr != null) {ListNode next = curr.next;  // 保存下一个节点curr.next = newHead;      // 将当前节点插入到新链表的头部newHead = curr;          // 更新新链表的头节点curr = next;             // 移动到下一个节点}return newHead;
}

头插法的思路相对简单,但需要理解构建新链表的过程。

总结:

  • 迭代法: 最直观易懂,代码简洁,推荐使用。

  • 递归法: 代码简洁优雅,但理解起来稍微复杂一些,需要注意递归的调用过程和终止条件。栈深度可能会成为一个问题,尤其对于非常长的链表。

  • 头插法: 思路清晰,模拟构建新链表的过程,代码也比较容易理解。

希望以上三种方法的解释和代码能够帮助你更好地理解单链表反转的各种实现方式。 选择哪种方法取决于你的个人偏好和具体场景。 在实际应用中,迭代法由于其简洁性和避免了递归的栈深度问题,通常是首选。

为了方便测试,你可以使用以下代码创建一个链表并测试不同的反转方法:

public static void main(String[] args) {ListNode head = new ListNode(1);head.next = new ListNode(2);head.next.next = new ListNode(3);head.next.next.next = new ListNode(4);// 测试不同的反转方法ListNode reversedHead1 = reverseListIterative(head);// ... (打印 reversedHead1) ...  记得重新创建一个链表,或者深拷贝一份进行测试head = new ListNode(1); // 重新创建链表head.next = new ListNode(2);head.next.next = new ListNode(3);head.next.next.next = new ListNode(4); // 重新构建链表,避免影响后续测试ListNode reversedHead2 = reverseListRecursive(head);// ... (打印 reversedHead2) ...head = new ListNode(1);head.next = new ListNode(2);head.next.next = new ListNode(3);head.next.next.next = new ListNode(4);ListNode reversedHead3 = reverseListHeadInsertion(head);// ... (打印 reversedHead3) ...
}

请注意,在测试不同方法时,需要重新创建链表或者深拷贝一份,因为反转操作会修改原链表的结构。

5. 复杂度分析

  • 时间复杂度: O(n),其中 n 是链表的长度。我们需要遍历链表一次。

  • 空间复杂度: O(1),我们只使用了常数个额外变量。

6. 总结

单链表反转是一个重要的算法,理解其原理并能够熟练地编写代码至关重要。本文提供的代码清晰易懂,希望能够帮助读者更好地理解和掌握这个算法。 通过使用 prev、curr 和 next 三个指针,我们可以优雅地实现链表的反转。记住关键步骤:保存下一个节点、反转当前节点指向、更新指针。 熟练掌握这个技巧,可以帮助你解决更多更复杂的链表问题。

最后感谢各位看官的观看,下期见,谢谢~


http://www.mrgr.cn/news/55008.html

相关文章:

  • idea中文国际化转码
  • C#中Task.ContinueWith如何使用
  • 微信小程序考试系统(lw+演示+源码+运行)
  • 测试教程分享
  • 网站分享丨UU在线工具
  • 智融SW5106 无线充电发射端全集成 SOC
  • 【每日一题】24.10.14 - 24.10.20
  • 单链表的经典算法OJ
  • 华为杯”第十三届中国研究生数学建模竞赛-C题:基于无线通信基站的室内三维定位问题
  • SpringCloud
  • process.platform 作用
  • C#基于SkiaSharp实现印章管理(11)
  • 智简魔方业务管理系统v10 好用的IDC业务管理软件
  • 嵌入式元件面试题及参考答案
  • MYSQL的SQL优化
  • PCL 点云配准 GICP算法(精配准)
  • ESP32-IDF 非易失存储 NVS
  • 《深度学习》dlib 人脸应用实例 仿射变换 换脸术
  • 时间复杂度知识点详解重点知识总结
  • 计算机网络—ACL技术和NAT转换
  • Java Exercise
  • 如何进行变基并更新拉取请求
  • 【文献及模型、制图分享】长江中游经济区“水—能源—粮食”系统与城市绿色转型适配性研究
  • 6.2 URDF集成Rviz基本流程
  • 前言——25机械考研复试专业面试问题汇总 机械复试超全流程攻略 机械复试看这一个专栏就够用了!机械复试调剂英语自我介绍口语专业面试常见问题总结 机械保研面试
  • Linux客户端/服务端安全攻防