【LeetCode】二叉树的结构与遍历

1,789 阅读3分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

本文已参与 「掘力星计划」 ,赢取创作大礼包,挑战创作激励金。

今天来学习树的遍历,递归和非递归的方式

树的结构

function TreeNode(val, left, right) {
	this.val = (val===undefined ? 0 : val)
	this.left = (left===undefined ? null : left)
	this.right = (right===undefined ? null : right)
}

树的遍历

在这里插入图片描述

深度优先遍历DFS (递归)

function DFS(root) {
	if (root === null) return;
	DFS(root.left);
	DFS(root.right);
}

深度优先遍历DFS (栈)

其实可以不用递归,小伙伴们可以在纸上画一画,等我有时间了再做几个图吧

function DFS(root) {
  const stack = [];
  stack.push(root);
  
  while (stack.length > 0) {
    root = stack.pop();
    if (root.right) stack.push(root.right);
    if (root.left) stack.push(root.left);
  }
}

广度优先遍历BFS (队列)

function BFS(root){
	const queue = [];
	queue.unshift(root);
	
	while(queue.length > 0) {
		root = queue.pop();
		if(root.left) queue.unshift(root.left);
		if(root.right) queue.unshift(root.right);
	}
}

94. 二叉树的中序遍历

中序遍历是先遍历左子树,然后访问根节点,然后遍历右子树。

左-中-右

leetcode-cn.com/problems/bi…

144. 二叉树的前序遍历

中-左-右

leetcode-cn.com/problems/bi…

145. 二叉树的后序遍历

左-右-中 leetcode-cn.com/problems/bi…

前序:根左右;中序:左根右;后序:左右根; 中序常用来在二叉搜索数中得到递增的有序序列; 后序可用于数学中的后缀表示法,结合栈处理表达式,每遇到一个操作符,就可以从栈中弹出栈顶的两个元素,计算并将结果返回到栈中;

【解法一】递归DFS

使用递归,三种遍历方式的书写方式比较统一

/**
 * @param {TreeNode} root
 * @return {number[]}
 */
function inorderTraversal(root) {
  // 定义一个结果数组,用来保存遍历的节点的值
  const result = [];

  // 定义递归函数
  function inorder(root) {
    // 递归出口,直到节点为空,退出递归
    if (root === null) return;
    
	// 【三种遍历方式更换顺序即可】
    // 递归调用,传入根节点的左孩子
    inorder(root.left);
    // 【中序遍历:左 - 中 - 右】
    // 将根节点的值放入result数组中
    result.push(root.val);
    // 递归调用,传入根节点的右孩子
    inorder(root.right);
  }

  // 执行递归函数 表示当前遍历到root节点的答案
  inorder(root);

  return result;
}

在这里插入图片描述

【解法二】非递归 迭代法 - 栈

非递归,用一个栈

中序遍历

用一个栈和循环来模拟递归操作

遍历这颗树和栈,用while循环

function inorderTraversal(root) {
    const result = []
    const stack = []
    // 遍历树,结束终点:节点为空且栈为空
    while(root || stack.length > 0){
        // 遍历 root节点及其所有左孩子 入栈
        while(root){
            stack.push(root)
            root = root.left
        }
        // 左孩子遍历完入栈了,栈顶元素 出栈 【左-中】
        root = stack.pop()
        // 中序【左 - 中 - 右】 【左-中】
        result.push(root.val)
        // 指向右孩子,没有就是null,下次循环就会出栈一个元素
        root = root.right
    }

    return result
}

在这里插入图片描述

前序遍历

var preorderTraversal = function(root) {
    const result = []
    const stack = []
    while(root || stack.length > 0){
        while(root){
        	// 【前序:中 - 左 - 右】
            result.push(root.val)
            stack.push(root)
            root = root.left
        }
        root = stack.pop()
        root = root.right
    }
    return result
};

在这里插入图片描述

后序遍历(重难点)

var postorderTraversal = function(root) {
    const result = []
    const stack = []
    // 用来标记节点
    let prev = null
    while(root || stack.length > 0){
        while(root){
        	// 遍历节点左孩子到底【左】
            stack.push(root)
            root = root.left
        }
        // 栈顶出栈一个节点进行下面操作
        root = stack.pop()
        
        // 【后序:左 - 右 - 中】
        
        // 有右孩子,且右孩子没有被标记过,就将右孩子入栈,再while遍历右孩子
        if(root.right !== null && root.right !== prev){
			// 节点进栈,指针移向右孩子,再去循环 【右】
			stack.push(root)
			root = root.right
		}else {
			// 此时,没有右孩子【左-右-中】,或者有右孩子,但是被标记过了的【中】
			// 将节点的值存入结果数组
			result.push(root.val)
			// 存过的节点进行标记
			prev = root
			// 节点清空
			root = null
		}
    }
    return result
};

在这里插入图片描述

【解法三】Morris 中序遍历

将二叉树转化为链表,即每一个node都只可能有右孩子

function inorderTraversal(root) {
  const result = [];
  let predecessor = null;
  
  while (root !== null) {
  
    if (root.left) {
      // predecessor 节点就是当前 root 节点向左走一步,然后一直向右走至无法走为止
      predecessor = root.left;
      
      while (predecessor.right && predecessor.right !== root) {
        predecessor = predecessor.right;
      }
      
      // 让 predecessor 的右指针指向 root,继续遍历左子树
      if (!predecessor.right) {
        predecessor.right = root;
        root = root.left;
      } else {
        // 说明左子树已经访问完了,我们需要断开链接
        result.push(root.val);
        predecessor.right = null;
        root = root.right;
      }
    
    } else {
      // 如果没有左孩子,则直接访问右孩子
      result.push(root.val);
      root = root.right;
    }
  }
  
  return result;
}

在这里插入图片描述