什么是二叉搜索树?
-
二叉搜索树(BST,Binary Search Tree),也称二叉排序树或二叉查找树
-
二叉搜索树是一颗二叉树, 可以为空;如果不为空,满足以下性质:
- 非空左子树的所有键值小于其根结点的键值。
- 非空右子树的所有键值大于其根结点的键值。
- 左、右子树本身也都是二叉搜索树。
-
下面哪些是二叉搜索树, 哪些不是
-
-
二叉搜索树的特点:
- 二叉搜索树的特点就是相对较小的值总是保存在左结点上, 相对较大的值总是保存在右结点上.
- 那么利用这个特点, 我们可以做什么事情呢?
- 查找效率非常高, 这也是二叉搜索树中, 搜索的来源.
二叉搜索树的操作
- 二叉搜索树有哪些常见的操作呢?
insert(key)
:向树中插入一个新的键。search(key)
:在树中查找一个键,如果结点存在,则返回true
;如果不存在,则返回false
。inOrderTraverse
:通过中序遍历方式遍历所有结点。preOrderTraverse
:通过先序遍历方式遍历所有结点。postOrderTraverse
:通过后序遍历方式遍历所有结点。min
:返回树中最小的值/键。max
:返回树中最大的值/键。remove(key)
:从树中移除某个键。
二. 二叉搜索树的实现
现在, 我们通过代码来实现二叉搜索树.
创建二叉搜索树
-
我们像封装其他数据结构一样, 先来封装一个BinarySearchTree的类
// 创建BinarySearchTree function BinarySerachTree() { // 创建结点构造函数 function Node(key) { this.key = key this.left = null this.right = null } // 保存根的属性 this.root = null // 二叉搜索树相关的操作方法 }
-
代码解析:
- 封装BinarySearchTree的构造函数.
- 还需要封装一个用于保存每一个结点的类Node.
- 该类包含三个属性: 结点对应的key, 指向的左子树, 指向的右子树
- 对于BinarySearchTree来说, 只需要保存根结点即可, 因为其他结点都可以通过根结点找到.
向树中插入数据
-
我们两个部分来完成这个功能.
-
外界调用的insert方法
// 向树中插入数据 BinarySerachTree.prototype.insert = function (key) { // 1.根据key创建对应的node var newNode = new Node(key) // 2.判断根结点是否有值 if (this.root === null) { this.root = newNode } else { this.insertNode(this.root, newNode) } }
-
代码解析:
- 首先, 根据传入的key, 创建对应的Node.
- 其次, 向树中插入数据需要分成两种情况:
- 第一次插入, 直接修改根结点即可.
- 其他次插入, 需要进行相关的比较决定插入的位置.
- 在代码中的insertNode方法, 我们还没有实现, 也是我们接下来要完成的任务.
-
插入非根结点
BinarySerachTree.prototype.insertNode = function (node, newNode) { if (newNode.key < node.key) { // 1.准备向左子树插入数据 if (node.left === null) { // 1.1.node的左子树上没有内容 node.left = newNode } else { // 1.2.node的左子树上已经有了内容 this.insertNode(node.left, newNode) } } else { // 2.准备向右子树插入数据 if (node.right === null) { // 2.1.node的右子树上没有内容 node.right = newNode } else { // 2.2.node的右子树上有内容 this.insertNode(node.right, newNode) } } }
-
代码解析:
- 插入其他节点时, 我们需要判断该值到底是插入到左边还是插入到右边.
- 判断的依据来自于新节点的key和原来节点的key值的比较.
- 如果新节点的newKey小于原节点的oldKey, 那么就向左边插入.
- 如果新节点的newKey大于原节点的oldKey, 那么就向右边插入.
- 代码的1序号位置, 就是准备向左子树插入数据. 但是它本身又分成两种情况
- 情况一(代码1.1位置): 左子树上原来没有内容, 那么直接插入即可.
- 情况二(代码1.2位置): 左子树上已经有了内容, 那么就一次向下继续查找新的走向, 所以使用递归调用即可.
- 代码的2序号位置, 和1序号位置几乎逻辑是相同的, 只是是向右去查找.
- 情况一(代码2.1位置): 左右树上原来没有内容, 那么直接插入即可.
- 情况二(代码2.2位置): 右子树上已经有了内容, 那么就一次向下继续查找新的走向, 所以使用递归调用即可.
-
测试代码: 如果按照下面的代码插入, 最后形成什么样的树呢?
// 测试代码 var bst = new BinarySerachTree() // 插入数据 bst.insert(11) bst.insert(7) bst.insert(15) bst.insert(5) bst.insert(3) bst.insert(9) bst.insert(8) bst.insert(10) bst.insert(13) bst.insert(12) bst.insert(14) bst.insert(20) bst.insert(18) bst.insert(25)
-
形成的树:
-
-
如果这个时候, 我新插入一个数据6, 那么插入的位置和顺序应该怎样的呢?
bst.insert(6)
-
新的树: