二叉树/BFS/DFS

103 阅读3分钟

每个节点最多两个孩子,即左右子树,子树也可以有孩子

二叉树类型定义

class TreeNode{
   value: number
   left?: TreeNode
   right?: TreeNode
   constructor(value: number, left?: TreeNode, right?: TreeNode){
      this.value = value;
      this.left = left;
      this.right = right
   }
}

d9f1e1ef0b2b136fe29f89f2c524922.jpg

const node4 = new TreeNode(4);
const node2 = new TreeNode(2, node4);

const node5 = new TreeNode(5);
const node6 = new TreeNode(6);
const node3 = new TreeNode(3, node5, node6);

const node1 = new TreeNode(1, node2, node3);

多叉树定义

class MultiTreeNode{
   value: number
   children?: Array<MultiTreeNode>
   constructor(value: number, children?: MultiTreeNode){
      this.value = value;
      this.children = children;
   }
}

广度优先遍历(层序遍历)

从二叉树的第一层开始逐层遍历,同一层中,从左至右遍历节点

02fdf87d5821fac4d41c24c47e128fd.jpg 借助队列实现

function bfs(root: TreeNode){
   const queue = new Array<TreeNode>();
   let level = 1;
   queue.push(root);
   while(queue.length){
      const size = queue.length; //当前层级的节点数;
      level ++;
      for(let i = 0; i < size; i++){
         const cur = queue.shift()!; // 非空断言,否则下面cur.value可能会认为undefined
         console.log(cur.value);
         // 加入下一层级的节点
         if(cur.left){
            queue.push(cur.left)
         }
         if(cur.right){
            queue.push(cur.right)
         }
      }
   }
}
  • 时间复杂度 O(n) -- 每个节点只访问一次
  • for循环可以去掉,输出结果不变,只不过不能确定哪几个节点属于一个层级

深度优先遍历(递归遍历)

D:访问根节点, L:遍历根节点左子树, R:遍历根节点右子树

1ed8c32a66ae99b460e688809cd05ec.jpg

  1. 先序遍历:DLR 1 2 3 4 5 6 7
function preOrder(root?: TreeNode){
   if(!root) return;
   console.log(root.value);
   preOrder(root.left);
   preOrder(root.right);
}
  1. 中序遍历:LDR 3 2 4 7 1 6 5
   preOrder(root.left);
   console.log(root.value);
   preOrder(root.right);
}
  1. 后序遍历:LRD 3 4 2 7 6 5 1
   preOrder(root.left);
   preOrder(root.right);
   console.log(root.value);
}

算法框架

  function traverse(root?: TreeNode){
    if(!root) return;
    // 前序位置
    traverse(root.left);
    // 中序位置
    
    traverse(root.right);
    // 后序位置
  }
  • 递归 - 定义函数,假设它实现某种功能

104. 二叉树最大深度

对比左子树 右子树的高度,取最大值+1

function maxDepth(root: TreeNode){
   if(!root) return 0;
   const lh = maxDepth(root.left);
   const rh = maxDepth(root.right);
   // 后序位置
   return Math.max(lh, rh) + 1;
   
}

144. 二叉树前序遍历

  let res = [];
  function help(root){
  if(!root) return;
    // 前序位置
    res.push(root.val);
    help(root.left);
    help(root.right);
  }
 
/**
 * @param {TreeNode} root
 * @return {number[]}
 */
var preorderTraversal = function(root) {
    res = [];
    help(root);
    return res;
};

543. 二叉树直径

任意两节点最长路径长度 - 左右子树之和最大值

let len = 0;
 function help(root){ //help函数 - 返回树的高度
     if(!root) return 0; //节点空,高度为0
     const lh = help(root.left);
     const rh = help(root.right);
     // 后序位置
     const h = Math.max(lh, rh) + 1;
     len = Math.max(len, lh+rh); //取左右子树和最高值
     return h;
 }
var diameterOfBinaryTree = function(root) {
    len = 0;
    help(root);
    return len;
};

help() 预先定义该函数的功能,就是返回节点的高度; 假设 help 成立

递归:先 假设,假设成立情况下,再推导

后序位置的代码,是自底向上的,可以获取左右子树的结果,可以获取更多信息

105. 根据前序和中序遍历,构造二叉树

前序确定根节点,中序确定左右子树

b55239b46bc0d6011a5254e9e4b1034.jpg 需要子数组标识, 前序遍历,中序遍历数组

function build(preOrder, preStart:开始下标, preEnd:结束下标,
  inOrder, inStart, inEnd){
  // 递归终止条件
  if(preStart > preEnd) return null
  
  // 前序 计算位置
  const rootVal = preOrder[preStart]; //根节点
  const index = inOrder.indexOf(rootVal); //根节点在中序遍历中的位置
  
  const leftSize = index - inStart; //左子树长度
  const leftNode = build(preOrder, preStart + 1, preStart + leftSize,
  inOrder, inStart, index - 1);
  const rightNode = build(preOrder, preStart + leftSize +1, preEnd,
  inOrder, index+1, inEnd);
  
  // 后序 生成树
  const root = new TreeNode(rootVal, leftNode, rightNode)
}
function buildTree(preOrder, inOrder){
   return build(preOrder,0,preOrder.length-1,
     inOrder,0,inOrder.length-1)
}