javascript数据结构之“树” 附leetcode算法题

  • 一种分层数据的抽象模型
  • 前端工作中常见的树包括: DOM树、级联选择(省市区)、树形控件
  • js中没有树,但可以用Object和Array构建树
  • 常用操作:深度/广度优先遍历、先中后序遍历

深度优先遍历:尽可能深的搜索树的分支
广度优先遍历:先访问离根节点最近的节点,数字即遍历顺序
在这里插入图片描述

深度优先遍历算法
  • 访问根节点
  • 对根节点children挨个进行深度优先遍历
const tree = {
	val: 'a',
	children: [{
		val: 'b',
		children: [
			{val: 'd', children: []},
			{val: 'e', children: []}
		]
	},{
		val: 'c', children: [
			{val: 'f', children:[]},
			{val: 'g', children:[]}
		]
	}]
}
const dfs = (root) = {
	console.log(root.val) // a b d e c f g
	root.children.forEach(dfs)
}
广度优先遍历算法
  • 新建一个队列,把根节点入队
  • 把对头出对并访问
  • 把对头的children挨个入队
  • 重复第二、三步,直到队列为空
const bfs = (root) => {
	const q = [root]
	while(q.length > 0) {
		const n = q.shift()
		console.log(n.val) // a b c d e f g
		n.children.forEach(child => {
			q.push(child)
		})
	}
}

闻风丧胆的二叉树

  • 树中每个节点最多有两个字节点
  • 在js中通常用Object来模拟二叉树
// 二叉树代码写法
const binaryTree = {
	val: 1,
	left: {
		val: 2,
		left: null,
		right: null
	},
	right: {
		val: 3,
		left: null,
		right: null
	}
}
二叉树先序遍历
  • 访问根节点
  • 对根节点的左子树进行先序遍历
  • 对根节点的右子树进行先序遍历
const binaryTree = {
	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
	    }
	}
}
// 递归版
const preorder = (root) => {
  if (!root) return;
  console.log(root.val) // 1 2 4 5 3 6 7
  preorder(root.left)
  preorder(root.right)
}

preorder(binaryTree)

// 非递归版 利用栈的后进先出
const preorder = (root) => {
  if (!root) return;
  const stack = [root]
  while(stack.length) {
  	const n = stack.pop()
  	console.log(n.val)  // 1 2 4 5 3 6 7
  	if (n.right) stack.push(n.right)
  	if (n.left) stack.push(n.left)
  }
}
二叉树中序遍历
  • 对根节点的左子树进行中序遍历
  • 访问根节点
  • 对根节点的右子树进行中序遍历
// 递归版
const inorder = (root) => {
  if (!root) return;
  inorder(root.left)
  console.log(root.val) // 4 2 5 1 6 3 7
  inorder(root.right)
}

inorder(binaryTree)
// 非递归版
const preorder = (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) // 4 2 5 1 6 3 7
	  p = n.right
  }
}

二叉树后序遍历
  • 对根节点的左子树进行后序遍历
  • 对根节点的右子树进行后序遍历
  • 访问根节点
// 递归版
const postorder = (root) => {
  if (!root) return;
  postorder(root.left)
  postorder(root.right)
  console.log(root.val) // 4 5 2 6 7 3 1
}

postorder(binaryTree)

// 非递归版
const postorder = (root) => {
  if (!root) return
  const stack = [root]
  const outputstack = []

  while (stack.length) {
    const n = stack.pop()
    outputstack.push(n)

    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)
  }
}
leetcode104:二叉树最大深度

在这里插入图片描述

  • 解题思路
    • 求最大深度,考虑使用深度优先遍历
    • 深度优先遍历过程中,记录每个节点所在的层级,找出最大的层级即可
  • 解题步骤
    • 新建一个变量,记录最大深度
    • 深度优先遍历整棵树,并记录每个节点的层级,同时不断刷新最大深度这个变量
    • 遍历结束返回最大深度变量
  • code
	// 时间复杂度O(n)  
	// 最差空间复杂度O(n)  最好的情况O(log n)   虽然代码中没有定义变量,但是递归存在调用栈,所以最差的情况即有多少个节点就有多少层,调用栈即存在多少,即O(n)
	var maxDep = function(root) {
	let res = 0
	const dfs = (n, level) => {
		if(!n) return
		if (!n.left && !n.right)  res = Math.max(res, level)
		console.log(n.val)
		dfs(n.left, level + 1);
		dfs(n.right, level + 1)
	}
	dfs(root, 1)
   return res
leetcode111:二叉树最小深度

在这里插入图片描述

  • 解题思路
    • 求最小深度,虽然可以使用深度优先遍历,但考虑使用广度优先遍历
    • 广度优先遍历过程中,遇到叶子节点,停止遍历,返回节点层级,此时不需要再次遍历其他叶子节点,比深度优先遍历快
  • 解题步骤
    • 广度优先遍历整棵树,并记录每个节点的层级
    • 遇到叶子节点,返回节点层级,停止遍历
  • code
// 时间复杂度O(n) 空间复杂度O(n)
var minDep = function(root){
	if (!root) return 0
	const q = [[root, 1]]
	while(q.length) {
		const [n, level] = q.shift()
		console.log(n.val)
		if (!n.left && !n.right) {return level}
		if (n.left) q.push([n.left, level + 1])
		if (n.right) q.push([n.right, level + 1])
	}
}
leetcode102:二叉树的层序遍历

在这里插入图片描述

  • 解题思路
    • 层序遍历顺序就是广度优先遍历
    • 不过在遍历时候需要记录当前节点的层级,方便将其添加在所在数组中
  • 解题步骤
    • 广度优先遍历整棵树
    • 遍历时,记录每个节点的层级,并将其添加到不同的数组中
  • code
// 方法一
var levelOrder = function(root){
	if (!root) return []
	const q = [[root, 0]]
	const res = []
	while(q.length){
		const [n, level] = q.shift()
		if (!res[level]) {
			res.push([n.val])
		} else {
			res[level].push(n.val)
		}
		if (n.left) q.push([n.left, level + 1])
		if (n.right) q.push([n.right, level + 1])
	}
}
// 方法二
// 统一层级的老成员先处栈,新的同一层级成员入栈
var levelOrder = function(root){
	if (!root) return []
	const q = [root]
	const res = []
	while(q.length){
		let len = q.length
		res.push([])
		while(len--) {
			const n = q.shift()
			res[res.length - 1].push(n.val)
			if (n.left) q.push(n.left)
			if (n.right) q.push(n.right)
		}
	}
	return res
}

leetcode112:路径总和

在这里插入图片描述

  • 解题思路
    • 深度优先遍历的过程中,记录当前路径的节点值的和
    • 在叶子节点处,判断当前路径的节点和是否等于目标值
  • code
// 时间复杂度O(n) 空间复杂度O(n)
var hasPathSum = function(root, sum) {
	if (!root) return false
	let res = false
	const dfs = (n, s) => {
		if (!n.left && !n.right && s === sum) 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
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值