关于二叉树哪些事

·  阅读 925

二叉树的遍历是面试经常会考察的知识点,二叉树的遍历包括深度遍历和广度遍历,而广度遍历又分前序遍历、中序遍历和后序遍历。广度的遍历包含递归的实现方式和非递归的方式,本文整理了这些知识点,希望对大家有帮助。

深度遍历

深度遍历就是根据二叉树的层数一层一层遍历,实现深度遍历的关键是利用队列,当队列当中有元素的时候,取出队列的头部元素,访问这个元素,然后把这个元素的左孩子和右孩子入队列,然后重复这个过程。代码如下:

function bfs(node) {
    var queue = []
    queue.push(node)
    // 队列一直有元素不断访问
    while(queue.length) {
        var item = queue.shift() // 取出队列的头部元素
        console.log(item.value)
        item.left && queue.push(item.left) // 入左孩子
        item.right && queue.push(item.right) // 入右孩子
    }
    return result
}

// 例子
var node = {value: 0, left: {value: 1, left: {value: 3}, right: {value: 4}}, right: {value: 2}}
console.log(bfs(node))

复制代码

求二叉树的右视图

广度遍历的另外一个考点,就是求二叉树的右视图或者左视图,整体的思路就是遍历每一层的时候,把每一层的最后一个元素取出来。而如何判断每一层的最后一个元素是个关键。做法就是在遍历的时候,取出队列中的所有元素,这些元素就是每一层的所有元素(假设有n个),然后循环这n个元素,判断下标是否为n-1,如果是,则是最后一个元素,同时每循环一次都需要取出左孩子和右孩子进队列,作为下一层遍历的元素。

function rightView(root) {
  var result = []
  var queue = []
  queue.push(root) 
  while(queue.length) {
    var len = queue.length
    // 用for循环每一层
    for (var i = 0; i < len; i++) {
      var node = queue.shift()
      // 判断是这一层的最后一个节点
      if (i === len - 1) {
        result.push(node.value)
      }
      // 将每层节点入队列
      if (node.left) {
        queue.push(node.left)
      }
      if (node.right) {
        queue.push(node.right)
      }
    }
  }
  return result
}

// 例子
var node = {value: 0, left: {value: 1, left: {value: 3}, right: {value: 4}}, right: {value: 2}}
console.log(rightView(node))
复制代码

广度遍历

广度遍历一般比较容易的实现方式是递归的方式,但是对于非递归的方式我们也需要理解下,面试官更喜欢问非递归的方式。非递归的实现的方式,就是利用栈。

前序遍历

前序遍历的递归方式,就是先访问节点,再递归左树,最后递归右树。

递归方式:

function dfs(node) {
    if (!node) return 
    console.log(node.value)
    dfs(node.left)
    dfs(node.right)
}

// 例子
var node = {value: 0, left: {value: 1, left: {value: 3}, right: {value: 4}}, right: {value: 2}}
console.log(dfs(node))
复制代码

非递归方式:
非递归实现前序遍历,主要是利用栈的先入后出特性,所以先入右树,再入左树。完整的过程描述如下:
1、先入根节点
2、当栈不为空,一直循环
3、取出栈顶元素,访问之
4、右孩子入栈
5、左孩子入栈
6、栈中有元素,一直循环2-5

function dfs(node) {
    var stack = []
    stack.push(node)
    while (stack.length) {
        var item = stack.pop()
        console.log(item.value)
        item.right && stack.push(item.right)
        item.left && stack.push(item.left)
    }
}
// 例子
var node = {value: 0, left: {value: 1, left: {value: 3}, right: {value: 4}}, right: {value: 2}}
console.log(dfs(node))
复制代码

中序遍历

递归方式
中序遍历的递归方式就是先递归左树,然后访问当前节点,再递归右树。

function dfs(node) {
    if (!node) return 
    dfs(node.left)
    console.log(node.value)
    dfs(node.right)
}

// 例子
var node = {value: 0, left: {value: 1, left: {value: 3}, right: {value: 4}}, right: {value: 2}}
console.log(dfs(node))
复制代码

非递归方式
非递归的实现方式也是利用栈,先把左树入栈,然后取出栈顶元素,再把右树入栈。完整的过程描述如下:
1、当队列不为空或者当前节点不为空,一直循环
2、如果当前节点不为空,入栈,当前节点赋值为其左孩子
3、如果当前节点为空,则取出栈顶元素,访问之,并把当前节点赋值为其右孩子
4、不断循环1-3直到栈为空且当前节点不存在

function dfs(node) {
    var stack = []
    while (stack.length || node) {
        if(node) {
            stack.push(node)
            node = node.left
        } else {
            node = stack.pop()
            console.log(node.value)
            node = node.right
        }
    }
}
// 例子
var node = {value: 0, left: {value: 1, left: {value: 3}, right: {value: 4}}, right: {value: 2}}
console.log(dfs(node))
复制代码

后序遍历

递归方式
后序遍历的递归方式就是先访问左树,再访问右树,最后访问当前节点。

function dfs(node) {
    if (!node) return 
    dfs(node.left)
    dfs(node.right)
    console.log(node.value)
}

// 例子
var node = {value: 0, left: {value: 1, left: {value: 3}, right: {value: 4}}, right: {value: 2}}
console.log(dfs(node))
复制代码

非递归方式
非递归的实现方式同样是利用栈,先把根结点和左树推入栈,然后取出左树,再推入右树,取出,最后取根结点。特殊的情况是,遍历的同时需要做个标记,防止重复入栈,完整的过程描述如下:
1、当队列不为空一直循环
2、如果当前节点有左孩子且左孩子未touch过,则其左孩子入栈,并标记当前节点的左孩子touch过,然后把当前节点指向其左孩子,一直循环到没有左孩子
3、如果当前节点有右孩子且右孩子未touch过,则其右孩子入栈,并标记当前节点的右孩子touch过,然后把当前节点指向其右孩子,一直循环到没有右孩子
4、取出栈顶元素,访问之,把当前节点指向栈中最后一个元素
5、循环2-5直到栈为空

function dfs(node) {
    var stack = []
    stack.push(node)
    while(stack.length) {
        // 这里不能用node.touched === 'left',因为这个时候被右树访问过会是right,如果用left会再执行,死循环
        if (node.left && !node.touched) {
            node.touched = 'left'
            node = node.left
            stack.push(node)
            continue
        } 
        if(node.right && node.touched !== 'right') {
            node.touched = 'right'
            node = node.right
            stack.push(node)
            continue
        }
        node = stack.pop() // 该节点无左右树时,取出访问
        console.log(node.value)
        // 这时当前结点不再从栈中取(弹出),而是不改变栈数据直接访问栈中最后一个结点,不然后序访问就没有了
        node = stack.length ? stack[stack.length - 1] : null;
    }
}
var node = {value: 0, left: {value: 1, left: {value: 3}, right: {value: 4}}, right: {value: 2}}
console.log(dfs(node))
复制代码
分类:
前端
标签: