二叉搜索树
二叉搜索树(BST,Binary Search Tree),也称为二叉排序树和二叉查找树。
二叉搜索树是一棵二叉树,可以为空。
如果不为空,则满足以下性质:
- 条件 1:非空左子树的所有键值小于其根节点的键值。比如三中节点 6 的所有非空左子树的键值都小于 6;
- 条件 2:非空右子树的所有键值大于其根节点的键值;比如三中节点 6 的所有非空右子树的键值都大于 6;
- 条件 3:左、右子树本身也都是二叉搜索树; 总结:二叉搜索树的特点主要是较小的值总是保存在左节点上,相对较大的值总是保存在右节点上。这种特点使得二叉搜索树的查询效率非常高,这也就是二叉搜索树中“搜索”的来源。
二叉搜索树的常见操作:
insert(key)向树中插入一个新的键。search(key)在树中查找一个键,如果节点存在,则返回 true;如果不存在,则返回false。preOrderTraverse通过先序遍历方式遍历所有节点。inOrderTraverse通过中序遍历方式遍历所有节点。postOrderTraverse通过后序遍历方式遍历所有节点。min返回树中最小的值/键。max返回树中最大的值/键。remove(key)从树中移除某个键。
二叉搜索树的封装
实现了插入、查找、先序遍历、中序遍历、后序遍历、返回最值、删除节点等操作。
class Node {
constructor(key) {
this.key = key;
this.left = null;
this.right = null;
}
}
class BinarySearchTree {
constructor() {
this.root = null;
}
//插入数据
insert(key) {
//根据传进来的key创建节点
let newNode = new Node(key);
//判断根节点是否为空
if (this.root == null) {
this.root = newNode;
} else {
this.insertNode(this.root, newNode);
}
}
insertNode(node, newNode) {
//判断被比较的节点和新节点的key大小
if (newNode.key > node.key) {
if (node.right == null) {
node.right = newNode;
} else {
this.insertNode(node.right, newNode);
}
} else {
if (node.left == null) {
node.left = newNode;
} else {
this.insertNode(node.left, newNode);
}
}
}
//先序遍历
preOrderTraversal() {
this.preOrderTraversalNode(this.root);
}
preOrderTraversalNode(node) {
if (node != null) {
console.log(node.key);
this.preOrderTraversalNode(node.left);
this.preOrderTraversalNode(node.right);
}
}
//中序遍历
midOrderTraversal() {
this.midOrderTraversalNode(this.root);
}
midOrderTraversalNode(node) {
if (node != null) {
this.midOrderTraversalNode(node.left);
console.log(node.key);
this.midOrderTraversalNode(node.right);
}
}
//后序遍历
postOrderTraversal() {
this.postOrderTraversalNode(this.root);
}
postOrderTraversalNode(node) {
if (node != null) {
this.postOrderTraversalNode(node.left);
this.postOrderTraversalNode(node.right);
console.log(node.key);
}
}
//查找key对应的节点
search(key) {
let node = this.root;
while (node != null) {
if (key > node.key) {
node = node.right;
} else if (key < node.key) {
node = node.left;
} else {
return true;
}
}
return false;
}
//删除数据
remove(key) {
//1.1定义变量保存信息
let parent = null;
let current = this.root;
let isLeftChild = true;
//1.2查找要删除的节点
while (current.key != key) {
parent = current;
if (key > current.key) {
current = current.right;
isLeftChild = false;
} else {
current = current.left;
isLeftChild = true;
}
if (current == null) {
return false;
}
}
//2.1删除叶子节点
if (current.left == null && current.right == null) {
if (current == this.root) {
this.root = null;
} else if (isLeftChild) {
parent.left = null;
} else {
parent.right = null;
}
}
//2.2删除只有一个子节点的节点
else if (current.left == null) {
if (current == this.root) {
this.root = current.right;
} else if (isLeftChild) {
parent.left = current.right;
} else {
parent.right = current.right;
}
} else if ((current.right = null)) {
if (current == this.root) {
this.root = current.left;
} else if (isLeftChild) {
parent.left = current.left;
} else {
parent.right = current.left;
}
}
//2.3删除有两个子节点的节点
else {
let successor = this.getSuccessor(current);
if (current == root) {
this.root = successor;
} else if (isLeftChild) {
parent.left = successor;
} else {
parent.right = successor;
}
successor.left = current.left;
}
}
getSuccessor(delNode) {
//设置变量保存临时信息
let successor = delNode;
let successorParent = delNode;
let current = delNode.right;
//查找后继节点
while (current != null) {
successorParent = successor;
successor = current;
current = current.left;
}
//判断找到的后继节点是否为delNode的right子节点
if (successor != delNode.right) {
successorParent.left = successor.right;
successor.right = delNode.right;
}
return successor;
}
}
删除操作的思路
- 查找节点
-
查找需要删除的节点,没找到返回false
-
找到的话分为三种情况:
- 该节点是叶子结点
- 该节点有一个子节点
- 该节点有两个子节点
- 删除节点
-
删除节点是叶子节点和只有一个子节点的情况比较简单,这里只分析删除有两个子节点的节点。
-
如果我们要删除的节点有两个子节点,甚至子节点还有子节点,这种情况下我们需要从下面的子节点中找到一个节点,来替换当前的节点。
-
这个节点怎么找呢?
- 比current小一点点的节点,一定是current左子树的最大值。
- 比current大一点点的节点,一定是current右子树的最小值。
-
前驱&后继
在二叉搜索树中,这两个特别的节点,有两个特别的名字。
- 比current小一点点的节点,称为current节点的前驱。
- 比current大一点点的节点,称为current节点的后继。
也就是为了能够删除有两个子节点的current,要么找到它的前驱,要么找到它的后继。