算法基础04 二叉树遍历

深度优先遍历和广度优先遍历

二叉树的遍历可以使用深度优先遍历(DFS,Depth-First-Search)和广度优先遍历(BFS,Breadth-First-Search),可以使用递归算法和非递归算法实现。

递归算法性能并无优势,但是可读性好,非递归算法的性能更优。

深度优先遍历按照遍历的次序可以分为三种:

  1. 前序遍历(dlr):根-左-右
  2. 中序遍历(ldr):左-根-右
  3. 后序遍历(lrd):左-右-根

首先构造出一个二叉树:

const nodes = {
  node: 100,
  left: {
    node: 5,
    left: {
      node: 4,
      left: {
        node: 6,
      },
      right: {
        node: 7,
        left: {
          node: 200,
        },
        right: {
          node: 300
        }
      }
    },
    right: {
      node: 3,
      right: {
        node: 8
      }
    }
  },
  right: {
    node: 2,
    right: {
      node: 1
    }
  }
};

结果:

/*
*        100
*        / \
*       5   2
*      / \   \
*     4   3   1
*    / \   \
*   6  7    8
*     / \
*   200 300
*/

深度优先遍历 - 前序遍历

先访问根节点,然后访问左节点、右节点:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kyfJZbDV-1571304250489)(http://image.oldzhou.cn/18-12-3/43292755.jpg)]

(1)使用递归算法:

  1. 如果二叉树为空,则遍历结束,否则
  2. 访问根节点
  3. 前序遍历根节点的左子树
  4. 前序遍历根节点的右子树
/**
 * 深度优先搜索 - 前序遍历 - 递归算法
 */
function dfs_dlr_1(nodes, result = []) {
  if (nodes.node) {
    result.push(nodes.node);
    nodes.left && dfs_dlr_1(nodes.left, result);
    nodes.right && dfs_dlr_1(nodes.right, result);
  }
  return result
}

(2)使用非递归算法:

需要借助一个栈(后入先出),通过不断的压入和取出来实现,关键点是①使用栈而非队列②先push右子树再push左子树

  1. 初始化一个栈,将根节点压入栈中
  2. 档栈非空时,循环执行步骤3、4,否则结束
  3. 取出栈顶节点,节点值放入结果数组
  4. 如果该节点的右子树非空,则将右子树压入栈,如果该节点的左子树非空,则将左子树压入栈
/**
 * 深度优先搜索 - 前序遍历 - 非递归算法
 */
function dfs_dlr_2(nodes) {
  let result = [];
  let stack = [];
  stack.push(nodes);
  while (stack.length > 0) {
    const target = stack.pop();
    result.push(target.node);
    target.right && stack.push(target.right);
    target.left && stack.push(target.left);
  }
  return result;
}

深度优先遍历 - 中序遍历

先访问左子树,然后访问根节点,最后访问右子树

(1)使用递归算法:

  1. 如果二叉树为空,则遍历结束,否则
  2. 中序遍历访问根节点的左子树
  3. 访问根节点
  4. 中序遍历访问根节点的右子树
/**
 * 深度优先搜索 - 中序遍历 - 递归算法
 */
function dfs_ldr_1(nodes, result = []) {
  if (nodes.node) {
    nodes.left && dfs_ldr_1(nodes.left, result);
    result.push(nodes.node);
    nodes.right && dfs_ldr_1(nodes.right, result);
  }
  return result;
}

(2)使用非递归算法:

  1. 借助一个栈,当栈为空时结束遍历
  2. 进入遍历,判断当前节点的左子树是否存在,如果存在则将当前节点压入栈中,然后将当前节点替换为其左子树
  3. 当前节点不再存在左子树时,意味着找到了整棵树最左下角的节点,出栈一项,记录当前节点的值
  4. 将当前节点替换为出栈项的右子树,重复2
/**
 * 深度优先搜索 - 中序遍历 - 递归算法
 */
function dfs_ldr_2(nodes) {
  let result = [],
    stack = [],
    root = nodes;

  while (true) {
    while (root) {
      stack.push(root);
      root = root.left;
    }
    if (stack.length === 0) {
      break;
    }
    const current = stack.pop();
    result.push(current.node);
    root = current.right;
  }
  return result;
}

深度优先遍历 - 后序遍历

先访问子树,然后访问根节点。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1jnVcUCh-1571304250491)(http://image.oldzhou.cn/18-12-3/91140976.jpg)]

(1)递归算法:

  1. 如果二叉树为空,则遍历结束,否则
  2. 访问根节点
  3. 前序遍历根节点的左子树
  4. 前序遍历根节点的右子树
/**
 * 深度优先搜索 - 后序遍历 - 递归算法
 */
function dfs_lrd_1(nodes, result = []) {
  if (nodes.node) {
    nodes.left && dfs_lrd_1(nodes.left, result);
    nodes.right && dfs_lrd_1(nodes.right, result);
    result.push(nodes.node);
  }
  return result
}

(2)使用非递归算法:

  1. 可以调整前序遍历的算法,前序遍历计算的是『根-左-右』
  2. 可以调整压入栈的顺序,将前序遍历中的先压入右子树调整为限压入左子树,调整后的顺序是『根-右-左』
  3. 后续遍历的顺序是『左-右-根』 = 『根-右-左』.reverse();
/**
 * 深度优先搜索 - 后序遍历 - 非递归算法
 */
function dfs_lrd_2(nodes) {
  let result = [],
    stack = [nodes];

  while (stack.length > 0) {
    const current = stack.pop();
    result.push(current.node);
    current.left && stack.push(current.left);
    current.right && stack.push(current.right);
  }
  return result.reverse();
}

广度优先搜索

一层一层从左至右向下遍历

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-40m0Y2TL-1571304250492)(http://image.oldzhou.cn/Ftk32bGk4Ousx4mELACZ_U_73Bkp)]

(没有用递归实现)

  1. 通过一个队列实现(先入先出),将后续的左节点、右节点分别加入队列
  2. 一层一层向下进行
/**
 * 广度优先搜索,没有尝试递归算法
 */
function bfs_1(nodes) {
  let result = [];
  let queue = [nodes];
  while (queue.length > 0) {
    const current = queue.shift();
    result.push(current.node);
    current.left && queue.push(current.left);
    current.right && queue.push(current.right);
  }
  return result;
}

二叉树最大深度

思路:使用使用广度优先搜索实现,队列中构造了一个标志level,用来标识当前深度(应该有更好的方式)

/**
 * 找到二叉树的最大深度
 */
function getDepth(nodes) {
  let currentDepth = 0;
  let queue = [{value: nodes, depth: 1}];
  while (queue.length > 0) {
    const current = queue.shift();
    const currentNode = current.value;

    currentDepth = Math.max(currentDepth, current.depth);

    currentNode.left && queue.push({value: currentNode.left, depth: currentDepth + 1});
    currentNode.right && queue.push({value: currentNode.right, depth: currentDepth + 1});
  }
  return currentDepth;
}

二叉树翻转

思路:如果根节点不为空,则将左右子节点交换,然后将左右子节点进行递归调用

const reverseTree = tree => {
  if (tree) {
    [tree.left, tree.right] = [tree.right, tree.left];
    reverseTree(tree.left);
    reverseTree(tree.right)
  }
  return tree
}

参考

发布了382 篇原创文章 · 获赞 91 · 访问量 33万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 编程工作室 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览