1.递归方法
题目:原链表中的数据为:0–>1–>2–>3–>4
反转后链表中数据为:4–>3–>2–>1–>0
1.1 递归介绍
- 递归定义:在定义方法的时候,在方法内部调用方法自身。
- 作用:通常把一个大型复杂的问题,层次转换为一个与原问题相似的,规模较小的问题来解决。递归策略只需要少量的程序就可以描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。
- 注意事项:在递归中,不能无限制的调用自己,必须要有边界条件,能够让递归结束,因为每一次递归调用都会在栈内存开辟新的空间,重新执行方法,如果递归的层级太深,很容易造成栈内存溢出。
1.2 递归解决单链表反转
-
递归反转其实就是从原链表的第一个存数据的结点开始,依次递归调用反转每一个结点,直到把最后一个结点反转完毕,整个链表就反转完毕。
-
代码实现:
import java.util.Iterator; /** * @author JIN * @description 单向链表 **/ public class LinkList<T> implements Iterable<T>{ private Node head;//头节点 private int N;//记录链表个数 private class Node{ //node节点 内部类 T data; Node next; public Node(T data, Node next){ this.data = data; this.next = next; } } //构造函数,初始化头节点,将链表个数记为0 public LinkList(){ head = new Node(null,null); N = 0; } //插入,尾插法 public void insert(T t){ Node node = head; while(node.next != null){ node = node.next; } node.next = new Node(t,null); N++; } //反转方法 public void reverse(){ //若链表为空,或者只有一个节点,不用反转,直接返回 if(N == 0 || N == 1){ return; } //调用反转方法 reverse(head.next); } private Node reverse(Node cur){ //递归的边界条件,当当前节点没有下一个节点即结束 if(cur.next == null){ head.next = cur; return cur; } //若当前节点有下一个节点,需先反转下一个节点。 //且接收上一个已经反转好的节点 Node pre = reverse(cur.next); pre.next = cur; cur.next = null; return cur; } //下面就是为了实现foreach功能,不是重点 @Override public Iterator iterator() { return new ILinkList(); } private class ILinkList implements Iterator{ private Node node; public ILinkList(){ node = head; } @Override public boolean hasNext() { return node.next != null; } @Override public Object next() { node = node.next; return node.data; } } public static void main(String[] args) { LinkList<Integer> list = new LinkList<>(); list.insert(0); list.insert(1); list.insert(2); list.insert(3); list.insert(4); for (Integer i: list) { System.out.print(i + " "); } System.out.println(); System.out.println("--------------"); list.reverse(); for (Integer i: list) { System.out.print(i + " "); } } }
-
图画解释:
我们反转只需要将节点的next指向反过来即可。由图可知,我们需要先反转最后一个节点,再继续反转倒数第二个节点,依次类推。为什么呢?就如上图来说,如果你先反转了节点1的话,你将再也找不到节点2了,因此,我们只能先反转后面的,然后再反转自身,这样子就完成整个的反转。
2.非递归方法
- 对于链表来说,有比较常见的两种插入的方法,头插法,跟尾插法。
- 按顺序依次插入1 2 3 4,尾插法则是 1—>2—>3---->4, 头插法则是 2–>1, 3—>2---->1, 4—>3---->2---->1.
- 从第二点可以看出,头插法,可以将顺序倒过来,因此在单链表反转的时候,我们使用头插法。
2.1 方法讲解
-
代码如下(其他部分代码跟上面一致):
public void reverse(){ //若链表为空,或者只有一个节点,无需反转,直接返回 if(N == 0 || N == 1){ return; } //定义三个Node节点,分别来记录节点信息。 Node temp = null; //由于我这链表有头节点,因此移动到当前需要的节点上。 Node cur = head.next; Node pre = cur; while(cur.next != null){ cur = cur.next; pre.next = temp; temp = pre; pre = cur; } //退出循环后,把最后一个节点,跟头节点设置好即可 pre.next = temp; head.next = pre; }
-
原理
- 使用3个节点来记录反转时候的信息,cur代表当前节点,pre为上一个节点,temp为中转临时辅助节点。
- 一开始将temp = null,cur与pre指向第一个需要反转的节点。
- 如果cur存在下一个节点即cur.next != null,将cur指向下一个节点即cur = cur.next,保证能找到下一个节点。
- 再将pre.next 指向temp(头插入),之后再让temp = pre(移动到新插入的节点地方,因为下一次插入的节点需要插到该节点的前面),pre = cur(因为刚刚的节点已经插入,移动到下一个需要插入的节点上面)。
- 重复3 4 步骤 直到退出循环。
- 设置好头节点跟最后一个节点,因为退出循环的时候,最后一个节点并还没有插入,且头节点还没有设置。
-
图像解析:
3 总结
- 反转其实不难,只要把next指向修改即可。具体怎么修改因人而已,建议多画图,自己走一下就好了。