一. 二叉搜索树的概念
什么是二叉搜索树?
-
二叉搜索树(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
// 二叉搜索树相关的操作方法
}
向树中插入数据
-
我们两个部分来完成这个功能.
-
外界调用的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)
}
}
- 插入非根结点
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)
}
}
}
- 测试代码
// 测试代码
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)
- 形成的树:
- 新的树:
遍历二叉搜索树
- 前面, 我们向树中插入了很多的数据, 为了能很多的看到测试结果. 我们先来学习一下树的遍历.
- 注意: 这里我们学习的树的遍历, 针对所有的二叉树都是适用的, 不仅仅是二叉搜索树.
- 树的遍历:
- 遍历一棵树是指访问树的每个结点(也可以对每个结点进行某些操作, 我们这里就是简单的打印)
- 但是树和线性结构不太一样, 线性结构我们通常按照从前到后的顺序遍历, 但是树呢?
- 应该从树的顶端还是底端开始呢? 从左开始还是从右开始呢?
- 二叉树的遍历常见的有三种方式: 先序遍历/中序遍历/后续遍历. (还有程序遍历, 使用较少, 可以使用队列来完成)
先序遍历
-
遍历过程为:
- ①访问根结点;
- ②先序遍历其左子树;
- ③先序遍历其右子树。
-
遍历过程:
- 遍历的代码实现
BinarySerachTree.prototype.preOrderTraversal = function (handler) {
this.preOrderTranversalNode(this.root, handler)
}
BinarySerachTree.prototype.preOrderTranversalNode = function (node, handler) {
if (node !== null) {
// 1.打印当前经过的节点
handler(node.key)
// 2.遍历所有的左子树
this.preOrderTranversalNode(node.left, handler)
// 3.遍历所有的右子树
this.preOrderTranversalNode(node.right, handler)
}
}
- 测试代码:
// 测试前序遍历结果
var resultString = ""
bst.preOrderTraversal(function (key) {
resultString += key + " "
})
alert(resultString) // 11 7 5 3 6 9 8 10 15 13 12 14 20 18 25
- 代码先序遍历图解:
中序遍历
-
遍历过程为:
- ①中序遍历其左子树;
- ②访问根结点;
- ③中序遍历其右子树。
-
遍历过程:
- 遍历的代码实现:
// 中序遍历
BinarySerachTree.prototype.inOrderTraversal = funct