JS数据结构(10)—— 二叉搜索树
1.二叉搜索树(BST,Binary Search Tree)是什么?
想要知道什么是二叉搜索树,先得了解什么是树结构,什么是二叉树,可以在我的另外一篇博客 树结构,二叉树 来了解。
二叉搜索树,也叫二叉查找树或二叉排序树。
二叉搜索树的特性:
- 非空左子树的所有键值小于其根节点的键值。
- 非空右子树的所有键值大于其根节点的键值。
- 左、右子树本身也是二叉搜索树。
二叉搜索树的特点就是相对较小的值总是保存在左节点上,相对较大的值总是保存在右节点上。
基于这个特点,使得二叉搜索树的查找效率非常高。
其实二叉搜索树的查找思想就是二分查找,查找所需要的最大次数是二叉搜索树的深度。
2.二叉搜索树的封装
2.1 代码解析
-
我们封装一个构造函数BinarySearchTree用来表示二叉搜索树
-
二叉搜索树只需要一个属性Root,因为其他节点都可以通过Root来找到
-
我们还需要封装一个Node类用于保存每个节点
-
Node类中包含三个属性,左子树,右子树和key
-
二叉搜索树的常见操作:
(1)insert(key):向树中插入一个新的key
(2)search(key):在树中查找一个key
(3)remove(key):从树中移除某个key
(4)preOrderTraverse:通过先序遍历遍历树
(5)inOrderTraverse:通过中序遍历遍历树
(6)postOrderTraverse:通过后序遍历遍历树
(7)mix:返回树中最小的key
(8)max:返回树中最大的key -
遍历:
树的遍历是指访问树的每个节点,但是树和线性结构不太一样,线性结构我们通常从前到后或者从后到前遍历,但是树不一样。
二叉树的遍历常见的有:- 先序遍历:先访问根节点,再先序遍历其左子树,最后先序遍历其右子树
- 中序遍历:先先序遍历根节点的左子树,再访问根节点,最后先序遍历根节点的右子树
- 后序遍历:先先序遍历根节点的左子树,再先序遍历根节点的右子树,最后访问根节点
测试:
按顺序插入:9 8 18 4 6 57 2 10
生成的树是:
手工计算:- 先序遍历 9 8 4 2 6 18 10 57
- 中序遍历 2 4 6 8 9 10 18 57
- 后序遍历 2 6 4 8 10 57 18 9
-
删除
二叉搜索树的删除是最复杂的一个操作,删除节点要先查找到该节点,找到之后还需要考虑三种情况:- 该节点没有叶节点
- 该节点有一个叶节点
- 该节点有两个叶节点
2.2 代码实现
function BinarySearchTree () {
// 属性
this.root = null;
// Node节点类
function Node(key) {
this.key = key;
this.left = null;
this.right = null;
}
// 操作
// insert(key):向树中插入一个新的key 给用户调用的方法
BinarySearchTree.prototype.insert = function(key) {
// 创建新节点
var newNode = new Node(key);
// 判断根节点是否为空
if(this.root == null)
this.root = newNode;
else {
this.insertNode(this.root, newNode);
}
}
// insertNode(node, newNode):插入的递归方法
BinarySearchTree.prototype.insertNode = function(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);
}
}
}
// search(key):在树中查找一个key
BinarySearchTree.prototype.search = function(key) {
var node = this.root;
while(node) {
if(node.key == key) {
return true;
} else if(node.key < key) {
node = node.right;
} else {
node = node.left;
}
}
return false;
}
// 遍历
// preOrderTraverse:通过先序遍历遍历树
BinarySearchTree.prototype.preOrderTraverse = function(callback) {
console.log('先序遍历:');
this.preOrderTraverseNode(this.root, callback);
}
// preOrderTraverseNode:先序遍历的递归函数
BinarySearchTree.prototype.preOrderTraverseNode = function(node, callback) {
if(node) {
callback(node.key);
this.preOrderTraverseNode(node.left, callback);
this.preOrderTraverseNode(node.right, callback);
}
}
// 中序遍历
// inOrderTraverse:通过中序遍历遍历树
BinarySearchTree.prototype.inOrderTraverse = function(callback) {
console.log('中序遍历:');
this.inOrderTraverseNode(this.root, callback);
}
// inOrderTraverseNode:中序遍历的递归函数
BinarySearchTree.prototype.inOrderTraverseNode = function(node, callback) {
if(node) {
this.inOrderTraverseNode(node.left, callback);
callback(node.key);
this.inOrderTraverseNode(node.right, callback);
}
}
// 后序遍历
// postOrderTraverse:通过后序遍历遍历树
BinarySearchTree.prototype.postOrderTraverse = function(callback) {
console.log('后序遍历:');
this.postOrderTraverseNode(this.root, callback);
}
// postOrderTraverseNode:后序遍历的递归函数
BinarySearchTree.prototype.postOrderTraverseNode = function(node, callback) {
if(node) {
this.postOrderTraverseNode(node.left, callback);
this.postOrderTraverseNode(node.right, callback);
callback(node.key);
}
}
// getRoot():返回树的根节点
BinarySearchTree.prototype.getRoot = function() {
return this.root;
}
// mix():返回树中最小的key
BinarySearchTree.prototype.mix = function() {
var node = this.root;
if(this.root == null) {
return null;
}
if(node) {
while(node && node.left) {
node = node.left;
}
}
return node.key;
}
// max:返回树中最大的key
BinarySearchTree.prototype.max = function() {
var node = this.root;
if(this.root == null) {
return null;
}
if(node) {
while(node && node.right) {
node = node.right;
}
}
return node.key;
}
// remove(key):从树中移除某个key
BinarySearchTree.prototype.remove = function(key) {
// 先判断是否存在key,如果不存在,则直接return 0
if(!this.search(key)) return false;
this.removeNode(this.root, key);
}
BinarySearchTree.prototype.removeNode = function(node, key) {
if(node == null)
return null;
if(node.key > key) {
node.left = this.removeNode(node.left, key);
} else if(node.key < key) {
node.right = this.removeNode(node.right, key);
} else { // 找到要删除的节点
// 要删除的节点是叶子节点,直接让该节点为null
if(node.left == null && node.right == null) {
node = null;
}
// 要删除的节点有一个子节点,则把要删除的节点替换为它的子节点
else if(node.left == null) {
node = node.right;
} else if(node.right == null) {
node = node.left;
}
// 要删除的节点有两个子节点,这时可以用左子树里的最大节点来替换要删除的节点,也可以用右子树里的最小节点来替换要删除的节点
// 这里我采用左子树的最大节点来替换
else {
// 找到要删除的节点的左子树里最大的值
var LeftMax = this.getLeftMax(node.left);
// 让要删除的节点的key等于刚才找到的左子树里最大值的key,保证了要删除的节点只改变了key,左右子树不变
node.key = LeftMax.key;
// 让要删除的节点的左子树里删除它的最大值
node.left = this.removeNode(node.left, LeftMax.key);
}
}
return node;
}
BinarySearchTree.prototype.getLeftMax = function(node) {
if(node) {
while(node && node.right) {
node = node.right;
}
return node;
}
return null;
}
}
// 测试:
var bst = new BinarySearchTree();
bst.insert(9);
bst.insert(8);
bst.insert(18);
bst.insert(4);
bst.insert(6);
bst.insert(57);
bst.insert(2);
bst.insert(10);
var result = "";
bst.preOrderTraverse(function(key) {
result += key + " ";
});
console.log(result);
// 先序遍历:
// 9 8 4 2 6 18 10 57
result = "";
bst.inOrderTraverse(function(key) {
result += key + " ";
});
console.log(result);
// 中序遍历:
// 2 4 6 8 9 10 18 57
result = "";
bst.postOrderTraverse(function(key) {
result += key + " ";
});
console.log(result);
// 后序遍历:
// 2 6 4 8 10 57 18 9
console.log('min:' + bst.mix(bst.getRoot()));
// min:2
console.log('max:' + bst.max(bst.getRoot()));
// max:57
console.log(bst.search(18)); // true
console.log(bst.search(99)); // false
bst.remove(4);
result = "";
bst.inOrderTraverse(function(key) {
result += key + " ";
});
console.log(result);
// 中序遍历:
// 2 6 8 9 10 18 57
(如果有什么错误,欢迎大佬在评论区指正,thank you~)