JavaScript 二叉树的前中后序遍历

1,232 阅读3分钟

树结构及测试用例

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

// preorder: [1, 2, 4, 5, 7, 3, 6, 8, 9]
// inorder: [4, 2, 7, 5, 1, 3, 8, 6, 9]
// postorder: [4, 7, 5, 2, 8, 9, 6, 3, 1]
const testCase = new TreeNode(
  1,
  new TreeNode(2, new TreeNode(4), new TreeNode(5, new TreeNode(7))),
  new TreeNode(3, undefined, new TreeNode(6, new TreeNode(8), new TreeNode(9)))
) // or [1, 2, 3, 4, 5, null, 6, 7, null, 8, 9]
graph TB

1---2
1---3
2---4
2---5
5--left---7
3--right---6
6---8
6---9

递归

// 1. 数组解构
function traversal1(root) {
  return root ? (
    // preorder
    [root.val, ...traversal(root.left), ...traversal(root.right)]
    // inorder
    // [...traversal(root.left), root.val, ...traversal(root.right)]
    // postorder
    // [...traversal(root.left), ...traversal(root.right), root.val]
  ) : [];
}

// 2.
function traversal2(root) {
  const result = [];
  
  function findNode(node) {
    if (!node) return;
  
    // preorder
    result.push(node.val);
    findNode(node.left);
    // inorder
    // result.push(node.val);
    findNode(node.right);
    // postorder
    // result.push(node.val);
  }
  
  findNode(root);
  return result;
}

迭代

前序/中序

// preorder and inorder
function traversal(root) {
  const result = [];
  const stack = [];
  
  while (stack.length || root) {
    while (root) {
      // preorder
      result.push(root.val);
      stack.push(root);
      root = root.left;
    }
    
    root = stack.pop();
    // inorder
    // result.push(root.val);
    root = root.right;
  }
  
  return result;
}

后序

// 1. 从右向左逆向遍历,反向前序遍历,因此同样可用于中序遍历
function postorderTraversal1(root) {
  const result = [];
  const stack = [];
  
  while (stack.length || root) {
    while (root) {
      // postorder
      result.unshift(root.val);
      stack.push(root);
      root = root.right;
    }
    
    root = stack.pop();
    // inorder
    // result.unshift(root.val);
    root = root.left;
  }
  
  return result;
}

// 2. 已访问右孩子节点标记法(颜色标记法)
// a. 左孩子全部入栈
// b. 栈顶节点无右孩子或其右孩子被标记过,存入结果数组,压出该节点并标记其右孩子
// c. 栈顶节点有右孩子且右孩子未标记过,重复 abc 流程
function postorderTraversal2(root) {
  const result = [];
  const stack = [];
  let visited;
  
  while (stack.length || root) {
    while (root) {
      stack.push(root);
      root = root.left;
    }
    
    root = stack[stack.length - 1];
    if (!root.right || (root.right === visited)) {
      root = stack.pop();
      result.push(root.val);
      visited = root;
      root = null;
    } else {
      root = root.right;
    }
  }
  
  return result;
}

// 3. 空值标记法(特殊颜色标记)
// 按照根节点,右节点(如果有),左节点(如果有)的顺序入栈,在根节点入栈时,紧跟着入栈 null。最终栈中存入节点的顺序为[根,null, 右,左(孩子根),null, ...],即左孩子根节点全部 追加了 null 在其后面,因此当栈顶左孩子压出后,将会对其兄弟的右孩子做相同循环操作,从而得到答案数组
function postorderTraversal3(root) {
  const result = [];
  const stack = [root];
  
  while (stack.length) {
    root = stack.pop();
    
    if (root) {
      stack.push(root, null);
      if (root.right) stack.push(root.right);
      if (root.left) stack.push(root.left);
    } else {
      root = stack.pop();
      result.push(root.val);
    }
  }
  
  return result;
}

Morris

前序/中序

// preorder/inorder
function traversal(root) {
  const result = [];
  let predecessor;
  
  while (root) {
    if (root.left) {
    // 根节点左孩子
      predecessor = root.left;
            
      // 根节点左孩子一直找到最右孩子
      while (predecessor.right && (predecessor.right !== root)) {
        predecessor = predecessor.right;
      }

      if (!predecessor.right) {
        // 最右孩子添加右节点等于根节点,目的是为了在之后循环回到这个节点操作完后,能够让 root 指针正确回到下一个节点
        predecessor.right = root;
        // preorder,移动指针之前把根节点 push 进结果数组
        result.push(root.val);
        // 根节点指针指向其左孩子,进入循环
        root = root.left;
      } else {
        // 即 predecessor.right === root 的情况,此时说明当前根节点的所有左孩子已全部遍历完成
        // inorder, 左节点及根节点全部遍历完成后,把右节点(即下一循环的根节点)加入结果数组
        // result.push(root.val);
        // 清空对原始结构的更改
        predecessor.right = null;
        // 开始遍历右孩子,进入循环
        root = root.right;
      }
    } else {
      // 根节点无左孩子,说明在最左位置
      // preorder: 左孩子进入结果数组
      // inorder: 左孩子优先进入结果数组,回去的循环时为根节点进入结果数组
      result.push(root.val);
      // 开始右孩子,进入循环
      root = root.right;
    }
  }

  return result;
};

后序

function traversel(root) {
  const result = [];
  let predecessor;
  
  while (root) {
    if (root.right) {
      predecessor = root.right;
      
      while (predecessor.left && (predecessor.left !== root)) {
        predecessor = predecessor.left;
      }
      
      if (!predecessor.left) {
        predecessor.left = root;
        // postorder,根节点优先进入结果数组,进入结果数组应该保持根右左顺序,从而逆序数组的时候可以保证左右根的后序排列
        result.unshift(root.val);
        root = root.right;
      } else {
        // inorder,此时右孩子全部遍历完成,插入左孩子(即下一循环的根节点)
        // result.unshift(root.val);
        predecessor.left = null;
        root = root.left;
      }
    } else {
      // postorder,右孩子或者为没有右孩子的根节点进入结果数组
      // inorder,右孩子或没有右孩子的根节点优先进入结果数组
      result.unshift(root.val);
      root = root.left;
    }
  }
  
  return result;
}