前言
单链表反转是leetcode(力扣)第206题:206. 反转链表
要想反转单链表,首先得知道什么是单链表。前一篇 图解数据结构:数组和单链表 已经非常详细的解析了什么是单链表。
单链表结构
/**
* 单链表
*/
public class SingleLinkedList<E> {
/**
* 内部节点
*/
private class Node {
private E e;
private Node next;
public Node(E e, Node next) {
this.e = e;
this.next = next;
}
public Node(E e) {
this(e, null);
}
}
// 头节点
private Node head;
// 节点个数
private int size;
}
三个指针实现反转
分别用三个指针prev、curr、next指向连续的三个节点,curr节点表示需要修改后继节点的节点,也就是:curr的后继节点本来指向next节点,现在要改成指向prev节点,对每一个节点(除了头节点外)执行这样的操作后,链表就被反转了。整个过程示意图如下:
看懂了过程图,再看实现代码,应该就一目了然了。
public Node reverse() {
if (head == null || head.next == null) {
// 空链表和只有一个节点的链表,反转后还是原来的结构
return head;
}
Node prev = head;
Node curr = prev.next;
Node next = null;
while (curr != null) {
next = curr.next;
curr.next = prev;
prev = curr;
curr = next;
}
head.next = null;
head = prev;
return head;
}
递归实现
反转整个链表,等于反转除头节点外,其余的链表,并将返回的反转后的链表的后继指针指向头节点。这样可以把原问题变成更小的子问题,从而写出递归算法。递归算法虽然写起来代码量更少,但是不如三个指针的方法更直观。下面是递归方式的实现:
/**
* 反转链表(递归版)
*
* @return
*/
public Node reverse() {
if (head == null) {
return null;
}
reverse(head);
return head;
}
/**
* 反转以node为头结点的单链表,并且返回反转后的尾结点(反转之后就成了尾结点)
*
* @param node
* @return
*/
private Node reverse(Node node) {
if (node.next == null) {
head = node;
return node;
}
Node lastTail = reverse(node.next);
node.next = null;
lastTail.next = node;
return node;
}
验证
为了验证两个反转方法的正确性,还得先写一个向单链表中添加元素的方法,由于不是重点,所以直接给出。以下是完整的源代码,包括:单链表结构定义、向单链表中添加元素、反转单链表、打印单链表等。
/**
* 单链表
*/
public class SingleLinkedList<E> {
/**
* 内部节点
*/
private class Node {
private E e;
private Node next;
public Node(E e, Node next) {
this.e = e;
this.next = next;
}
public Node(E e) {
this(e, null);
}
}
private Node head;
private int size;
/**
* 链表尾部新增节点
*
* @param e
*/
public void add(E e) {
head = add(head, e);
}
/**
* 向以node为根节点的链表插入节点,并返回插入后的链表
*
* @param node
* @param e
* @return
*/
private Node add(Node node, E e) {
if (node == null) {
// 递归结束条件
size++;
return new Node(e);
}
node.next = add(node.next, e);
return node;
}
/**
* 反转链表(非递归版)
* @return
*/
// public Node reverse() {
// if (head == null || head.next == null) {
// // 空链表和只有一个节点的链表,反转后还是原来的结构
// return head;
// }
// Node prev = head;
// Node curr = prev.next;
// Node next = null;
// while (curr != null) {
// next = curr.next;
// curr.next = prev;
// prev = curr;
// curr = next;
// }
// head.next = null;
// head = prev;
// return head;
// }
/**
* 反转链表(递归版)
*
* @return
*/
public Node reverse() {
if (head == null) {
return null;
}
reverse(head);
return head;
}
/**
* 反转以node为头结点的单链表,并且返回反转后的尾结点(反转之后就成了尾结点)
*
* @param node
* @return
*/
private Node reverse(Node node) {
if (node.next == null) {
head = node;
return node;
}
Node lastTail = reverse(node.next);
node.next = null;
lastTail.next = node;
return node;
}
/**
* 输出链表信息
*
* @return
*/
@Override
public String toString() {
StringBuilder result = new StringBuilder();
Node curr = head;
while (curr != null) {
result.append(curr.e).append("->");
curr = curr.next;
}
result.append("NULL");
return result.toString();
}
}
测试方法
public static void main(String[] args) {
SingleLinkedList<Integer> list = new SingleLinkedList();
for (int i = 0; i < 7; i++) {
list.add(i);
}
System.out.println(list);
list.reverse();
System.out.println(list);
}
执行结果:
反转前:0->1->2->3->4->5->6->NULL
反转后:6->5->4->3->2->1->0->NULL
总结
单是一个线性数据结构的反转其实不难,主要是单链表只能根据当前节点找到后一个节点,所以操作起来感觉处处受限。所以需要发散思维,借助一些辅助的指针等完成反转操作。
当然除了以上两种方法,还有一些简单粗暴的方法:直接开辟一个数组,数组长度等于单链表节点个数,把单链表节点存入数组,再将数组反序遍历,重新构造成单链表。或者利用栈(Stack)LIFO的性质等等。算法比较简单,就不赘述了。