第8章:树

1.树是什么

  1. 一种分层数据的抽象模型
  2. 前端工作中常见的树包括:DOM树,级联选择(省市区),树形控件,…
  3. javascript中没有树,但是可以用Object和Array构建树
  4. 请添加图片描述
    4.树的常用操作:深度/广度优先遍历,先中后序遍历

深度 / 广度遍历

深度优先遍历:尽可能深的搜索树的分支。如下图深度访问顺序:

请添加图片描述

广度优先遍历:先访问离跟节点最近的节点。

标题,目录,深入看每个目录下的小节。
请添加图片描述

深度优先遍历算法口诀:(其实就是一个递归)

1.访问根节点。
2.对根节点的children挨个进行深度优先遍历。
请添加图片描述
只有2步,写代码也只有2行代码,但是这2行代码实现了深度优先递归,在遍历的过程中被反复调用很多次。

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 = function (tree) {
	console.log(tree.val)
	// root.children.forEach((child) => dfs(child))
	root.children.forEach(dfs)
}

广度优先遍历算法口诀(对列)

1.新建一个队列,把根节点入队
2.把队头出队并访问
3.把队头的children挨个入队
4.重复第2,3步,知道队列为空。
请添加图片描述

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

const bfs = function (root) {
	const q = [root]
	while(q.length > 0) {
		const n = q.shift()
		console.log(n.val)
		if (n.children) {
			n.children.forEach(child => {
				q.push(child)
			})
		}
	}
}

二叉树的先,中, 后序的三种遍历(递归)

1.二叉树:树中每个树的节点最多有2个节点
2.在js中通常用Object来模拟二叉树

请添加图片描述

先序遍历:(根,左,右)

1.访问根节点
2.对结节点的左子树进行先序遍历
3.对根节点的右子树进行先序遍历
请添加图片描述

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


const preorder = function (root) {
	
	// 保证程序的健壮性
	if (!root) return 
	// 访问根节点
	console.log(root.val)
	
	// 访问左子树
	preorder(root.left)
	
	// 访问右子树
	preorder(root.right)
}

// 输出结果:1,2, 4, 5,3,  6, 7

中序遍历(左根右)

  1. 对根节点的左子树进行遍历
  2. 访问根节点
  3. 对根节点的右子树进行遍历
const bt = {
      val: 1,
      left: {
        val: 2,
        left: {
          val: 4,
          left: {},
          right: {}
        },
        right: {
          val: 5,
          left: {},
          right: {}
        }
      },
      right: {
        val: 3,
        left: {
          val: 6,
          left: {},
          right: {}
        },
        right: {
          val: 7,
          left: {},
          right: {}
        }
      }
    }


const inorder = function (root) {
	if (!root) return 
	
	// 访问左子树
	preorder(root.left)
	
	// 访问根节点
	console.log(root.val)
	
	// 访问右子树
	preorder(root.right)
}

// 4 2 5 1 6 3 7 

后序遍历 (左右根)

  1. 对根节点的左子树进行遍历
  2. 对根节点的右子树进行遍历
  3. 访问根节点
const bt = {
  val: 1,
  left: {
    val: 2,
    left: {
      val: 4,
      left: {},
      right: {}
    },
    right: {
      val: 5,
      left: {},
      right: {}
    }
  },
  right: {
    val: 3,
    left: {
      val: 6,
      left: {},
      right: {}
    },
    right: {
      val: 7,
      left: {},
      right: {}
    }
  }
}


const postorder = function (root) {
	if (!root) return 
	
	// 访问左子树
	preorder(root.left)
	
	// 访问右子树
	preorder(root.right)
	
	// 访问根节点
	console.log(root.val)
}

// 4 5 2 6 7 3 1

二叉树的先,中, 后序的三种遍历(非递归)

面试时一般要求写非递归版本的先中后序遍历。

我们可以用栈模拟非递归的过程。

先序遍历

const preorder = function (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. 对于中序遍历第一步就是把所有的左子树丢到栈里面,那么这怎么做呢,我们需要用到一个指针§,刚开始指针的值是root,每遍历一个就把它放到栈里面,然后移动我们的指针,
  3. 需要把栈定的元素弹出,访问,访问完之后,我们要访问它的右节点,我们直接把指针指向右节点就行了,那么我们还要在做一次循环, while (stack.length || p) {},


const inorder = function (root) {
    if (!root) return
	
	 // 创建栈,用来模拟函数调用堆栈
     const stack = []
	
	// 指针(对于中序遍历来说,刚开始堆栈就堆了很多了,
	// 因为中序遍历刚开始就又调用了一个中序遍历,并把他的左子树传递进去了,如果还有左子树就不断的累加对栈)

     
     let p = root
     while (stack.length || p) {
		
	  // 1.对于中序遍历第一步就是把所有的左子树丢到栈里面,那么这怎么做呢,我们需要用到一个指针(p),刚开始指针的值是root,每遍历一个就把它放到栈里面,然后移动我们的指针,
       while (p) {
         stack.push(p)
         p = p.left
       }
		// 2.需要把栈定的元素弹出,访问,访问完之后,我们要访问它的右节点,我们直接把指针指向右节点就行了
       const n = stack.pop()
       console.log(n.val)
       p = n.right
     }
   }

后序遍历

1.把后序遍历的顺序给倒置一下,改为:根 右 左,利用先序遍历的算法逻辑逆序的访问。
2.然后利用栈的(先进后出)特性,把刚才先序遍历的顺序倒过来,重新访问一下就好了。

const postorder = (root) => {
    if (!root) {
       return;
     }
	
	 //  实现倒置的栈
     const outputStack = [];
	
	// 帮助实现先序遍历的栈
     const stack = [root];
     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);
     }
   };

leetCode 104 二叉树的最大深度

给一个二叉树,找出其最大的深度。
二叉树的深度:根节点到叶子结点的最长路径上的节点数。

求最大深度,考虑使用深度优先遍历。
请添加图片描述

请添加图片描述

 var maxDepth = function (root) {
     let res = 0

     // 深度优先编辑
     const dfs = (n, l) => {
       if (!n) return
       // 判断是否为叶子结点
       if (!n.left && !n.right) {
         res = Math.max(res, l)
       }
       console.log(n.val, l)
       dfs(n.left, l + 1)
       dfs(n.right, l + 1)
     }
     // l : 层级 ,初始化是1
     dfs(root, l = 1)
     return res
}

leetCode.111二叉树的最小深度

最小深度,广度优先遍历。
给定一个二叉树,找出最小深度。
最小深度:从根节点到最近叶子节点的最短路径上的节点数量。
叶子节点:没有子节点的节点。

请添加图片描述
请添加图片描述

const minDepth = (root) => {
	 if (!root) return
      const q = [[root, 1]]
      while (q.length) {
        const [n, l] = q.shift()
        // 输出当前值,和层级
        console.log(n.val, l)
        if (n.left) q.push([n.left,  l+ 1])
        if (n.right) q.push([n.right, l + 1])
      }
}


const minDepth = (root) => {
	 if (!root) return
      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])
      }
}

leetCode. 102.二叉树的层序遍历

给你一个二叉树,请你返回按【层序遍历】得到的的节点值,(即逐层的从左到右访问所有节点)。
广度优先遍历。

请添加图片描述

请添加图片描述


    var bfs = (root) => {

      if (!root) return

      const res = []

      const q = [[root, 0]]
      while (q.length) {
        const [n, level] = q.shift()
        // console.log([n.val, level])
        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])
      }

      return res
    }
    console.log(bfs(bt))

leetCode 94.二叉树的中序遍历

给定一个二叉树,返回中序遍历
请添加图片描述
递归算法很简单,你可以使用迭代完成么?

// 递归
const inorder = function (root) {
	const res = []
	const rec = (n) => {
		if (!n) return
		rec(n.left)
		res.push(n.val)
		rec(n.right)
	}
	
	rec(root)
	return res
}

迭代

const inorder = function (root) {
	const res = []
	const stack = []
	let p = root
	while(stack.length || p){
		while(p) {
			stack.push(p)
			p = p.left
		}
		const n = stack.pop()
		res.push(n.val)
		p = p.right
	}
}

leetCode 112.路径总和

给定一个二叉树和一个目标和,判断该树中是否存在根节点到叶子节点的路径,这条路径上所有节点值都相加等于目标和。

请添加图片描述

请添加图片描述

 var fasPathSum = function (root, num) {
     if (!root) return false
     let res = false
     const dfs = (n, s) => {
       if (!n.left && !n.right && s == num) {
         console.log('1', s, )
         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
   }
   console.log(fasPathSum(bt, 7))

遍历JSON的所有节点值

const json = {
    a: { b: { c: 1 } },
    d: [1, 2],
};
 
const dfs = (n, path) => {

	// 访问当前节点
    console.log(n, path);
	
	// Object.keys(n) 获取所有的key
    Object.keys(n).forEach(k => {
        dfs(n[k], path.concat(k));
    });
};
 
dfs(json, []);

渲染antd中的树组件 (深度优先遍历)

const json = [
	{ title: 'titl1', key: 'key1', children: [] },
	{ title: 'titl2', key: 'key2', children: [] },
]

class Demo extends. React.Component{
	dfs = (n) => {
		return <TreeNode title={n.title} key={n.key}>
			{n.children.map(this.dfs)}
		</TreeNode>
	}
	
	render() {
		return <Tree>{json.map(this.dfs)}</Tree>
	}
}




总结

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值