前言

在计算机科学中, 树(tree) 是一种广泛使用的抽象数据类型(ADT),是一类非线性数据结构。树在计算机领域得到广泛应用,尤其二叉树最为常用。

树的相关概念:

  • 结点:每个元素称为结点
  • 树根:根节点
  • 度:一个结点含有的子结点的个数称为该结点的度
  • 叶子节点:度为0的节点

一、二叉树

概念:每个节点最多含有两个子树的树称为二叉树。

1.1、遍历二叉树

二叉树有两种遍历深度遍历和广度遍历,其中深度遍历有前序、 中序和后序三种遍历方法。 广度遍历就是层次遍历,按层的顺序一层一层遍历。

四种遍历的主要思想:

  • 前序:先访问根,然后访问左子树,最后访问右子树,DLR。
  • 中序:先访问左子树,然后访问根,最后访问右子树,LDR。
  • 后序:先后访问左子树,然后访问右子树,最后访问根,LRD。
  • 广度:按层的顺序一层一层遍历。

例如a b*(c-d)-e/f,该表达式用二叉树表示:

对他分别进行遍历:

  • 前序:- a*b-cd/ef
  • 中序:a b*c-d-e/f
  • 后序:abcd-* ef/-
  • 广度:- /a*efb-cd

1.2、用js表示二叉树

我们用js的对象来表示二叉树,对象拥有三个属性,left、value、right,分别是左子树,值和右子树,上面a b*(c-d)-e/f的例子我们用js可以这样表示。

var tree = {
    value: '-',
    left: {
        value: ' ',
        left: {
            value: 'a'
        },
        right: {
            value: '*',
            left: {
                value: 'b'
            },
            right: {
                value: '-',
                left: {
                    value: 'c'
                },
                right: {
                    value: 'd'
                }
            }
        }
    },
    right: {
        value: '/',
        left: {
            value: 'e'
        },
        right: {
            value: 'd'
        }
    }
}

1.3、前序遍历算法

前序:有两种方法,第一种很简单就是直接使用递归的办法。

function preOrder(treeNode) {
  if(treeNode) {
    console.log(treeNode.value); // 打印出来代表访问这个节点
    preOrder(treeNode.left);
    preOrder(treeNode.right);
  }
}

算法思路很简单,先遍历根节点,然后递归遍历左子树,左子树遍历结束后,递归右子树。

第二种非递归遍历

function preOrder(treeNode) {
  if(treeNode) {
    var stack = [treeNode]; //将二叉树压入栈
    while (stack.length !== 0) {
      treeNode = stack.pop(); // 取栈顶
      document.getElementById('text').appendChild(document.createTextNode(treeNode.value)); // 访问节点
      if(treeNode.right) stack.push(treeNode.right); // 把右子树入栈
      if(treeNode.left) stack.push(treeNode.left); // 把左子树入栈
    }
  }
}

第二种是使用栈的思想,我们都知道,栈是先进后出的一种数据结构,我们先把根节点入栈,然后取栈顶,访问根节点,分别把右左子树入栈,这边必须右边先入栈,因为我们是要先从左边开始访问的,所以右子树先入栈,然后就循环取出栈,直到栈空。

1.4、中序遍历算法

中序递归算法:

function InOrder(treeNode) {
    if(treeNode) {
        InOrder(treeNode.left);
        document.getElementById('text').appendChild(document.createTextNode(treeNode.value));
        InOrder(treeNode.right);
    }
}

和前序递归算法同样的思路,只是访问节点位置不同

第二种:

function InOrder(node) {
  if(node) {
    var stack = [];             // 建空栈
    //如果栈不为空或结点不为空,则循环遍历
    while (stack.length !== 0 || node) { 
      if (node) {               //如果结点不为空
          stack.push(node);     //将结点压入栈
          node = node.left;     //将左子树作为当前结点
      } else {                  //左子树为空,即没有左子树的情况
          node = stack.pop();   //将结点取出来
          document.getElementById('text').appendChild(document.createTextNode(node.value));
          node = node.right;    //将右结点作为当前结点
      }
    }
  }
}

非递归中序算法的思想就是,把当前节点入栈,然后遍历左子树,如果左子树存在就一直入栈,直到左子树为空,访问但前节点,然后让右子树入栈。

1.5、后序遍历算法

第一种:递归遍历算法

function postOrder(node) {
    if (node) { //判断二叉树是否为空
        postOrder(node.left); //递归遍历左子树
        postOrder(node.right); //递归遍历右子树
        document.getElementById('text').appendChild(document.createTextNode(node.value));
    }
}

第二种:非递归遍历算法

function postOrder(node) {
    if (node) { //判断二叉树是否为空
        var stack = [node]; //将二叉树压入栈
        var tmp = null; //定义缓存变量
        while (stack.length !== 0) { //如果栈不为空,则循环遍历
            tmp = stack[stack.length - 1]; //将栈顶的值保存在tmp中
            if (tmp.left && node !== tmp.left && node !== tmp.right) { //如果存在左子树,node !== tmp.left && node !== tmp.righ 是为了避免重复将左右子树入栈
                stack.push(tmp.left);   //将左子树结点压入栈
            } else if (tmp.right && node !== tmp.right) { //如果结点存在右子树
                stack.push(tmp.right);  //将右子树压入栈中
            } else {
                document.getElementById('text').appendChild(document.createTextNode(stack.pop().value));
                node = tmp;
            }
        }
    }
}

这里使用了一个tmp变量来记录上一次出栈、入栈的结点。思路是先把根结点和左树推入栈,然后取出左树,再推入右树,取出,最后取根结点。

下面是用这个算法遍历前面那个二叉树的过程

        stack       tmp         node        打印
初始 :  -          null         -
第1轮:  -           -           -
第2轮:  - a                    -
第3轮:  -           a           a            a
第4轮:  - *                    a 
第5轮:  - *b        *           a 
第6轮:  - *         b           b            b
第7轮:  - *-        *           b
第8轮:  - *-c       -           b
第9轮:  - *-        c           c            c
第10轮: - *-d       -           c
第11轮: - *-        d           d            d
第12轮: - *         -           -            -
第13轮: -           *           *            *
第14轮: -                                 
第15轮: -/          -                      
第16轮: -/e         /          
第17轮: -/          e           e            e
第18轮: -/f         /           e           
第19轮: -/          f           f            f
第20轮: -           /           /            /
第21轮:             -           -            -

结果:abcd-* ef/-

1.6、按层遍历算法

function breadthTraversal(node) {
    if (node) {                             //判断二叉树是否为空
        var que = [node];                   //将二叉树放入队列
        while (que.length !== 0) {          //判断队列是否为空
            node = que.shift();             //从队列中取出一个结点
            document.getElementById('text').appendChild(document.createTextNode(node.value));   //将取出结点的值保存到数组
            if (node.left) que.push(node.left);   //如果存在左子树,将左子树放入队列
            if (node.right) que.push(node.right); //如果存在右子树,将右子树放入队列
        }
    }
}

使用数组模拟队列,首先将根结点归入队列。当队列不为空时,执行循环:取出队列的一个结点,如果该节点有左子树,则将该节点的左子树存入队列;如果该节点有右子树,则将该节点的右子树存入队列。

二、算法题

1.1、二叉树的最大深度

给定一个二叉树,找出其最大深度。

二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。

比如下面这个二叉树,返回深度3。

    3
   / \
  9  20
    /  \
   15   7

const tree = {
    value: 3,
    left: {
        value: 9
    },
    right: {
        value: 20,
        left: { value: 15 },
        right: { value: 9 }
    }
}

递归算法:递归算法的思路很简单,先拿到左子树最深层,再拿到右子树最深层,取他们最大值就是树的深度。

var maxDepth = function(root) {
  if (!root) {
    return 0;
  }
  const leftDeep = maxDepth(root.left)   1;
  const rightDeep = maxDepth(root.right)   1;
  return Math.max(leftDeep, rightDeep);
};
/*
maxDepth(root) = maxDepth(root.left)   1  = 2
maxDepth(root.left) = maxDepth(root.left.left)   1 = 1
maxDepth(root.left.left) = 0;

maxDepth(root) = maxDepth(root.right)   1 = 3
maxDepth(root.right) = maxDepth(root.right.right)   1 = 2
maxDepth(root.right.right) = maxDepth(root.right.right.right)   1 = 1
maxDepth(root.right.right.right) = 0
*/

1.2、二叉树的所有路径

给定一个二叉树,返回所有从根节点到叶子节点的路径。

比如:

    3
   / \
  9  20
    /  \
   15   7
 
返回:['3->9', '3->20->15', '3->20->7']

使用递归的方法:

var binaryTreePaths = function(root) {
  if (!root) return [];
  const res = [];
  function dfs(curNode, curPath) {
    if(!curNode.left && !curNode.right) {
      res.push(curPath);
    }
    if(curNode.left) {
      dfs(curNode.left, `${curPath}->${curNode.left.value}`)
    }
    if(curNode.right) {
      dfs(curNode.right, `${curPath}->${curNode.right.value}`)
    }
  }
  dfs(root, `${root.value}`);
  return res;
};

总结

到此这篇关于利用JS实现二叉树遍历算法的文章就介绍到这了,更多相关JS二叉树遍历算法内容请搜索Devmax以前的文章或继续浏览下面的相关文章希望大家以后多多支持Devmax!

利用JS实现二叉树遍历算法实例代码的更多相关文章

  1. html5 拖拽及用 js 实现拖拽功能的示例代码

    这篇文章主要介绍了html5 拖拽及用 js 实现拖拽,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  2. amaze ui 的使用详细教程

    这篇文章主要介绍了amaze ui 的使用详细教程,本文通过多种方法给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  3. 初识Swift集合之字典集合

    这个函数也会返回被替换或者增加的值。

  4. swift的一些知识点演练

    表示可以有值,也可以没有值//?如果对象为空,就不会调用后面的方法,感觉上和oc中给nil发送消息类似varstr:Nsstring?str="hello"//打印可选项的时候,同时会输出一个Optional,提示开发者,这是一个可选项println(str?.length)letl=10//目前的代码存在什么风险?如果str没有设置初始值,会直接崩溃//苹果把判断对象是否有内容的工作交给了程序员//letlen=l+str!用来快速判断对象是否为nilletlen2=l+(str?0)//以下代码和上面

  5. swift皮筋弹动发射飞机ios源码

    这是一个款采用swift实现的皮筋弹动发射飞机游戏源码,游戏源码比较详细,大家可以研究学习一下吧。

  6. swift 基础笔记四数组

  7. Swift值字典使用

    字典是一种用来存放相同类型的数据项的集合。Swift中字典的概念和现实世界中的字典的概念很相似,都是通过索引来查里面特定的值。修改一个值5、删除字典键值对四、字典遍历同数组一样,字典遍历也需要使用forin循环。

  8. Swift学习笔记十三——区间运算符和for-in循环

    区间运算符RangeOperator也是Swift的一个比较突出的特点。可以用来表示一段数据的区域。区间运算符主要可以分为以下两类:ClosedRangeOperator:闭区间[a,b]a...b:注意:a和b之间是三个点Half-ClosedRangeOperator:前闭后开区间a..

  9. Swift遍历数组的三种方式

    1.forindexin0..

  10. Swift入门五——数组Array

    集合集合的定义Swift中提供了两种数据结构用于存放数据的集合,分别是数组和字典。一共有三种方法来定义数组的类型:第一种是数组类型的完整定义,即Array关键字加上一对尖括号,括号内写上数组元素的类型。1]其实是一个SubArray,在Swift中它的类型叫做ArraySlice,即Int类型的数组切片,而右边是一个Array类型变量,根据Swift类型安全的特性,这样的操作自然是被禁止的。

随机推荐

  1. js中‘!.’是什么意思

  2. Vue如何指定不编译的文件夹和favicon.ico

    这篇文章主要介绍了Vue如何指定不编译的文件夹和favicon.ico,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教

  3. 基于JavaScript编写一个图片转PDF转换器

    本文为大家介绍了一个简单的 JavaScript 项目,可以将图片转换为 PDF 文件。你可以从本地选择任何一张图片,只需点击一下即可将其转换为 PDF 文件,感兴趣的可以动手尝试一下

  4. jquery点赞功能实现代码 点个赞吧!

    点赞功能很多地方都会出现,如何实现爱心点赞功能,这篇文章主要为大家详细介绍了jquery点赞功能实现代码,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  5. AngularJs上传前预览图片的实例代码

    使用AngularJs进行开发,在项目中,经常会遇到上传图片后,需在一旁预览图片内容,怎么实现这样的功能呢?今天小编给大家分享AugularJs上传前预览图片的实现代码,需要的朋友参考下吧

  6. JavaScript面向对象编程入门教程

    这篇文章主要介绍了JavaScript面向对象编程的相关概念,例如类、对象、属性、方法等面向对象的术语,并以实例讲解各种术语的使用,非常好的一篇面向对象入门教程,其它语言也可以参考哦

  7. jQuery中的通配符选择器使用总结

    通配符在控制input标签时相当好用,这里简单进行了jQuery中的通配符选择器使用总结,需要的朋友可以参考下

  8. javascript 动态调整图片尺寸实现代码

    在自己的网站上更新文章时一个比较常见的问题是:文章插图太宽,使整个网页都变形了。如果对每个插图都先进行缩放再插入的话,太麻烦了。

  9. jquery ajaxfileupload异步上传插件

    这篇文章主要为大家详细介绍了jquery ajaxfileupload异步上传插件,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  10. React学习之受控组件与数据共享实例分析

    这篇文章主要介绍了React学习之受控组件与数据共享,结合实例形式分析了React受控组件与组件间数据共享相关原理与使用技巧,需要的朋友可以参考下

返回
顶部