深度优先遍历(DFS) 与广度优先遍历(BFS)是图论中两种非常重要的算法,生产上广泛用于拓扑排序,寻路(走迷宫),搜索引擎,爬虫等,深度优先就是自上而下的遍历搜索 广度优先则是逐层遍历
对于算法来说 无非就是时间换空间 空间换时间
- 深度优先不需要记住所有的节点, 所以占用空间小, 而广度优先需要先记录所有的节点占用空间大
- 深度优先有回溯的操作(没有路走了需要回头)所以相对而言时间会长一点
深度优先采用的是堆栈的形式, 即先进后出
广度优先则采用的是队列的形式, 即先进先出
深度优先遍历
主要思路是从图中一个未访问的顶点 V 开始,沿着一条路一直走到底,然后从这条路尽头的节点回退到上一个节点,再从另一条路开始走到底…,不断递归重复此过程,直到所有的顶点都遍历完成,它的特点是不撞南墙不回头,先走完一条路,再换一条路继续走。
树是图的一种特例(连通无环的图就是树),接下来我们来看看树用深度优先遍历该怎么遍历。
算法实现:
准备
假设页面上的dom结构如下:
<div id="root">
<ul>
<li>
<a href="">
<img src="" alt="">
</a>
</li>
<li>
<span></span>
</li>
<li>
</li>
</ul>
<p></p>
<button></button>
</div>
第一个参数是需要遍历的节点,第二个是节点所存储的数组,并且返回遍历完之后的数组,
该数组的元素顺序就是遍历顺序,调用方法:
let root = document.getElementById('root')
deepTraversal(root,nodeList=[])
js实现该算法代码(递归版本):
function deepFirstSearch(node,nodeList) {
if (node) {
nodeList.push(node);
var children = node.children;
for (var i = 0; i < children.length; i++)
//每次递归的时候将 需要遍历的节点 和 节点所存储的数组传下去
deepFirstSearch(children[i],nodeList);
}
return nodeList;
}
非递归版本:
function deepTraversal(node) {
var nodes = [];
if (node !=null){
var stack = [];
stack.push(node);
while (stack.length !== 0){
var item = stack.pop();
nodes.push(item);
var children = item.children;
for (var i=children.length-1;i>=0;i--){
stack.push(children[i])
}
}
}
return nodes;
}
广度优先遍历
广度优先遍历,指的是从图的一个未遍历的节点出发,先遍历这个节点的相邻节点,再依次遍历每个相邻节点的相邻节点。
递归版本的BFS由于层级太深,会导致堆栈溢出:Maximum call stack size exceeded,但遍历的顺序依旧没有问题,可以在遍历过程中进行操作,不返回遍历数组即可。
非递归版本:
function breadthFirstSearch(node) {
var nodes = [];
if (node != null) {
var queue = [];
queue.unshift(node);
while (queue.length != 0) {
var item = queue.shift();
nodes.push(item);
var children = item.children;
for (var i = 0; i < children.length; i++)
queue.push(children[i]);
}
}
return nodes;
}
102. 二叉树的层序遍历
代码如下:
BFS 遍历使用队列数据结构,BFS 的遍历结果是一个一维数组,无法区分每一层。层序遍历要求我们区分每一层,也就是返回一个二维数组。
var levelOrder = function(root) {
if(root == null) return [];
let queue = [];
queue.push(root);
while(queue.length !== 0){
let node = queue.shift();
if(node.left !== null){
queue.push(node.left)
}
if(node.right !== null){
queue.push(node.right)
}
}
};
- 我们需要稍微修改一下代码,在每一层遍历开始前,先记录队列中的结点数量 n(也就是这一层的结点数量),然后一口气处理完这一层的 n个结点。
- 可以看到,在 while 循环的每一轮中,都是将当前层的所有结点出队列,再将下一层的所有结点入队列,这样就实现了层序遍历。
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {TreeNode} root
* @return {number[][]}
*/
var levelOrder = function(root) {
if(root == null) return [];
let res = [];
let queue = [];
queue.push(root);
while(queue.length !== 0){
//在每一层遍历开始前,先记录队列中的结点数量 len(也就是这一层的结点数量),然后一口气处理完这一层的len个结点。
let len = queue.length;
let level = [];
for(let i =0;i<len;i++){ // 变量 i 无实际意义,只是为了循环 n 次
let node = queue.shift();
level.push(node.val);
if(node.left !== null){
queue.push(node.left)
}
if(node.right !== null){
queue.push(node.right)
}
}
res.push(level);
}
return res;
};
NC14 按之字形顺序打印二叉树
奇数层正序,偶数层反序
/* function TreeNode(x) {
this.val = x;
this.left = null;
this.right = null;
} */
function Print(pRoot)
{
// write code here
//边界条件
if(pRoot == null) return [];
let res = [];
let queue = [];
let floor = 1; //记录层数
queue.push(pRoot);
while(queue.length !== 0){
//在每一层遍历开始前,先记录队列中的结点数量 len(也就是这一层的结点数量),然后一口气处理完这一层的len个结点。
let len = queue.length;
let level = []; //放置每一层Nederland节点数
for(var i =0;i<len;i++){
let node = queue.shift();
level.push(node.val);
if(node.left !== null){
queue.push(node.left);
}
if(node.right !== null){
queue.push(node.right);
}
}
if(floor%2){
res.push(level);
}else{
res.push(level.reverse())
}
floor++;
}
return res;
}
module.exports = {
Print : Print
};