树型数据结构在前端中很常见,比如(虚拟)DOM 树、抽象语法分析树。
总结起来,树就是有限节点组成一个具有层次关系的集合,因为它看起来非常像一棵倒着生长的树,根朝上叶朝下,所以命名为“树”。
树根据结构不同,可以分为很多类,例如:
- 有序树(树中任意节点,比如,点的子节点之间有顺序关系)
- 二叉树(每个节点最多有 2 个子树)
- 满二叉树(除最后一层所有节点都有两个子节点)等
其中,二叉树是最简单且最基础的树。说它简单,是因为每个节点至多包含两个子节点;说它基础,是因为二叉树可以延伸出一些子类,例如:
- 二叉搜索树(BST)
- 平衡二叉搜索树(AVL)
- 红黑树(R/B Tree)等
所以我们重点分析二叉树的查询操作——遍历。
树的遍历操作分为两类:深度遍历和广度遍历,其中深度遍历按照遍历根节点的顺序不同又可以分为 3 类:先序遍历、中序遍历和后序遍历。它们的遍历顺序如下:
- 先序遍历,根节点→左子树→右子树
- 中序遍历,左子树→根节点→右子树
- 后序遍历,左子树→右子树→根节点
- 广度遍历,逐层从左至右访问
实现深度遍历最简单的方式就是通过递归,下面是具体代码:
// 先序遍历,根->左->右
function preOrder(node, result=[]) {
if (!node) return
result.push(node.value);
preOrder(node.left, result);
preOrder(node.right, result);
return result;
}
// 中序遍历,左->根->右
function inOrder(node, result=[]) {
if (!node) return
inOrder(node.left, result);
result.push(node.value);
inOrder(node.right, result);
return result;
}
// 后序遍历,左->右->根
function postOrder(node, result=[]) {
if (!node) return
postOrder(node.left, result);
postOrder(node.right, result);
result.push(node.value);
return result;
}
广度优先遍历的实现会稍稍复杂一些,因为每次访问节点时都要回溯到上一层的父节点,通过其指针进行访问。
但每一层都是从左至右的遍历顺序,这种操作方式很符合队列的先进先出原则,所以可以通过队列来缓存遍历的节点,具体代码如下所示:
function breadthOrder(node) {
if(!node) return
var result = []
var queue = []
queue.push(node)
while(queue.length !== 0) {
node = queue.shift()
result.push(node.value)
if(node.left) queue.push(node.left)
if (node.right) queue.push(node.right)
}
return result
}
参考:《前端高手进阶》