• 二叉树的深度优先遍历和广度优先遍历


    二叉树是一种很重要的数据结构,对于二叉树的遍历,有深度优先遍历和广度优先遍历,深度优先遍历又有先序、中序、后续遍历,广度优先遍历就是按层遍历。

    1. 深度优先遍历


    深度优先遍历,也就是先序、中序、后续遍历,我之前有一篇随笔已经说的很清楚了,在这里我只贴下代码就好了。

    传送门:详细教你实现BST(二叉排序树)

    在这里我依然用之前建立好的Node、Stack、BST结构来实现代码。

    class Node {
      constructor(data, leftNode, rightNode) {
        this.data = data
        this.leftNode = leftNode
        this.rightNode = rightNode
      }
      print () {
        return this.data
      }
    }
    
    class Stack {
      constructor() {
        this.arr = []
      }
      pop () {
        return this.arr.shift()
      }
      push (data) {
        this.arr.unshift(data)
      }
      isEmpty () {
        return this.arr.length == 0
      }
    }
    
    class BST {
      constructor() {
        this.root = null
      }
      insert (data) {...}
      preOrder () {...}
      inOrder () {...}
      postOrder () {...}
      ...
    }
    

    先是先序、中序、后序遍历的递归实现,很简单。

    // 递归先序
    function preOrderFn (node) {
      if (node) {
        console.log(node.print())
        preOrderFn(node.leftNode)
        preOrderFn(node.rightNode)
      }
    }
    
    // 递归中序
    function inOrderFn (node) {
      if (node) {
        inOrderFn(node.leftNode)
        console.log(node.print())
        inOrderFn(node.rightNode)
      }
    }
    
    // 递归后续
    function postOrderFn (node) {
      if (node) {
        postOrderFn (node.leftNode)
        postOrderFn (node.rightNode)
        console.log(node.print())
      }
    }
    

    然后就是先序、中序、后续遍历的非递归实现了。详细的解释和说明,点击上面的传送门就有了,这里不过多赘述。

    // 非递归先序
    function PreOrderWithoutRecursion (root) {
      if (!root)
        return
    
      var parentNode = root
      var stack = new Stack()
    
      while (parentNode || !stack.isEmpty()) {
    
        // 一直遍历到左子树的最下面,一边打印data,将一路遍历过的节点push进栈中
        if (parentNode) {
          console.log(parentNode.data)
          stack.push(parentNode)
          parentNode = parentNode.leftNode
        }
        // 当parentNode为空时,说明已经达到了左子树的最下面,可以出栈操作了
        else {
          parentNode = stack.pop()
          // 进入右子树,开始新一轮循环
          parentNode = parentNode.rightNode
        }
      }
    }
    
    // 非递归中序
    function inOrderWithoutRecursion (root) {
      if (!root)
        return
    
      var parentNode = root
      var stack = new Stack()
    
      while (parentNode || !stack.isEmpty()) {
    
        // 一直遍历到左子树的最下面,将一路遍历过的节点push进栈中
        if (parentNode) {
          stack.push(parentNode)
          parentNode = parentNode.leftNode
        }
        // 当parentNode为空时,说明已经达到了左子树的最下面,可以出栈操作了
        else {
          parentNode = stack.pop()
          console.log(parentNode.data)
          // 进入右子树,开始新一轮循环
          parentNode = parentNode.rightNode
        }
      }
    }
    
    // 非递归后续
    function PostOrderWithoutRecursion (root) {
      if (!root)
        return
    
      var parentNode = root
      var stack = new Stack()
      var lastVisitNode = null
    
      while (parentNode || !stack.isEmpty()) {
        if (parentNode) {
          stack.push(parentNode)
          parentNode = parentNode.leftNode
        }
        else {
          parentNode = stack.pop()
          // 如果当前节点没有右节点或者是右节点被访问过,则访问当前节点
          if (!parentNode.rightNode || parentNode.rightNode.data == lastVisitNode.data) {
            console.log(parentNode.data)
            lastVisitNode = parentNode
          }
          // 访问右节点
          else {
            stack.push(parentNode)
            parentNode = parentNode.rightNode
            while (parentNode) {
              parentNode = parentNode.leftNode
            }
          }
        }
      }
    }
    

    2. 广度优先遍历


    其实这片随笔有点打酱油了,只说了两个遍历,还有一个是在以前实现过的。

    广度优先遍历,顾名思义,就是横向先遍历,也就是按层次遍历,从根节点往下,对每一层依此访问,在每一层中从左到右(也可以从右到左)遍历,遍历完一层就进入下一层,直到没有节点。

    之前讲深度优先非递归遍历的时候,我们用到了一个栈的数据结构,到了广度优先遍历的时候,我们就要用到队列这个数据结构。

    为什么上一次用栈,这一次就要用到队列了呢?

    拿非递归中序遍历举例,我们每遍历到一个节点就要进行入栈操作,遍历完左节点之后,还需要找到根节点,再通过根节点找到右节点,所以我们需要最后遍历到的节点在这个数据结构的最顶端,这不就是栈吗?

    先把我们的队列的数据结构先建立起来再说。依然用数组模拟队列的操作。

    class Queue {
      constructor () {
        this.arr = []
      }
      enqueue (data) {
        return this.arr.push(data)
      }
      dequeue () {
        return this.arr.shift()
      }
      isEmpty () {
        return this.arr.length == 0
      }
    }
    

    为什么要用队列呢,我们按层次遍历,首先遍历根节点,然后左子树,右子树,然后左子树的左子树,左子树的右子树,右子树的左子树,依此类推。每遍历到一个节点,就将它存在一个数据结构里,先把它的前面的节点遍历完,才能遍历它,也就是一个先进先出(FIFO)的遍历方式,这不就是队列吗?

    说下思路:首先现将根节点做入队操作,队列里的节点表示我们要遍历的节点,所以队列为空的时候也就是没有节点可以遍历了,即队列不为空的时候循环遍历整个队列。首先我们取出队列的第一个节点,也就是对这个队列做出队操作,访问这个节点的值,如果这个节点存在左子树,那么将它的左子树放在队列的末尾,也就是对左子树做入队操作,右子树同理。

    思路很简单,实现起来不难:

    class BST {
      constructor() {
        this.root = null
      }
      // 广度优先遍历
      levelOrderTraversal () {
        levelOrderTraversalFn(this.root)
      }
      insert (data) {...}
      preOrder () {...}
      inOrder () {...}
      postOrder () {...}
      find (data) {..}
      getMax () {...}
      getMin () {...}
      deleteNode (data) {...}
      depth () {...}
      nodeCount () {...}
    }
    
    // 广度优先遍历
    function levelOrderTraversalFn (node) {
      if(!node) {
        return
      }
    
      var que = new Queue()
      que.enqueue(node)
      while(!que.isEmpty()) {
        node = que.dequeue()
        console.log(node.data)
        if(node.leftNode) que.enqueue(node.leftNode)
        if(node.rightNode) que.enqueue(node.rightNode)
      }
    }
    

    我们试一下:

    没错,那我们的广度优先遍历也就写完了。

  • 相关阅读:
    面向对象的-作用域
    什么时候会有作用域的形成
    面向对象-作用域
    1.3tableView向左滑动出现多个按钮操作
    tableView自带删除与添加操作
    使用偏好设置归档放到哪里
    使用RSA对数据进行加密
    12.22UIAlertController 使用
    在PCH中定制自己的LOG打印日志,分别在DEBUG 与 RELEASE的状态下处理,及如何把PCH引入到项目中
    在程序document文件夹里边创建新的文件夹及删除文件夹
  • 原文地址:https://www.cnblogs.com/isLiu/p/8328533.html
Copyright © 2020-2023  润新知