递归和推栈
首先我们看一下函数底层工作原理:
执行上下文:是一个内部数据结构,包含有关函数执行时的详细细节(控制流所在位置,当前变量,this的值,以及一些内部的细节
当函数嵌套调用时:
- 当前函数被暂停
- 相关的执行上下文被
执行上下文堆栈
的特殊数据结构保存 - 执行嵌套调用
- 调用结束都,再从堆栈中恢复执行上下文,并从停止的位置恢复外部函数
递归
简要讲一下递归,递归就是上面的函数嵌套调用模式,不断的将当前的上下文压入堆栈的顶端,执行嵌套后的操作;当运行到出口
return
时,在从堆栈的顶部一个个的恢复执行的过程所有的递归算法,都可以用循环算法实现,两者的区别
循环算法:一般情况下更加节省内存,更快;但代码结构不如递归直观
递归算法:算法很直观,易于维护;但占内存大
更好的理解递归:我们引入链表
链表
为什么要引入链表?链表这一数据类型在js中不常使用,一般都会使用数数组来,存储数据,但是,当我们使用
arr.unshift(obj)
操作必须对所有元素重新编号以便为新的元素obj
腾出空间,而且如果数组很大,会很耗时。如果我们确实需要快速插入/删除,则可以选择另一种叫做 [链表]的数据结构
链表元素:是一个使用递归定义的对象
let list = { value: 1, next: { value: 2, next: { value: 3, next: { value: 4, next: null } } } };
通过建立了这么一个链式结构:我们对插入删除元素更加的方便
>//比如:头插数据,删除中间节点 >let list = { value: 1 }; >list.next = { value: 2 }; >list.next.next = { value: 3 }; >list.next.next.next = { value: 4 }; >list = { value: "new item", next: list }; // 将新值添加到链表头部 >list.next = list.next.next; //删除value=1的节点
链表的缺点:
- 不能和数组一样通过下表快速查找引用
升级的链表:
- 双向链表:添加一个
prev
属性指向前一个节点,尾部加一个tail
变量指向尾部节点(尾部节点变化时,实时更新)
实例比较:
- 斐波那契数
//递归算法 function fib (n) { return n <= 1 ? n: fib(n-1) + fib(n-2); } //循环算法(自下而上动态递归) function fib (n) { let a = 1; let b = 1; for (let i = 2; i < n ; i++) { let c = a + b; a = b; b = c; } return b; } //两种算法直观看起来,递归逻辑关系非常的简单,但性能方面有一定的差距 //递归算法,算n较大时,非常的慢,而且很占内存!! 比如fib(77);非常慢
- 反向输出链表
let list = { value: 1, next: { value: 2, next: { value: 3, next: { value: 4, next: null } } } }; //递归:使用先遍历,后输出,天然使用上下堆栈的恢复特点 function printReverseList(list) { if (list.next) { printReverseList(list.next); } alert(list.value); } printReverseList(list); //循环操作:实际上也是新建了一个上下堆栈,存储对应的输出,然后,倒叙循环输出 function printReverseList(list) { let arr = []; let tmp = list; while (tmp) { arr.push(tmp.value); tmp = tmp.next; } for (let i = arr.length - 1; i >= 0; i--) { alert( arr[i] ); } } printReverseList(list);