阅读 570

🌲有关树遍历的javascript实现【前端-总结-leetcode算法题】

在这里插入图片描述

前言

二月的第一天,总结一下近段时间刷的有关树遍历的leetcode算法题,希望写完这篇文章的我和看完文章的你都有些收获吧。全篇主要讲的是有关树的遍历,使用前端javascript语言实现。当然有关树的操作还有很多需要的深入理解和总结的。今天就暂时先把树遍历理解全吧。🧐🧐

开干,先放导图:

在这里插入图片描述

1、树和二叉树

1.1、 树

1.1.1 树的基本概念

树是一种数据结构,它是由n(n>=1)个有限结点组成一个具有层次关系的集合。把它叫做“树”是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。它具有以下的特点: 每个结点有零个或多个子结点;没有父结点的结点称为根结点;每一个非根结点有且只有一个父结点;除了根结点外,每个子结点可以分为多个不相交的子树。 在这里插入图片描述

在javascript中并没有树,但是我们可以Object 和 Array构建树,比如构建上图的树:

const tree = {
  val:'a',
  children:[
    {
      val:'b',
      children:[
        {
          val:'d',
          children:[],
        },
        {
          val:'e',
          children:[],
        }
      ]
    },
    {
      val:'c',
      children:[
        {
          val:'f',
          children:[],
        },
        {
          val:'g',
          children:[],
        }
      ]
    }
  ]
}

复制代码

树的常用操作: 深度/广度优先遍历,先中后序遍历(二叉树)

1.1.2 树的深度优先遍历(DFS)

尽可能深的搜索树的分支

1、访问根节点

2、对根节点的children挨个进行深度优先遍历

👉 (递归)

在这里插入图片描述

  • 代码实现

(这里操作的树就是我们上面用javascript代码创建的树)

const dfs = (root) => {
  console.log(root.val)
  root.children.forEach(dfs)
  // root.children.forEach((child) => {dfs(child)})
  // 遍历节点的每个孩子节点,并且在孩子节点上使用dfs递归 继续遍历
}
dfs(tree);
复制代码

操作台结果

在这里插入图片描述

1.1.3 树的广度优先遍历(BFS)

先访问离节点最近的节点

先遍历兄弟节点,在遍历子节点。 采用队列实现,出队时添加子节点。

  • 操作思想

1、新建一个队列,把根节点入队

2、把对头出队,并访问

3、把对头的children挨个入队

4、重复第二,三步,直到队列为空

👉(队列) 在这里插入图片描述

  • 代码操作

(这里操作的树就是我们上面用javascript代码创建的树)

const bfs = (root) => {
  //1、新建一个队列,把根节点入队
  const q = [root]
  //4、重复第2,3步,直到队列为空
  while (q.length > 0) {
    //2、把对头出队,并访问
    const n = q.shift();
    console.log(n.val);
    // 3、把对头的children挨个入队
    n.children.forEach(child => {
      q.push(child);
    });
  }
}
bfs(tree);
复制代码

操作台打印的结果:

在这里插入图片描述

1.2、二叉树

1.2.1 二叉树的基本概念

二叉树是一个每个结点最多只能有两个分支的树,左边的分支称之为左子树,右边的分支称之为右子树。 用javascript创建一棵二叉树

在这里插入图片描述

代码如下:

const bt = {
  val:1,
  left:{
      val:2,
      left:{
        val:4,
      left:null,
      right:null
      },
      right:{
        val:5,
      left:null,
      right:null
      }
  },
  right:{
    val:3,
    left:{
      val:6,
      left:null,
      right:null
    },
    right:{
      val:7,
      left:null,
      right:null
    },
  },
};

复制代码

1.2.2 二叉树的先序遍历

递归版

1、访问根节点

2、对根节点的左子树进行先序遍历

3、对根节点的右子树进行先序遍历

✍️ 1-2-4-5-3-6-7 在这里插入图片描述

代码:

const preorder = (root) => {
  if(!root) { return }
  console.log(root.val);
  preorder(root.left);
  preorder(root.right)
};
复制代码

结果: 在这里插入图片描述

非递归版

用堆栈来模拟先序遍历(根,左,右)递归的过程

用栈这个数据结构

1、新建一个stack代表函数的调用堆栈,刚开始堆栈里面存放root根节点

2、访问根节点的值,用pop()方法弹出

3、先把right右节点推到栈中,再把left左节点推到栈中(因为栈是后进先出),我们需要得到的是先访问左节点再访问右节点

4、当stack中有值的时候,一直循环2,3操作

代码:

const preorder = (root) => {
  if(!root) {return}
  const stack = [root];
  while(stack.length){
    const n = stack.pop();
    console.log(n.val);//访问这个节点
    if(n.right) stack.push(n.right);
    if(n.left) stack.push(n.left);
  }
  }
复制代码

1.2.3 二叉树的中序遍历

递归版

1、对根节点的左子树进行中序遍历

2、访问根节点

3、对根节点的右子树进行中序遍历

✍️ 4-2-5-1-6-3-7

在这里插入图片描述

代码:

const inorder = (root) => {
 if(!root){return}
 inorder(root.left);
 console.log(root.val);
 inorder(root.right);
}
复制代码

在这里插入图片描述

非递归版

用堆栈来模拟中序遍历(左,根,右)递归的过程

1、新建一个stack代表函数的调用堆栈

2、对于中序遍历来说,最开始的时候就是递归root.left,因此我们用栈模拟的时候,第一步就是要把所有的左子树都丢到栈中,使用指针p,最开始为root

3、当p有值的情况下,让p= p.left,在遍历的同时,把它推到栈中

4、把最近左节点弹出来,访问它

5、访问右节点,将指针指向右节点p = n.right;

6、再做一次循环while (stack.length || p){}

const inorder = (root) => {
  if(!root) {return;}
  const stack = [];
  let p = root;
  while (stack.length || p) {
    while(p) {
      stack.push(p);
      p = p.left;
    }
    const n = stack.pop();
    console.log(n.val);
    p = n.right;
  }
}
复制代码

1.2.4 二叉树的后序遍历

递归版

1、对根节点的左子树进行后序遍历

2、对根节点的右子树进行后序遍历

3、访问根节点

✍️ 4-5-2-6-7-3-1在这里插入图片描述

代码:

const postorder = (root) => {
  if(!root) { return }
  postorder(root.left)
  postorder(root.right);
  console.log(root.val);
}
复制代码

在这里插入图片描述

非递归版

用堆栈来模拟后序遍历(左,右,根)递归的过程

我们知道,先序遍历是(根左右)

因此,我们可以先做先序遍历,再使用入栈操作,再利用栈的后进先出,(这里要注意哦,我们需要把先序遍历变成根右左,因此我们还需要改动一下)

1、创建一个栈stack,代表函数的调用堆栈

2、创建一个栈outputStack 实现倒置的栈

调用先序遍历的方法

const stack = [root];
  while(stack.length){
    const n = stack.pop();
    console.log(n.val);
    if(n.right) stack.push(n.right);
    if(n.left) stack.push(n.left);
  }
复制代码

3、将先序遍历的 根右左变成根左右

  if(n.left) stack.push(n.left);
  if(n.right) stack.push(n.right);
复制代码

4、将节点的左右子节点都压入栈

5、用pop()把存放在栈outputStack的节点都弹出来

代码:

const postorder = (root) => {
  if(!root) {return}
  const stack = [root];
  const  outputStack = [];
  while(stack.length){
    const n = stack.pop();
    //console.log(n.val);//访问这个节点
    outputStack.push(n); //把先序遍历访问这个节点的值,变成把节点push推入栈中
    if(n.left) stack.push(n.left);
    if(n.right) stack.push(n.right);
  }
  //倒序输出 并访问
    while(outputStack.length) {
      const n =  outputStack.pop();
      console.log(n.val);
    }
  }
复制代码

2、 leetcode算法题

94、 二叉树的中序遍历

题目:二叉树的中序遍历

  • 递归版
/**
 * @param {TreeNode} root
 * @return {number[]}
 */
var inorderTraversal = function(root) {
    const res = []
    const rec = (n) => {
        if(!n) {return}
        rec(n.left);
        res.push(n.val)
        rec(n.right)
    }
    rec(root);
   return res
};
复制代码
  • 非递归(栈)
/**
 * @param {TreeNode} root
 * @return {number[]}
 */
var inorderTraversal = function(root) {
    const res = [];
    //模拟堆栈
    const stack = [];
    let p = root;//指针
   while(stack.length || p) {
    while(p) {
        //先把左子树全部丢在栈里面去
        stack.push(p);
        p = p.left;//将指针指向left左节点,并把它推到栈中
    }
    //把最近左节点弹出来(栈顶的节点元素)
    const n = stack.pop();
    res.push(n.val);
    //访问右节点
    p = n.right
   }
   return res
};
复制代码

102、 二叉树的层序遍历

题目:二叉树的层序遍历

/**
 * @param {TreeNode} root
 * @return {number[][]}
 */
var levelOrder = function(root) {
    if (!root) {return [];}
    const q = [[root,0]];
    const res = [];//存取结构
    while(q.length) {
        const [n,l] = q.shift();
        // console.log(n.val,l);
        //当存取结果的数组res没有东西的时候,也就是为空[],把节点的值push到里面
        //其他的值就根据层级放
        if(!res[l]){
            res.push([n.val]);
        } else{
            res[l].push(n.val)
        }
        if(n.left) q.push([n.left,l + 1]);
        if(n.right) q.push([n.right,l + 1]);
        //如果当前节点存在左右节点,那么遍历这两个节点时的层级要加一,
        //因为他们在当前节点的下一级
    }
  return res;
};
复制代码

104、二叉树的最大深度

题目:二叉树的最大深度

🥭深度优先

/**
 * @param {TreeNode} root
 * @return {number}
 */
var maxDepth = function (root) {
  let res = 0;
  //l 表示层
  const dfs = (n,l) => {
    if(!n) {return;}
    if(!n.left && !n.right) {
      res = Math.max(res,l);
    }
    console.log(n,l);
    dfs(n.left,l+1);
    dfs(n.right,l+1);
  };
  dfs(root,1);
  return res
}
复制代码

111、 二叉树的最小深度

题目:二叉树的最小深度

🥭 广度优先

/**
 * @param {TreeNode} root
 * @return {number}
 */
//二叉树最小深度  ---广度优先
var minDepth = function(root) {
    if(!root) {return 0}
    const q = [[root,1]];
    while(q.length) {
      const [n,l] = q.shift();
      if(!n.left && !n.right) {
        return l;
      }
      if (n.left) q.push([n.left,l + 1]);
      if (n.right) q.push([n.right,l + 1]);
    }
};
复制代码

112、路径总和

题目:二叉树的最小深度

/**
 * @param {TreeNode} root
 * @param {number} targetSum
 * @return {boolean}
 */
var hasPathSum = function(root, targetSum) {
 if (!root) return false;
 let res = false;//标志,判断是否满足targetSum
 const dfs = (n,s) => {
     if (!n.left && !n.right && s === targetSum) {
         res = true;
     }
     if (n.left) dfs(n.left, s + n.left.val)
     if (n.right) dfs(n.right,s + n.right.val);
 };
 dfs(root,root.val);
 return res;
};
复制代码

3、前端有关

遍历JSON的所有值

Object.keys() Object.keys() 方法会返回一个由一个给定对象的自身可枚举属性组成的数组

//深度优先遍历

const json = {
  a:{
    b:{
      c:1
    }
  },
  d:[1,2]
}

const dfs = (n) => {
  console.log(n); //{ a: { b: { c: 1 } }, d: [ 1, 2 ] }
  console.log(Object.keys(n)) //[ 'a', 'd' ]
  Object.keys(n).forEach(k => {
    dfs(n[k])
  })
}
dfs(json)
复制代码

结果:


{ b: { c: 1 } }
[ 'b' ]
{ c: 1 }
[ 'c' ]
1
[]
[ 1, 2 ]
[ '0', '1' ]
1
[]
2
[]

复制代码

总结

好啦,今天的有关树的遍历就说到这里。就当是总结,leetcode的题目也没有说的那么详细,可以参考一下前面的树的深度/广度优先遍历,和二叉树的先中后序遍历。

😁 二月,祝你好运!

😳 卑微码字人,求赞

💌 炒鸡感谢

🙈 笔芯

在这里插入图片描述