剑指Offer题目5:从未到头打印链表(含递归分析)(Java)

205 阅读2分钟

p32 面试题2:输入一个链表的头结点,从尾到头反过来打印出每个结点的值。

思路分析

  1. 借助Stack数据结构实现

从头遍历节点,将所有遍历到的值都压入栈,遍历完之后再从栈弹出值并打印,就实现了从尾到头打印的要求。

public static void reversePrintNodes(Node head) {
    Stack<Integer>  stack = new Stack<>();
    while (head != null) {
        stack.push(head.data);
        head = head.next;
    }
    while (!stack.isEmpty()) {
        System.out.println(stack.pop());
    }
}

  1. 递归实现

递归的本质是栈。

这句话应该如何理解?

需要从起源去理解:计算机发展过程中,为了表示函数调用自身的场景,不得不创建一种叫 Stack 的数据结构,来表达这种函数调用形式,并将其称为 recursion,即递归。

详见下文:《虚拟机栈和栈帧的起源和关系》

理解递归的前提是先理解 Stack:入栈就是先从头一个个元素积压到最后一个,取元素(即出栈)则只能从最后一个挨个取到最底层 -- 先进后出,先来的压到最底,最后才弹出。

扩展到递归:

入栈阶段:

Basic Notation:栈帧 Stack Frame【切成一片片的小栈叫栈帧】

函数第一次被调用,压入方法栈底的栈帧(未执行到返回语句,栈帧还得活着)

函数第二次被调用,压到方法栈第二层栈帧(未执行到返回语句,栈帧还得活着)

函数第三次被调用,压到方法栈第三层栈帧(未执行到返回语句,栈帧还得活着)

······

函数最后一次被调用,压到方法栈最顶层(执行到返回语句,栈帧可以撤走了)


出栈阶段:

最顶层的方法执行到返回语句,将 Base Case (触底了) 的执行结果返回给上一个栈帧,然后撤走当前栈帧(即弹出栈)。

此时返回值作为参数传入到倒数第二个栈帧,被其中的函数作为计算的参数,执行到返回语句时,
同上,将结果返回给上一个栈帧,然后撤走当前栈帧。

······

出栈到最底下的那个栈帧,前面所有的计算结果一个传递给上一个,累积计算,直到传到祖宗十八代这里,宣告大结局。
这一代拿到前面十八代祖宗流传下来的值,计算完成,通过执行返回语句,将最终结果交给大boss,
出栈,这个家族的使命就完美结束了。

以上,即递归的整个流程分析。

递归写法如下:

public static void reversePrintNodesRecursively(Node head) {
    if (head == null) {
        return;
    }
    Node tmpNode = head.next;
    if (tmpNode != null) {
        reversePrintNodesRecursively(tmpNode);
    }
    System.out.println(head.data);
}