本文语言类型:JavaScript

有一个理论是“所有的递归都可以用堆栈实现”,道理大家都懂,实现起来怎么样呢?

用js的前端开发者或许都不关心算法,本文尝试用前端们熟悉的编码形式,让前端能更容易理解。我就从最简单的二叉树的深度优先搜索(DFS)入手。

数据结构定义

function Node(name) {this.name = name;this.left = null;this.right = null;this.setLeft = function(left) {this.left = left;return this;}this.setRight = function(right) {this.right = right;return this;}this.visit = function() {console.log(`visit ${this.name}`);}
}

上面是一个树节点对象的构造方法。每个对象的leftright分别指向自己的左右子节点,还有一个visit()方法,调用它就表示遍历到这个节点了。

构造一棵二叉树

根据上面的方法,我们就可以构造出一棵简单的二叉树了:

let a = new Node('a')
let b = new Node('b')
let c = new Node('c')
let d = new Node('d')
let e = new Node('e')
let f = new Node('f')
let g = new Node('g')
let h = new Node('h')a.setLeft(b).setRight(f)
b.setLeft(c).setRight(d)
d.setLeft(e)
f.setLeft(g).setRight(h)

递归实现法

递归的实现就很简单了,这里不用解释:

//recursive
function dfs1(node) {if(!node) return;node.visit()dfs1(node.left)dfs1(node.right)
}dfs1(a)

运行结果:

visit a
visit b
visit c
visit d
visit e
visit f
visit g
visit h

转成堆栈模式

递归函数实际上在编译器内部维护了一个隐藏的工作栈。递归发生一次,就进行一次进栈、递归结束一次,就进行一次弹栈。当这个栈变成空的时候,也是整个递归函数结束的时候。

以上递归函数dfs1的特征是:

  • 1.所有的操作都在栈顶的这个节点上进行,首先访问这个节点,此时是这个节点第一次出现在栈顶;
  • 2.如果当前节点有左子节点,就压入它的左子节点入栈,当左子节点处理完毕弹出时,当前节点又回到栈顶;
  • 3.当前节点第二次回到栈顶时,就要考虑右子节点了,压入右子节点入栈,当左子节点处理完毕弹出时,当前节点第三次回到栈顶;

当前节点第三次回到栈顶时,表示本节点处理完毕,可以弹出了,本节点使命结束,转而处理它的父节点。

递归转非递归的难点是判断何时弹栈。经过以上分析,无非是以上三个步骤都处理完毕时——也就是节点第三次出现在栈顶时、也就是dfs1中的三行语句都执行完毕时——弹栈。

那么,可以设计一个数据结构:{ node, rest: 3 },模拟dfs1的作用域保存在堆栈中,每当这个作用域出现在栈顶一次,rest自减1,当rest为0时,这个作用域就可以弹出了。

//use stacks
function dfs2(node) {if(!node) return;let stack = []stack.push(__makeScope(node))while(true) {let Scope = stack[stack.length - 1]let current = Scope.nodeif(Scope.rest === 3) {Scope.rest--current.visit()} else if(Scope.rest === 2) {Scope.rest--if(current.left) {stack.push(__makeScope(current.left))}} else if(Scope.rest === 1) {Scope.rest--if(current.right) {stack.push(__makeScope(current.right))}} else/* if(Scope.rest === 0)*/ {stack.pop()if(stack.length === 0) break;}}function __makeScope(node) {return { node, rest: 3 }}
}dfs2(a)

性能分析

当树节点比较少的时候,两个函数性能差别不大,但是当节点数达到10万数量级时(老旧浏览器会更低),dfs1直接"stack overflow",dfs2依然坚挺。从这里或许也能看出递归的弱点。

引申

这里先考虑了二叉树,每个节点的处理次数为3,那么n叉树的情况下,每个节点处理次数就是n+1,以上程序还是很方便改写的,无非就是子树用数组来存储。这个以后有时间再写。

本文为原创,转载请注明出处

递归求二叉树的深度_优雅地用堆栈替代递归实现二叉树的深度优先搜索相关推荐

  1. 二叉树的深度(前序 中序 后序 递归非递归搜素)、广度、搜索 C++

    a b c 使用 1 2 3 表示 /* 描述:二叉树的深度(前序 中序 后序 递归非递归搜素).广度.搜索 作者:jz 日期:20140819 */ #include<stdio.h> ...

  2. C语言用递归求斐波那契数,让你发现递归的缺陷和效率瓶颈

    C语言用递归求斐波那契数,让你发现递归的缺陷和效率瓶颈 分享到: QQ空间 新浪微博 腾讯微博 豆瓣 人人网 递归是一种强有力的技巧,但和其他技巧一样,它也可能被误用. 一般需要递归解决的问题有两个特 ...

  3. 二叉树的深度_十七:二叉树的最小深度

    二叉树的最小深度:从根结点到叶结点依次经过的结点(含根.叶结点)形成树的一条路径,最短路径的长度为树的最小深度. 算法一 /** * @description 二叉树最小深度 * @param {*} ...

  4. 二叉树的深度_[LeetCode 104] 二叉树的深度

    题目描述 给定一个二叉树,找出其最大深度. 二叉树的深度为根节点到最远叶子节点的最长路径上的节点数. 说明: 叶子节点是指没有子节点的节点. ** 示例:** 给定二叉树 [3,9,20,null,n ...

  5. java二叉树是什么_树的基本概念以及java实现二叉树

    树具有的特点有: (1)每个结点有零个或多个子结点 (2)没有父节点的结点称为根节点 (3)每一个非根结点有且只有一个父节点 (4)除了根结点外,每个子结点可以分为多个不相交的子树. 树的基本术语有: ...

  6. python关于递归求组合与组合数-通过阶乘, fibonacci加深对递归的理解

    python关于递归求组合与组合数: 首先需要找到递归的突破口. 要求如下: Exercise 3: Enumerating Combinations You need to write a func ...

  7. 语言非递归求解树的高度_算法素颜(11):无死角“盘”它!二分查找树

    引言 <菜鸟也能"种"好二叉树!>一文中提到了:为了方便查找,需要进行分层分类整理.而满足这种目标的数据结构之一就是树. 树的叶子节点可以看作是最终要搜寻的目标物:叶子 ...

  8. java图遍历求最长路径_如何在Java中使用递归实现矩阵中最长路径的返回

    我正试图用递归来解决这个问题. 问题是:对于二维正整数数组,我如何返回最长路径(步骤),以便最长路径的每个单元格中的值是从整数的降序序列开始的,并且每个单元格和单元格之间的差异是一个给定的数字(num ...

  9. 《剑指offer》-- 树的子结构、二叉树的镜像、二叉树的深度、平衡二叉树

    一. 树的子结构: 1.题目: 输入两棵二叉树A,B,判断B是不是A的子结构.(ps:我们约定空树不是任意一个树的子结构. 2.解题思路: 这个题比较简单,利用递归的方式就可以判断B是不是A树的子结构 ...

最新文章

  1. 网站被降权后该进行检查哪些问题?
  2. 类加载过程(加载+验证+准备+解析+初始化)
  3. tableau必知必会之学做直观的华夫饼图(Waffle Chart)
  4. Photoshop通道抠出散乱的儿童头发
  5. 女人要的安全感到的是什么?
  6. 【MVC】使用FormCollection获取Form表单数据
  7. cdr圆形渐变填充怎么设置_玩出新花样|渐变应用于形状
  8. 嵌入式入门必去的网站 —— 介绍的非常详细
  9. 梯形法则 matlab代码,matlab第二章常微分方程的数值解法
  10. java九宫格代码_Java实现九宫格的简单实例
  11. C++图书馆资料管理系统
  12. 一个矩阵类,很好用,分享给大家
  13. 苹果手机上滑动会卡顿_苹果手机Safari浏览器下滑动卡顿的问题
  14. This Apple ID has not yet been used in the ITunes Store/此Apple ID尚未在iTunes Store使用过
  15. 如何魔改Xilinx Vivado 的MIG IP核
  16. 大话卫星导航中的信号处理系列文章——GPS信号L1频点的中频数据生成与验证
  17. 微信小程序页面顶部出现一段空白解决方法
  18. 2021年危险化学品经营单位主要负责人考试资料及危险化学品经营单位主要负责人新版试题
  19. 数据结构课程设计(八)---家谱管理系统(十几个功能)
  20. orb_slam3实现保存/加载地图功能and发布位姿功能

热门文章

  1. goland go test_七天用Go写个docker(第一天)
  2. C++ reference很全面
  3. java集合作为参数 传递的是_Java:数组和集合类作为参数传递时的差别
  4. 调用python接口并画图_【PySpark源码解析】教你用Python调用高效Scala接口
  5. IOS设备唯一标示符的方案比较
  6. centos7环境下ELK部署之elasticsearch
  7. 阿里资深技术专家:谁说程序员是吃“青春饭”的?
  8. 专访驭势科技吴甘沙:无人驾驶硝烟弥漫,“创造”才有未来|封面人物
  9. WAF指纹识别和XSS过滤器绕过技巧
  10. linux下装jdk以及failed /usr/local/jdk1.6.0_10/jre/lib/i386/client/libjvm.so,