Java - 反转链表

  • 目标是反转链表,效果如下:

    Java - 反转链表_第1张图片

  • 观察要实现以上效果,需要达成什么条件,然后挨个拆解:

    • 需要一个链表
    • 链表遍历
    • 反转函数
    • 测试用例
  • 那么ok,挨个实现以上条件即可

    1.构造链表:链表是一种递归结构,每一个节点除了保存自己的元素之外还有一个指向空或者下一个节点的地址。直白点:
    Java - 反转链表_第2张图片
    所以链表的节点抽象起来就很简单了:

    private static class Node {
        Integer value;
        Node next;
    
        public Node(Integer value) {
            this.value = value;
        }
    
        @Override
        public String toString() {
            return "Node{" +
                    "value=" + value +
                    ", next=" + next +
                    '}';
        }
    }
    

    之所以用私有静态内部类,主要是节点一般不对外暴露,而是通过操作节点的类提供的api供外部调用,比如虽然我们都知道list内部是个object数组,但也不能直接操作那个数组。

    2.遍历链表:主要是方便验证反转的准确性。

    private static void showLink(Node node) {
        while (node != null) {
            System.out.printf("%d ---> ", node.value);
            node = node.next;
        }
        System.out.printf("%s%n", "null");
    }
    

    3.反转函数,这个是核心,下面逐步分析每行代码的意义:

    private static Node reverse(Node node) {
        Node current = null, prev = null;
    
        while (node != null) {
            current = node;
            node = node.next;
            current.next = prev;
            prev = current;
        }
        return prev;
    }
    

    1)反转最终是要让链表顺序对调,所以对于每个节点操作是一样的,那么首先需要确定循环终止条件,可以借鉴遍历的代码,每次node进入循环的时候其头节点都不一样,所以只要node不为空的时候,则反转的操作要一直继续。
    Java - 反转链表_第3张图片
    其对应的代码是:

    	while (node != null) {
    		//TODO...
    		node = node.next;
    	}
    

    2)循环体确定之后,那么在每一次循环中,如何将两个相邻节点的指针交换方向成为思考的重点。

  • 做个类比,想象一下有个贪吃蛇每次进入房间(循环体)都会往外面吐出一个方块,我们要将每次吐出来的方块跟上一次吐出来的连接起来,那么需要拿到上一次吐出来的方块和这次吐出来的一共两个方块。

    落地到代码层面,就需要两个指针分别来保存这两个方块的引用,为了区分先后,一个指针叫prev代表前一个方块的引用,current代表当前吐出来的方块。
    对应的代码:

    	Node current = null, prev = null;
    

    3)进行到这一步,就剩交换指针的动作了。核心操作就是让current的next指向prev,动态的变化过程如下:

    可以看到node就代表贪吃蛇的头,它每次都吐出一个方块,current和prev分别代表的就是当前和上一个方块,随着node不断吐出方块,current和prev也在一直前进,直到所有的方块都吐出来,这时候prev所代表的链表就是反转之后的。

    对应的代码:

    		current = node;//node吐出的节点交给current,代表这次要处理的节点
            node = node.next;//下一个节点成为蛇头
            current.next = prev;//当前节点的指向改为前一个
            prev = current;//prev向前移动一位
    

    这里的执行顺序是需要注意的点,这行代码

    	node = node.next;//下一个节点成为蛇头
    

    可不是乱放的,因为node将当前节点交给current的时候是把当前链表的头节点地址给了current,两者目前指向的是同一个地址。node是循环条件,负责每次吐出节点的,这时候直接交换就连node指向的对象一起变更了。所以node在交出当前节点的时候就应该立马确定下一次循环的节点位置。

    1. 测试用例,将所有散装的代码合并起来。
    public class ReverseLinkedList {
    
        public static void main(String[] args) {
            Node n1 = new Node(1);
            Node n2 = new Node(2);
            Node n3 = new Node(3);
            Node n4 = new Node(4);
            Node n5 = new Node(5);
            n1.next = n2;
            n2.next = n3;
            n3.next = n4;
            n4.next = n5;
            showLink(n1);
            showLink(reverse(n1));
        }
    
        private static Node reverse(Node node) {
            Node current = null, prev = null;
    
            while (node != null) {
                current = node;
                node = node.next;
                current.next = prev;
                prev = current;
            }
            return prev;
        }
    
        private static void showLink(Node node) {
            while (node != null) {
                System.out.printf("%d ---> ", node.value);
                node = node.next;
            }
            System.out.printf("%s%n", "null");
        }
    
        private static class Node {
            Integer value;
            Node next;
    
            public Node(Integer value) {
                this.value = value;
            }
    
            @Override
            public String toString() {
                return "Node{" +
                        "value=" + value +
                        ", next=" + next +
                        '}';
            }
        }
    }
    

·总结:反转链表是一个比较基础但面试常考的题,思路不难,谁都可以用大白话讲清楚怎么反转,难点在于落地成代码。对于java而言,容易出错的地方是指针对象的应用,值传递的应用,不然很容易出错。

你可能感兴趣的:(Java刷算法,java,链表,开发语言)