实际上,编译器就是经过两个栈来实现的。其中一个保存操做数的栈,另外一个是保存运算符的栈。咱们从左向右遍历表达式,当遇到数字,咱们就直接压入操做数栈;当遇到运算符,就与运算符栈的栈顶元素进行比较。java
若是比运算符栈顶元素的优先级高,就将当前运算符压入栈;若是比运算符栈顶元素的优先级低或者相同,从运算符栈中取栈顶运算符,从操做数栈的栈顶取 2 个操做数,而后进行计算,再把计算完的结果压入操做数栈,继续比较。数据结构
要想写出没有 bug 的循环队列的实现代码,我我的以为,最关键的是,肯定好队空和队满的断定条件。函数
编写递归代码的关键是,只要遇到递归,咱们就把它抽象成一个递推公式,不用想一层层的调用关系,不要试图用人脑去分解递归的每一个步骤调试
递归代码要警戒堆栈溢出日志
我在 “栈” 那一节讲过,函数调用会使用栈来保存临时变量。每调用一个函数,都会将临时变量封装为栈帧压入内存栈,等函数执行完成返回时,才出栈。系统栈或者虚拟机栈空间通常都不大。若是递归求解的数据规模很大,调用层次很深,一直压入栈,就会有堆栈溢出的风险。code
为了不重复计算,咱们能够经过一个数据结构(好比散列表)来保存已经求解过的 f (k)。当递归调用到 f (k) 时,先看下是否已经求解过了。若是是,则直接从散列表中取值返回,不须要重复计算,这样就能避免刚讲的问题了。递归
public int f(int n) { if (n == 1) return 1; if (n == 2) return 2; // hasSolvedList 能够理解成一个 Map,key 是 n,value 是 f(n) if (hasSolvedList.containsKey(n)) { return hasSovledList.get(n); } int ret = f(n-1) + f(n-2); hasSovledList.put(n, ret); return ret; }
递归弊端: 栈溢出、重复计算、函数调用耗时多、空间复杂度高等,因此,在编写递归代码的时候,必定要控制好这些反作用。
调试递归: 1. 打印日志发现递归值。2. 结合条件断点进行调试。队列