测试数据:
graph TD
1 --> 1-1
1 --> 1-2
1 --> 1-3
1-2 --> 2
2 --> 2-1
2 --> 2-2
const treeData = {
id: '1',
title: '节点1',
children: [
{
id: '1-1',
title: '节点1-1'
},
{
id: '1-2',
title: '节点1-2',
children: [
{
id: '2',
title: '节点2',
children: [
{
id: '2-1',
title: '节点2-1'
},
{
id: '2-2',
title: '节点2-2'
}
]
}
]
},
{
id: '1-3',
title: '节点1-3'
}
]
};
(一)深度优先遍历
深度优先遍历用的是栈(先进后出),栈对数组尾部进行操作
1. 递归实现
- 思路:
- 从图中一个未访问的顶点 V 开始,沿着一条路一直走到底
- 看该节点 是否还有除该子节点以外的节点,没有继续回退到父节点,有则遍历该子节点之外的其他节点
- 优缺点: 递归的表达性很好,也很容易理解,不过如果层级过深,很容易导致栈溢出。
const dfsWithRecursion = (node, nodeList) => {
if (node) {
nodeList.push(node.id); //存入当前节点的数据
const children = node.children;
if (children && children.length) {
for (let i = 0; i < children.length; i++) {
dfsWithRecursion(children[i], nodeList);//递归调用
}
}
}
console.log('【 nodes 】-68', nodeList);
// 结果:['1', '1-1', '1-2', '2', '2-1', '2-2', '1-3']
return nodeList;
};
2. 非递归实现
- 思路: 对二叉树来说,是先序遍历(先遍历当前节点 -> 再遍历左节点 -> 再遍历右节点)
- ① 对于每个节点来说,先遍历当前节点(将当前节点弹栈),然后把右节点压栈,再压左节点(这样弹栈的时候会先拿到左节点遍历,符合深度优先遍历要求)。
- ② 弹栈,拿到栈顶的节点,如果节点不为空,重复步骤 ①,如果为空,结束遍历。
- 优点: 不用担心递归那样层级过深导致的栈溢出问题
- 前置知识点:
| 方法名 | 作用 | | --- | --- | | push | 向数组的末尾添加一个或更多元素,并返回新的长度 | | pop | 把数组的最后一个元素从其中删除,并返回最后一个元素的值|
const dfsWithNoRecursion = node => {
const nodes = []; //保存遍历到的数据
const stack = []; //存储栈
if (node) {
stack.push(node); //将最顶部的节点入栈
// 栈不为空
while (stack.length) {
// 返回最后一个元素的值,即栈顶元素
let topItem = stack.pop(); //每次取出当前最顶部的节点(即最后入栈的节点)
nodes.push(topItem.id); //存入当前节点的数据
const children = topItem.children;
if (children && children.length) {
//(根据先进后出的顺序),
// 存的时候:同一层级从右到左依次入栈
// 取的时候:同一层级从左到右依次出栈
for (let i = children.length - 1; i >= 0; i--) {
stack.push(children[i]);
}
}
}
}
console.log('【 nodes 】-68', nodes);
// 结果:['1', '1-1', '1-2', '2', '2-1', '2-2', '1-3']
return nodes;
};
(二)广度优先遍历
广度优先遍历用的是用队列(先进先出) 来实现,队列对数组首部进行操作
非递归实现
- 思路: 指的是从图的一个未遍历的节点出发,先遍历这个节点的相邻节点,再依次遍历每个相邻节点的相邻节点。
- 优点: 不用担心递归那样层级过深导致的栈溢出问题
- 前置知识点:
| 方法名 | 作用 | | --- | --- | | unshift | 向数组的开头添加一个或更多元素,并返回新的长度 | | shift | 把数组的第一个元素从其中删除,并返回第一个元素的值。 |
const bfsWithNoRecursion = node => {
const nodeList = []; //保存遍历到的数据
if (node) {
const queue = []; //存储队列
queue.unshift(node); //进入队列
// 队列不为空
while (queue.length) {
let firstItem = queue.shift(); //离开队列
nodeList.push(firstItem.id); //存入当前节点的数据
const children = firstItem.children;
if (children && children.length) {
//(根据先进先出的顺序),
for (let i = 0; i < children.length; i++) {
queue.push(children[i]); // 依次将该层的子元素放到队列中
}
}
}
}
console.log('【 nodeList 】-68', nodeList);
// 结果:['1', '1-1', '1-2', '1-3', '2', '2-1', '2-2']
return nodeList;
};