树
是一种经常用到的数据结构,用来模拟具有树状结构性质的数据集合。
树里的每一个节点有一个根植和一个包含所有子节点的列表。从图的观点来看,树也可视为一个拥有N 个节点
和N-1 条边
的一个有向无环图。
1.二叉树遍历
二叉树
是一种更为典型的树树状结构。如它名字所描述的那样,二叉树是每个节点最多有两个子树
的树结构,通常子树被称作“左子树”和“右子树”,👇是一种二叉树节点的定义。
function TreeNode(val) {
this.val = val;
this.left = this.right = null;
}
一棵二叉树可以按照前序
、中序
、后序
或者层序
来进行遍历。所谓前、中、后,不过是根的顺序,也可以称为先根遍历、中根遍历、后根遍历。
- 前序遍历 - 首先访问根节点,然后遍历左子树,最后遍历右子树:
- 中序遍历 - 首先遍历左子树,然后访问根节点,最后遍历右子树;
- 后序遍历 - 首先遍历左子树,然后遍历右子树,最后访问根节点;
- 层序遍历 - 按照从左到右的顺序,逐层遍历各个节点;
N叉树的中序遍历没有标准定义,中序遍历只有在二叉树中有明确的定义。
二叉树的遍历示例:
给定一个二叉树,返回它的前序
遍历。
示例:
输入:[1,null,2,3,5,7,9]
输出: [1,2,3,7,9,5],此时输入的数组被格式化成以下的二叉树结构:
const root = {
val: 1,
left: null,
right: {
val: 2,
left: {
val: 3,
left: {
val: 7
},
right: {
val: 9
}
},
right: {
val: 5
}
}
}
递归解法1,初始结题思路未想到创建闭包函数inner:
/**
* @param {TreeNode} root
* @return {number[]}
*/
function preorderTraversal(root) {
if (!root) {
return []
}
const res = []
const inner = (node) => {
if (!node) return
const {val, left, right} = node
res.push(val)
inner(left)
inner(right)
}
inner(root)
return res
};
递归解法2,用到了扩展操作符,更简洁:
/**
- @param {TreeNode} root
- @return {number[]}
*/
var preorderTraversal = function(root) {
if (root) {
return [root.val, ...preorderTraversal(root.left), ...preorderTraversal(root.right)]
} else {
return []
}
};
迭代解法1:
利用栈来记录遍历的过程,实际上,递归就使用了调用栈,所以这里我们可以使用栈来模拟递归的过程
- 首先根入栈
- 将根节点出栈,将根节点值放入结果数组中
- 然后遍历左子树、右子树,因为
栈是先入后出
,所以,我们先右子树入栈,然后左子树入栈 - 继续出栈(左子树被出栈)…….
依次循环出栈遍历入栈,直到栈为空,遍历完成。
Javascript中虽没有栈和队列的定义,但是push和pop组合使用可以模拟栈
,push和shift组合使用可以模拟队列
// 前序遍历
const preorderTraversal = (root) => {
const list = [];
const stack = [];
// 当根节点不为空的时候,将根节点入栈
if(root) stack.push(root)
while(stack.length > 0) {
// pop方法用于删除数组的最后一个元素,并返回该元素。注意,该方法会改变原数组。
const curNode = stack.pop()
// 第一步的时候,先访问的是根节点
// push方法用于在数组的末端添加一个或多个元素,并返回添加新元素后的数组长度。注意,该方法会改变原数组。
list.push(curNode.val)
// 我们先打印左子树,然后右子树
// 所以先加入栈的是右子树,然后左子树
if(curNode.right !== null) {
stack.push(curNode.right)
}
if(curNode.left !== null) {
stack.push(curNode.left)
}
}
return list
}
2.N叉树遍历
如果想把二叉树遍历转换为N叉树遍历,我们只需把如下表述:
遍历左子树… 遍历右子树…
转变为:
对于每个子节点: 通过递归调用遍历函数来遍历以该子节点为根的子树
N叉树遍历示例(以四叉树为例):
1.前序遍历
前序遍历首先访问根节点,然后逐个遍历以其子节点为根的子树。
结果: A->B->C->D->E->F->G->J->K->H.
2.后序遍历
后序遍历首先逐个遍历以根节点(A)的子节点为根的子树,最后访问根节点。
结果: C->D->B->E->J->K->G->H->F->A.
3.层序遍历
N叉树的层序遍历与二叉树的一致。从根节点开始,按照从左至右顺序逐层遍历。
结果: A->B->E->F->C->D->G->H->J->K.
N叉树遍历示例:
递归解法:
/**
* // Node定义
* function Node(val, children) {
* this.val = val;
* this.children = children;
* };
*/
/**
* @param {Node} root
* @return {number[]}
*/
var preorder = function(root) {
if (!root) {
return []
}
const res = []
const inner = (node) => {
if (!node) return
const {val, children} = node
res.push(val)
// forEach方法与map方法很相似,也是对数组的所有成员依次执行参数函数。但是,forEach方法不返回值,只用来操作数据。
// 这就是说,如果数组遍历的目的是为了得到返回值,那么使用map方法,否则使用forEach方法。
// forEach的用法与map方法一致,参数是一个函数,该函数同样接受三个参数:当前值、当前位置、整个数组。
children.forEach(child => {
inner(child)
})
}
inner(root)
return res
};