二叉搜索树深度优先与广度优先遍历实现

656 阅读5分钟

树数据结构

是一种分层数据的抽象模型。在现实中,家谱或者公司的组织结构与树的数据结构非常相似。

树的相关术语

树的介绍-2019-12-16.png

一个树结构包含一系列存在父子关系的节点。每个节点都有一个父节点(除了根节点)以及零个或多个节点。

位于树顶部的节点叫作根节点。它没有父节点。树中的每个元素都叫作节点,节点分为内部节点和外部节点。至少有一个子节点的节点被称为内部节点。没有子元素的节点称为外部节点或者叶子节点

子树由节点和它的后代构成。

节点的深度取决于它的祖先节点的数量。

二叉树

二叉树中的节点最多只能有两个子节点:一个是左侧子节点,另一个是右侧子节点。

二叉搜索树是二叉树的一种,但是它只允许在左侧节点存储(比父节点)小的值,在右侧节点存储(比父节点)大的值。

二叉树的遍历

二叉树遍历分为深度优先遍历和广度优先遍历。其中深度优先遍历又可以分为中序、先序、后序遍历。

深度优先遍历

中序遍历

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

中序遍历-2019-12-16.png

递归算法:

  1. 若二叉树为空,则算法结束;否则:
  2. 中序遍历根结点的左子树;
  3. 访问根结点;
  4. 中序遍历根结点的右子树;

实现:

  _inOrderTraverseNode(node, cb) {
    if (node !== null) {
      this._inOrderTraverseNode(node.left, cb)
      cb(node.key)
      this._inOrderTraverseNode(node.right, cb)
    }
  }

非递归算法:

  1. 初始化一个栈,将根节点压入栈中,并标记为当前节点(node);
  2. 当栈为非空时,执行步骤 3,否则执行结束;
  3. 如果当前节点(node)有左子树且没有被 touched,则执行步骤 4,否则执行不步骤 5;
  4. 对当前节点(node)标记 touched,将当前节点的左子树赋值给当前节点(node=node.left) 并将当前节点(node)压入栈中,回到步骤 3;
  5. 清理当前节点(node)的 touched 标记,取出栈中的一个节点标记为当前节点(node),并访问,若当前节点(node)的右子树为非空,则将该结点的右子树入栈,回到步骤 3;
  inOrderTraverseNoRecursion(cb, node) {
    node = node || this.root
    let stack = new Stack()
    stack.push(node)
    while (!stack.isEmpty()) {
      // 左节点先入栈
      if (node.left && !node.touched) {
        node.touched = true
        node = node.left
        stack.push(node)
        continue
      }
      node.touched && delete node.touched
      node = stack.pop()
      cb && cb(node.key)
      node.right && stack.push(node.right)
    }
  }

先序遍历

先序遍历-2019-12-16.png

递归算法:

  1. 若二叉树为空,则算法结束,否则:
  2. 访问根结点;
  3. 前序遍历根结点的左子树;
  4. 前序遍历根结点的右子树。
  _preOrderTraverseNode(node, cb) {
    if (node !== null) {
      cb(node.key)
      this._preOrderTraverseNode(node.left, cb)
      this._preOrderTraverseNode(node.right, cb)
    }
  }

非递归算法:

  1. 初始化一个栈,将根节点压入栈中;
  2. 当栈为非空时,循环执行步骤 3 到 4,否则执行结束;
  3. 出队列取得一个结点,访问该结点;
  4. 若该结点的右子树为非空,则将该结点的右子树入栈,若该结点的左子树为非空,则将该结点的左子树入栈;
  preOrderTraverseNoRecursion(cb, node) {
    node = node || this.root
    let stack = new Stack()
    stack.push(node)
    while (!stack.isEmpty()) {
      node = stack.pop()
      cb && cb(node.key)
      // 右节点先入栈
      node.right && stack.push(node.right)
      node.left && stack.push(node.left)
    }
  }

后序遍历

后序遍历-2019-12-16.png

递归算法:

  1. 若二叉树为空,则算法结束,否则:
  2. 后序遍历根结点的左子树;
  3. 后序遍历根结点的右子树;
  4. 访问根结点。
  _postOrderTraverseNode(node, cb) {
    if (node !== null) {
      this._postOrderTraverseNode(node.left, cb)
      this._postOrderTraverseNode(node.right, cb)
      cb(node.key)
    }
  }

非递归算法:

  1. 初始化一个栈,将根节点压入栈中,并标记为当前节点(item);
  2. 当栈为非空时,执行步骤 3,否则执行结束;
  3. 如果当前节点(item)有左子树且没有被 touched,则执行 4,如果被 touched left 但没有被 touched right 则执行 5 否则执行 6;
  4. 对当前节点(item)标记 touched left,将当前节点的左子树赋值给当前节点(item=item.left) 并将当前节点(item)压入栈中,回到 3;
  5. 对当前节点(item)标记 touched right,将当前节点的右子树赋值给当前节点(item=item.right) 并将当前节点(item)压入栈中,回到 3;
  6. 清理当前节点(item)的 touched 标记,弹出栈中的一个节点并访问,然后再将栈顶节点标记为当前节点(item),回到 3;
  postOrderTraverseNoRecursion(cb, node) {
    node = node || this.root
    let stack = new Stack()
    stack.push(node)

    while (!stack.isEmpty()) {
      // 左节点先入栈
      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
      }

      let item = stack.pop()

      item.touched && delete item.touched

      cb && cb(item.key)

      // 指向栈顶的节点
      node = stack.peek()
    }
  }

广度优先遍历

度优先遍历二叉树(层序遍历)是用队列来实现的,从二叉树的第一层(根结点)开始,自上至下逐层遍历;在同一层中,按照从左到右的顺序对结点逐一访问。

按照从根结点至叶结点、从左子树至右子树的次序访问二叉树的结点。步骤:

  1. 初始化一个队列,并把根结点入列队;
  2. 当队列为非空时,循环执行步骤 3 到 4,否则执行结束;
  3. 出队列取得一个结点,访问该结点;
  4. 若该结点的左子树为非空,则将该结点的左子树入队列,若该结点的右子树为非空,则将该结点的右子树入队列;

递归实现:

  // 广度优先遍历(递归)
  breadthFirstSearch(cb, node) {
    node = node || this.root
    let queue = new Queue()
    queue.enqueue(node)
    bfs(cb)
    function bfs(cb) {
      if (queue.isEmpty()) return
      let item = queue.dequeue()
      cb && cb(item.key)
      item.left && queue.enqueue(item.left)
      item.right && queue.enqueue(item.right)
      bfs(cb)
    }
  }

非递归实现:

  breadthFirstSearchNoRecursion(cb, node) {
    node = node || this.root
    let queue = new Queue()
    queue.enqueue(node)
    let pointer = 0
    while (pointer < queue.size()) {
      let item = queue.list[pointer++]
      cb && cb(item.key)
      item.left && queue.enqueue(item.left)
      item.right && queue.enqueue(item.right)
    }
  }

完整实现

完整代码可查看github