单链表反转的分析及实现
基本概念
单链表是一种常见的数据结构,由一系列节点组成,每个节点包含两部分:数据域和指向下一个节点的指针域,反转单链表意味着将链表中的节点顺序逆置,例如将链表1>2>3>4>5
反转为5>4>3>2>1
。
为了深入理解反转过程,首先需要创建一个单链表,通常使用一个节点类来封装数据域和指针域:
public class ListNode { int val; ListNode next; ListNode(int x) { val = x; }}
可以使用以下方式创建一个单链表:
ListNode head = new ListNode(1);head.next = new ListNode(2);head.next.next = new ListNode(3);// ...以此类推
1. 迭代法(三指针法)
这种方法使用三个指针:pre
(前驱节点)、cur
(当前节点)和next
(后续节点),核心思想是遍历链表,不断改变当前节点的指针方向,使其指向前驱节点。
初始化:pre = null
,cur = head
循环直到cur
为null
:
next = cur.next
// 暂存下一个节点
cur.next = pre
// 改变当前节点的指针方向
pre = cur
// 移动前驱指针
cur = next
// 移动当前指针
返回新的头结点pre
代码示例:
public ListNode reverseIteratively(ListNode head) { ListNode pre = null, cur = head; while (cur != null) { ListNode nextTemp = cur.next; cur.next = pre; pre = cur; cur = nextTemp; } return pre;}
递归法利用函数自身调用反转剩余部分的子链表,然后将当前节点连接到反转后的子链表末尾。
如果链表为空或只有一个节点,直接返回。
否则,递归调用反转除第一个节点外的子链表。
将第一个节点接到反转后的子链表的头部。
代码示例:
public ListNode reverseRecursively(ListNode head) { if (head == null || head.next == null) return head; ListNode p = reverseRecursively(head.next); head.next.next = head; head.next = null; return p;}
头插法通过改变原链表的节点指向来实现反转。
从链表头开始,将每个节点依次插入到新链表的头部。
同时改变原节点的指针方向。
直到所有节点都被重新插入。
代码示例:
public ListNode reverseByHeadInsert(ListNode head) { ListNode newHead = new ListNode(0); ListNode p = head; while (p != null) { ListNode nextTemp = p.next; p.next = newHead.next; newHead.next = p; p = nextTemp; } return newHead.next;}
时间复杂度:以上三种方法的时间复杂度均为O(N),其中N为链表长度。
空间复杂度:迭代法的空间复杂度为O(1);递归法的空间复杂度取决于递归深度,最坏情况下为O(N);头插法的空间复杂度也为O(1)(不考虑新建节点带来的额外空间)。
问1:为什么需要反转单链表?
答:在实际应用中,反转单链表可以用于多种场景,如回文检测、数据流逆序处理等,掌握链表反转技巧有助于加深对链表操作的理解,提高数据结构处理能力。
问2:如何判断一个单链表是否是回文?
答:一种方法是先将链表反转,然后逐个比较原链表和反转后链表的节点值,如果完全相同,则该链表是回文结构,另一种更高效的方法是使用双指针技术,快慢指针定位到链表中央,反转后半部分链表,再逐一比较前后半部分是否对称。
下面是一个介绍,对比了单链表和双向链表在反转操作的分析和实现上的不同:
特性/操作 | 单链表反转 | 双向链表反转 |
节点结构 | 只有next指针,指向下一个节点 | 同时有next和prev指针,分别指向下一个和前一个节点 |
实现复杂性 | 较复杂,需要额外存储前一个节点 | 较简单,直接使用prev指针 |
代码实现 | ||
步骤1 | 保存当前节点的下一个节点 | 临时保存当前节点的下一个节点 |
步骤2 | 当前节点的next指针指向前一个节点 | 当前节点的prev指针指向下一个节点(反转) |
步骤3 | 更新前一个节点为当前节点 | 更新下一个节点为当前节点 |
步骤4 | 移动到下一个节点(最初保存在步骤1) | 移动到下一个节点(最初保存在步骤1) |
循环条件 | 当前节点不为null | 当前节点不为null |
时间复杂度 | O(n) | O(n) |
空间复杂度 | O(1)(不需要额外存储,除了几个临时变量) | O(1)(不需要额外存储,除了几个临时变量) |
适用场景 | 适用于只需要单向遍历的场景 | 适用于需要双向遍历的场景,反转操作更简单 |
注意事项 | 需要特别小心指针的更新,以免丢失链表 | 直接操作prev指针,但需要确保不会违反双向链表的结构 |
这个介绍总结了单链表和双向链表在反转操作上的主要区别和实现步骤,由于双向链表的节点包含了指向前一个节点的指针,因此在执行反转操作时更加直观和简单,而单链表由于缺乏指向前一个节点的直接引用,需要额外的变量来保存前一个节点,以避免在反转过程中丢失链表的连接。
欢迎大家讨论交流,如有疑问请留言,谢谢观看。