Morris遍历

195 阅读2分钟

复习Morris遍历

一般来讲,我们在进行树的遍历的时候
会进行递归
它的时间复杂度是O(N²),空间复杂度O(N)

那么有没有一套从天而降的掌法能优化它的复杂度呢?

它的名字叫 MorrisTraversal
它用几个变量就遍历完整棵树,使空间复杂度降为O(1)

Morris遍历图示:

morris-inorder-traversal.jpg

Morris遍历的次数:

树的递归遍历,每一个节点有三个操作时机,分别在:

//第一个时机
recursion(node.left)
//第二个时机
recursion(node.right)
//第三个时机

也就是会来到每个节点三次
而Morris遍历会来到有左树的节点两次,叶子节点一次

Morris遍历的步骤:

  1. 判断是否有左节点,没有cur右移
  2. 有左节点,取到左节点的最右节点
    判断最右节点的右指针指向:
    1. 若指向null,则让它指向cur,cur左移
    2. 若指向cur,则让它指向null,cur右移
  3. 当 cur 为 null 时,游戏结束(这个时候cur来到了最右位置,遍历完整棵树)

Morris遍历实现:

通用代码实现:

const morrisTraversal = (root) => {
  let cur = root
  let res = []
  while (cur) {
    let mostRight = cur.left
    if (mostRight) {
      while (mostRight.right !== null && mostRight.right !== cur) {
        mostRight = mostRight.right
      }
      if (mostRight.right === null) {
        //第一次访问有左树节点
        mostRight.right = cur
        cur = cur.left
        continue
      } else {
        //第二次访问有左树节点
        //第一次访问叶子节点
        mostRight.right = null
      }
    }
    //第二次访问有左树节点
    //第一次访问叶子节点
    //cur左为null
    cur = cur.right
  }
  return res
}

前序遍历:

//前序遍历
const morrisTraversal = (root) => {
  let cur = root
  let res = []
  while (cur) {
    let mostRight = cur.left
    if (mostRight) {
      while (mostRight.right !== null && mostRight.right !== cur) {
        mostRight = mostRight.right
      }
      if (mostRight.right === null) {
        res.push(cur.val)
        mostRight.right = cur
        cur = cur.left
        continue
      } else {
        mostRight.right = null
      }
    } else {
      res.push(cur.val)
    }
    cur = cur.right
  }
  return res
}

中序遍历

//中序遍历
const morrisTraversal = (root) => {
  let cur = root
  let res = []
  while (cur) {
    let mostRight = cur.left
    if (mostRight) {
      while (mostRight.right !== null && mostRight.right !== cur) {
        mostRight = mostRight.right
      }
      if (mostRight.right === null) {
        mostRight.right = cur
        cur = cur.left
        continue
      } else {
        mostRight.right = null
      }
    }
    res.push(cur.val)
    cur = cur.right
  }
  return res
}

后序遍历

这个比较有意思,比前面两个需要多加两个函数
它们的功能是:

printEdge:反向打印树的一条边,那么就需要 反转 打印 然后再反转回去
reverseEdge:实现边的反转

const printEdge = (node) => {
  let tail = reverseEdge(node)
  let res = []
  node = tail
  while (node) {
    res.push(node.val)
    node = node.right
  }
  reverseEdge(tail)
  return res
}
const reverseEdge = (node) => {
  let pre = null
  while (node) {
    let next = node.right
    node.right = pre
    pre = node
    node = next
  }
  return pre
}
const morrisTraversal = (root) => {
  let cur = root
  let res = []
  while (cur) {
    let mostRight = cur.left
    if (mostRight) {
      while (mostRight.right !== null && mostRight.right !== cur) {
        mostRight = mostRight.right
      }
      if (mostRight.right === null) {
        mostRight.right = cur
        cur = cur.left
        continue
      } else {
        mostRight.right = null
        //第二次来到有左树节点打印其左子节点及其右边
        res.push(...printEdge(cur.left))
      }
    }
    cur = cur.right
  }
  //最后还剩下根节点及其右边没打,给它打印了
  res.push(...printEdge(root))
  return res
}