JavaScript 二叉搜索树以及实现翻转二叉树

本文包括:二叉搜索树(建立、遍历、搜索、插入等)、JavaScript 实现翻转二叉树node

欢迎关注个人:我的博客Githubgit

什么是二叉树?

二叉树的定义:二叉树的每一个结点至多只有二棵子树(不存在度大于2的结点),二叉树的子树有左右之分,次序不能颠倒。github

二叉查找树(BST):又称为是二叉排序树(Binary Sort Tree)或二叉搜索树。二叉查找树是二叉树的一种,可是它只容许你在左侧节点存储(比父节点)小的值,在右侧节点存储(比父节点)大(或者等于)的值。算法

建立一个二叉查找树

首先建立一个 BinarySearchTree 类。bash

// 使用了 ES6 的 Class 语法
class BinarySearchTree {
  constructor() {
    this.root = null
  }

  Node(key) {
    let left = null
    let right = null
    return {
      key,
      left,
      right
    }
  }
}
复制代码

来看一下二叉查找树的数据结构组织方式(没有找到二叉搜索树的先用二叉树的代替一下):数据结构

二叉树是经过指针(指向下一个节点)来表示节点之间的关系的,因此须要在声明 Node 的时候,定义两个指针,一个指向左边,一个指向右边。 还须要声明一个 root 来保存树的根元素。函数

向树中插入一个键(节点)

class BinarySearchTree {
  // ...省略前面的
  
  insert (key) {
    let newNode = this.Node(key)
    if (this.root === null) {
      // 若是根节点为空,那么插入的节点就为根节点
      this.root = newNode
    } else {
      // 若是根节点不为空
      this.insertNode(this.root, newNode)
    }
  }

  insertNode (node, newNode) {
    // 当新节点比父节点小,插入左边
    if (newNode.key < node.key) {
      // 左边没有内容则插入
      if (node.left === null) {
        node.left = newNode
      } else {
        // 有内容就继续递归,直到没有内容而后能够插入
        this.insertNode(node.left, newNode)
      }
    } else {
      // 右边和左边相同,不重复说明
      if (node.right === null) {
        node.right = newNode
      } else {
        this.insertNode(node.right, newNode)
      }
    }
  }
}
复制代码

由于使用了 class 因此没有学过 class 的同窗能够先看一下 ES6 的 class,再来看文章。post

仔细分析上面的代码,多看几遍就能够了解其中的奥妙(也能够本身在游览器中运行一下,插入几个值试一下)。学习

运行一遍试一下:ui

let m = new BinarySearchTree()
m.insert(5)
m.insert(4)
m.insert(3)
m.insert(6)
m.insert(7)
复制代码

会获得这样的结构:

{
  key: 5,
  left: {
    key: 4,
    left: {
      key: 3,
      left: null,
      right: null
    },
    right: null
  },
  right: {
    key: 6,
    left: null,
    right: {
      key: 7,
      left: null,
      right: null
    }
  }
}
复制代码

emmm,真复杂(本身看的都头晕),仍是画个图吧。

会生成这样一个二叉查找树~,插入功能就算完成啦!

树的遍历

遍历一棵树是指访问树的每一个节点并对它们进行某种操做的过程。访问树会有三种方法:中序、先序、后续。下面分别讲解

中序遍历

中序遍历是一种以上行顺序访问 BST 全部节点的遍历方式,也就是从最小到最大的顺序进行访问全部节点。具体方法,看代码吧,配上图多看两遍代码就能明白了(我是这么认为的)。

class BinarySearchTree {
  // ...省略前面的
  
  inOrderTraverse (callback) {
    this.inOrderTraverseNode(this.root, callback)
  }
  
  inOrderTraverseNode (node, callback) {
    if (node !== null) {
      this.inOrderTraverseNode(node.left, callback)
      callback(node.key)
      this.inOrderTraverseNode(node.right, callback)
    }
  }
}
复制代码

一样,用图展现一下遍历的过程,具体过程看代码多思考一下。

先序遍历

先序遍历会先访问节点自己,而后再访问它的左侧子节点,最后再访问右侧的节点。

class BinarySearchTree {
  // ...省略前面的
  
  preOrderTraverse (callback) {
    this.preOrderTraverseNode(this.root, callback)
  }
  
  preOrderTraverseNode (node, callback) {
    if (node !== null) {
      callback(node.key)
      this.preOrderTraverseNode(node.left, callback)
      this.preOrderTraverseNode(node.right, callback)
    }
  }
}
复制代码

仔细看代码,发现和中序遍历的区别不过是先执行了 callback 而后再遍历左右。

后序遍历

后序遍历则是先访问节点的后代节点,而后再访问节点自己。实现:

class BinarySearchTree {
  // ...省略前面的
  
  postOrderTraverse (callback) {
    this.postOrderTraverseNode(this.root, callback)
  }
  
  postOrderTraverseNode (node, callback) {
    if (node !== null) {
      this.postOrderTraverseNode(node.left, callback)
      this.postOrderTraverseNode(node.right, callback)
      callback(node.key)
    }
  }
}
复制代码

再仔细看代码,发现和中序遍历的区别不过是先执行了遍历了左右,最后执行了 callback

惯例,画张图~

三种遍历方式讲完啦,不懂的能够多看几遍代码哦~

搜索二叉搜索树中的值

在树中,一般有三种常常使用的搜索类型:

  • 搜索最大值
  • 搜索最小值
  • 搜索特定值

下面一一列举

搜索最小和最大值

首先咱们知道二叉搜索树中的最小值在最左边,最大值在最右边。既然知道这个,那么实现搜索最大和最小就十分简单了。因此直接上代码:

class BinarySearchTree {
  // ...省略前面的
  
  // 搜索最小
  min () {
    return this.minNode(this.root)
  }
  
  minNode (node) {
    if (node) {
      // 若是节点存在,并且左边不为 null
      while (node && node.left !== null) {
        node = node.left
      }
      
      return node.key
    }
    
    // 若是树为空,则返回 null
    return null
  }
  
  // 搜索最大
  max () {
    return this.maxNode(this.root)
  }
  
  maxNode (node) {
    if (node) {
      while (node && node.right !== null) {
        node = node.right
      }
      return node.key
    }
    
    return null
  }
}
复制代码

搜索特定的值

基本上的思路和遍历节点差很少,具体看代码。

class BinarySearchTree {
  // ...省略前面的
  
  search (key) {
  	return this.searchNode(this.root, key)
  }
  
  searchNode (node, key) {
    if (node === null) {
      return false
    }
    
    // 若是 key 比节点的值小,那么搜索左边的子节点,下面的相反
    if (key < node.key) {
      return this.searchNode(node.left, key)
    } else if (key > node.key) {
      return this.searchNode(node.right, key)
    } else {
      return true
    }
  }
}
复制代码

翻转二叉树

翻转一个二叉树,直观上看,就是把二叉树的每一层左右顺序倒过来。

例如:

Input:

4
   /   \
  2     7
 / \   / \
1   3 6   9
复制代码

Output:

4
   /   \
  7     2
 / \   / \
9   6 3   1
复制代码

仔细看就是先把最底下的节点反转,而后上一个节点再翻转。例如:1 - 3 反转成 3 - 1,6 - 9 反转成 9 - 6, 而后再让 2 - 7 反转。固然反过来也同样,先反转 2 - 7 也是能够的。

因此具体的过程是:

  1. 翻转根节点的左子树(递归调用当前函数)
  2. 翻转根节点的右子树(递归调用当前函数)
  3. 交换根节点的左子节点与右子节点

最后看一下实现的代码:

class BinarySearchTree {
  // ...省略前面的
 
  invertTree (node = this.root) {
    if (node === null) {
      return
    }
    this.invertTree(node.left)
    this.invertTree(node.right)
    this.exchange(node)
  }
  
  exchange (node) {
    let temp = node.left
    node.left = node.right
    node.right = temp
  }
}
复制代码

这样就简单实现啦,舒服舒服~

代码

所有代码在这里~

class BinarySearchTree {
  constructor() {
    this.root = null
  }

  Node(key) {
    let left = null
    let right = null
    return {
      key,
      left,
      right
    }
  }

  insert(key) {
    let newNode = this.Node(key)
    if (this.root === null) {
      // 若是根节点为空,那么插入的节点就为根节点
      this.root = newNode
    } else {
      this.insertNode(this.root, newNode)
    }
  }

  insertNode(node, newNode) {
    console.log(node)
    if (newNode.key < node.key) {
      if (node.left === null) {
        node.left = newNode
      } else {
        this.insertNode(node.left, newNode)
      }
    } else {
      if (node.right === null) {
        node.right = newNode
      } else {
        this.insertNode(node.right, newNode)
      }
    }
  }

  inOrderTraverse(callback) {
    this.inOrderTraverseNode(this.root, callback)
  }

  inOrderTraverseNode(node, callback) {
    if (node !== null) {
      this.inOrderTraverseNode(node.left, callback)
      callback(node.key)
      this.inOrderTraverseNode(node.right, callback)
    }
  }

  preOrderTraverse(callback) {
    this.preOrderTraverseNode(this.root, callback)
  }

  preOrderTraverseNode(node, callback) {
    if (node !== null) {
      callback(node.key)
      this.preOrderTraverseNode(node.left, callback)
      this.preOrderTraverseNode(node.right, callback)
    }
  }
  postOrderTraverse(callback) {
    this.postOrderTraverseNode(this.root, callback)
  }

  postOrderTraverseNode(node, callback) {
    if (node !== null) {
      this.postOrderTraverseNode(node.left, callback)
      this.postOrderTraverseNode(node.right, callback)
      callback(node.key)
    }
  }

  // 搜索最小
  min() {
    return this.minNode(this.root)
  }

  minNode(node) {
    if (node) {
      // 若是节点存在,并且左边不为 null
      while (node && node.left !== null) {
        node = node.left
      }

      return node.key
    }

    // 若是树为空,则返回 null
    return null
  }

  // 搜索最大
  max() {
    return this.maxNode(this.root)
  }

  maxNode(node) {
    if (node) {
      while (node && node.right !== null) {
        node = node.right
      }

      return node.key
    }

    return null
  }
  
  search(key) {
    return this.searchNode(this.root, key)
  }

  searchNode(node, key) {
    console.log('node-', node, '---', node === null, '-key-', key)
    if (node === null) {
      return false
    }
    // 若是 key 比节点的值小,那么搜索左边的子节点,下面的相反
    if (key < node.key) {
      return this.searchNode(node.left, key)
    } else if (key > node.key) {
      return this.searchNode(node.right, key)
    } else {
      console.log('didi')
      return true
    }
  }

  invertTree (node = this.root) {
    if (node === null) {
      return
    }
    this.invertTree(node.left)
    this.invertTree(node.right)
    this.exchange(node)
  }
  
  exchange (node) {
    let temp = node.left
    node.left = node.right
    node.right = temp
  }
}
复制代码

最后

文章是本身的学习的一个记录,若是可以顺便帮助你们学习一下,那就再好不过了。

可是由于本人技术技术有限,因此文章不免会有疏漏,欢迎指出。

参考

相关文章
相关标签/搜索