知识点:
- 什么是二叉搜索树
- 实现
ps:写在前面,树的结构虽然比前面的复杂一点,但是呢只要会用递归其实还是很简单的。
理解递归也是蛮简单的,不信画个执行栈走走。这一节我本来打算是只把代码放上来的,因为除了一个删除比较复杂点别的就是跟着逻辑写,所以文字描述显得比较应付。看代码吧,代码思路写的还是没毛病的
1. 二叉搜索树
二叉树我们都很熟悉,二叉搜索树是在二叉树的基础上又做了一下规定。即只允许你在左侧节点存(比父节点)小的值,在右侧节点存储(比父节点)大的值。
创建二叉搜素树类:
class BinarySearchTree {
constructor() {
this.root = null;
}
}
二叉搜素树的节点要保存以下信息
- 自身key值
- 左孩子地址
- 右孩子地址
即:二叉搜索树节点结构
class TreeNode {
constructor(key) {
this.key = key;
this.right = null;
this.left = null;
}
}
我们接下来要实现二叉搜索树的以下方法:
- add(key)向树中添加节点
- 遍历(中序先序后序只是输出key的位置不同而已,以中序为例)
- min()取最小值
- max()取最大值
- search(key)判数组是否存在某值
- remove(key)删除树中某节点
2. 实现
树结构常见操作就是使用递归或者使用循环,这两种方法各有优缺点。递归比较好理解,循环方式效率高,看个人喜好吧。下面的栗子我两种方式都有用到,但是因为递归便于理解故大部分使用的是递归完成的。
add:它的实现比较简单,就是比较key。用于判断程序走向,再用递归向下一步步找下去。找到合适位置放入新节点。
add(key) {
let treeNode = new TreeNode(key);
if (this.root == null) {
this.root = treeNode;
} else {
this.addNode(this.root, treeNode);
}
}
addNode(node, newNode) {
// 右
if (newNode.key > node.key) {
if (node.right == null) {
node.right = newNode;
} else {
this.addNode(node.right, newNode);
}
} else {
if (node.left == null) {
node.left = newNode;
} else {
this.addNode(node.left, newNode);
}
}
}
中序遍历:见代码注释
ergodicityS() {
this.ergodicity(this.root);
}
// 中序遍历
ergodicity(node) {
// 基线条件即左到头或右到头,拿程序栈来理解此递归
if (node != null) {
// 去找左边
this.ergodicity(node.left);
// 打印
console.log(node.key);
// 去找右边
this.ergodicity(node.right);
}
}
min,max:这里我分别用了一下循环和递归方式。拿最小最大值很简单。因为在二叉搜索树中,最小值是最左边的叶子;最大值是最右边的叶子
min() {
return this.minNode(this.root).key;
}
minNode(node) {
// 最小值在树的最左边
while (node != null && node.left != null) {
node = node.left;
}
return node
}
max() {
return this.maxNode(this.root).key;
}
maxNode(node) {
if (node.right == null) {
return node;
} else {
return this.maxNode(node.right);
}
}
search:还是根据key判断向左还是向右找,递归进去
// 查询某个特值节点是否存在
search(key) {
return this.searchNode(this.root, key);
}
searchNode(node, key) {
if (node === null) {
return false;
}
if (node.key < key) { //向左找
return this.searchNode(node.right, key);
} else if (node.key > key) { //向右找
return this.searchNode(node.left, key);
} else if (node.key == key) {
return true;
}
}
remove:这里需要好好说下思路,删除有三种情况,1 叶子 2. 仅有一个孩子 3. 有两个孩子
删除叶子的操作很简单,只要把这个节点设置为null即可;仅有一个孩子也不难,即可以用这个节点的孩子代替要删除节点;有两个孩子的情况较为复杂一点吗,它的继承者是它右子树的左叶子。别的不变,左子树还是原来的左子树,右子树即是那个去掉了左叶子的右子树。
remove(key) {
return this.root = this.removeNode(this.root, key);
}
removeNode(node, key) {
// 三种情况需要考虑
//1 要删除的是叶子
//2 要删除的节点只有一个孩子
//3 要删除的节点有两个孩子
// 先定位到要做删除节点的位置
if (node.key > key) {
// 去左边找
node.left = removeNode(node.left, key);
return node;
} else if (node.key < key) {
// 去右边找
node.right = this.removeNode(node.right, key);
return node;
} else {
//进行删除操作
// 情况一:
if (node.left == null && node.right == null) {
node = null;
return node;
}
// 情况二
if (node.left == null) {
node = node.right;
return node;
} else if (node.right == null) {
node = node.left;
return node;
}
//情况三
// 它的继承者是此子树其右子树中最小值,左右子树保持不变(去掉右子树中min之后的)
let rightTree = node.right;
let currentKey = this.minNode(node.right);
node.key = currentKey;
// 此时还需要做最后一步,即去除其右子树最小值。即删一个叶子
node.right = this.removeNode(node.right, currentKey);
return node;
}
完整代码及部分测试
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
class TreeNode {
constructor(key) {
this.key = key;
this.right = null;
this.left = null;
}
}
class BinarySearchTree {
constructor() {
this.root = null;
}
addNode(node, newNode) {
// 右
if (newNode.key > node.key) {
if (node.right == null) {
node.right = newNode;
} else {
this.addNode(node.right, newNode);
}
} else {
if (node.left == null) {
node.left = newNode;
} else {
this.addNode(node.left, newNode);
}
}
}
add(key) {
let treeNode = new TreeNode(key);
if (this.root == null) {
this.root = treeNode;
} else {
this.addNode(this.root, treeNode);
}
}
// 中序遍历
ergodicity(node) {
// 基线条件即左到头或右到头,拿程序栈来理解此递归
if (node != null) {
// 去找左边
this.ergodicity(node.left);
// 打印
console.log(node.key);
// 去找右边
this.ergodicity(node.right);
}
}
// 中序遍历实调
ergodicityS() {
this.ergodicity(this.root);
}
min() {
return this.minNode(this.root).key;
}
minNode(node) {
// 最小值在树的最左边
while (node != null && node.left != null) {
node = node.left;
}
return node
}
max() {
return this.maxNode(this.root).key;
}
maxNode(node) {
if (node.right == null) {
return node;
} else {
return this.maxNode(node.right);
}
}
// 查询某个特值节点是否存在
search(key) {
return this.searchNode(this.root, key);
}
searchNode(node, key) {
if (node === null) {
return false;
}
if (node.key < key) { //向左找
return this.searchNode(node.right, key);
} else if (node.key > key) { //向右找
return this.searchNode(node.left, key);
} else if (node.key == key) {
return true;
}
}
remove(key) {
return this.root = this.removeNode(this.root, key);
}
removeNode(node, key) {
// 三种情况需要考虑
//1 要删除的是叶子
//2 要删除的节点只有一个孩子
//3 要删除的节点有两个孩子
// 先定位到要做删除节点的位置
if (node.key > key) {
// 去左边找
node.left = removeNode(node.left, key);
return node;
} else if (node.key < key) {
// 去右边找
node.right = this.removeNode(node.right, key);
return node;
} else {
//进行删除操作
// 情况一:
if (node.left == null && node.right == null) {
node = null;
return node;
}
// 情况二
if (node.left == null) {
node = node.right;
return node;
} else if (node.right == null) {
node = node.left;
return node;
}
//情况三
// 它的继承者是此子树其右子树中最小值,左右子树保持不变(去掉左子树中min之后的)
let rightTree = node.right;
let currentKey = this.minNode(node.right);
node.key = currentKey;
// 此时还需要做最后一步,即去除其右子树最小值。即删一个叶子
node.right = this.removeNode(node.right, currentKey);
return node;
}
}
}
let tree = new BinarySearchTree();
tree.add(6);
tree.add(7);
tree.add(4);
tree.add(2);
tree.add(10);
tree.ergodicityS();
console.log(tree);
console.log(tree.min());
console.log(tree.max());
tree.remove(7);
console.log(tree.search(11));
</script>
</body>
</html>