昨天讲了二叉搜索树
的概念,现在我们来实现一个自己的二叉树:
首先应该定义一个根属性root
用来表示二叉搜索树的根
constructor(){
root = null
}
然后就是开始插入节点,这里要判断插入节点时根属性是否为空,如果为空那么就将这个节点作为根节点,如果不为空就执行插入非空二叉树的操作(insertNode)。
// 插入节点
insert(key) {
//创建一个新的节点对象
const newNode = {
key,
left: null
right: null
}
// 判断根节点是否为空
if(root === null) {
root = newNode
} else {
insertNode(root, newNode)
}
}
插入非空二叉树操作
// 将新节点插入到非空二叉搜索树中
insertNode(node, newNode) {
// 如果新节点的键值小于当前节点的键值,则向当前节点的左子树插入新节点
if (newNode.key < node.key) {
if (node.left === null) {
node.left = newNode
} else {
insertNode(node.left, newNode)
}
} else {
// 如果新节点的键值大于当前节点的键值,则向当前节点的右子树插入新节点
if (node.right === null) {
node.right = newNode
} else {
insertNode(node.right, newNode)
}
}
}
接下来现实遍历操作,一般常见的遍历搜索二叉树的方式有三种:
先序遍历
后序遍历
中序遍历
先序遍历
先序遍历
指的是先遍历根节点,然后遍历左子树,再遍历右子树,如下图:
遍历顺序为:11->7->5->3->6->9->8->10-15->13->12->14->20->18->25,具体代码实现:
// 先序遍历
preOrder(node = this.root) {
if (node !== null) {
console.log(node.key)
this.preOrder(node.left)
this.preOrder(node.right)
}
}
这里理解起来可能会有一点困难,就是连续使用递归来实现遍历。实际上这个过程就是不断的向函数调用栈中压入和弹出函数的过程,先压入参数为root.left
的prevOrderTraversalNode
的函数,直到下一个root.left
为null
时,从函数调用栈中弹出当前函数,接着开始压入参数为root.right
的prevOrderTraversalNode
的函数,直到下一个root.left
为null
时,返回上一个函数,如此循环就可以按顺序从左子树到右子树遍历得到每一个节点的值。
中序遍历
我们了解了先序遍历
后,中序遍历
就很好理解了,即先遍历左子树,再遍历根节点,最后遍历右子树。
遍历顺序为:3->5->6->7->8->9->10->11-15->12->13->14->18->20->25,代码实现就是把先序遍历
的代码打印顺序颠倒一下:
// 中序遍历
inOrder(node = this.root) {
if (node !== null) {
this.inOrder(node.left)
console.log(node.key)
this.inOrder(node.right)
}
后序遍历
这个就不用再解释了吧,先遍历右子树,再遍历根节点,最后遍历左子树。直接贴图和代码了:
// 后序遍历
postOrder(node = this.root) {
if (node !== null) {
this.postOrder(node.left)
this.postOrder(node.right)
console.log(node.key)
}
}
二叉搜索树的极值
求二叉搜索树
的最大值
和最小值
,其实我们在上面遍历的时候就已经找到了,关键点就是判断下一个左节点或者右节点是否为空。但是要注意先判断是否为空树,如果为空树直接返回null
。
// 最大值
max() {
// 判断根节点是否为空
if (this.root === null) {
return null
}
const node = this.root
while (node.right !== null) {
node = node.right
}
return node.key
}
// 最小值
min() {
// 判断根节点是否为空
if (this.root === null) {
return null
}
const node = this.root
while (node.left !== null) {
node = node.left
}
return node.key
}
搜索指定的值
还是拿一幅图来解释:
我想判断3
或者25
或者任意一个数是否在这颗二叉搜索树里要怎么办?肉眼判断(手动狗头)。还记得你说家是唯一的城堡二分法查找吗,这里也同样适用。首先同样判断是否为空树,是的话直接返回false。再判断目标数字是否大于根节点11,大于往右找,小于往左找,等于直接返回,然后重复这一操作。代码表示为:
// 查找
search(key) {
// 判断根节点是否为空
if (this.root === null) {
return false
}
// 创建一个临时变量,用于存储当前节点
let current = this.root
// 判断当前节点是否为空
while (current !== null) {
// 如果当前节点的键值小于要查找的键值,则向右子树查找
if (current.key < key) {
current = current.right
} else if (current.key > key) {
// 如果当前节点的键值大于要查找的键值,则向左子树查找
current = current.left
} else {
// 如果找到了要查找的键值,则返回当前节点
return current
}
}
// 如果没有找到要查找的键值,则返回 null
return null
}
删除操作
删除操作相对于其他操作来说,稍微复杂了亿点点,要分四种情况。
- 如果要删除的节点没有左右子节点,即删除的节点是叶节点,则直接删除。
- 如果要删除的节点只有一个左子节点,则用左子节点替换要删除的节点。
- 如果要删除的节点只有一个右子节点,则用右子节点替换要删除的节点。
- 如果要删除的节点有两个子节点,则用右子节点的最小值替换要删除的节点。
// 删除
remove(key) {
let current = this.root
let parent = null
let isLeftChild = false
// 查找要删除的节点
while (key != current) {
parent = current
if (key < current.key) {
isLeftChild = true
current = current.left
} else {
isLeftChild = false
current = current.right
}
if (current == null) {
return false
}
}
// 如果要删除的节点没有左右子节点,则直接删除
if (current.left == null && current.right == null) {
if (current == this.root) {
this.root = null
} else if (isLeftChild) {
parent.left = null
} else {
parent.right = null
}
}
// 如果要删除的节点只有一个左子节点,则用左子节点替换要删除的节点
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
}
}
// 如果要删除的节点只有一个右子节点,则用右子节点替换要删除的节点
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 {
let successor = this.getSuccessor(current)
if (current == this.root) {
this.root = successor
} else if (isLeftChild) {
parent.left = successor
} else {
parent.right = successor
}
successor.left = current.left
}
}
以上是具体代码,过程稍微有亿点点长,耐心看吧。最后贴上完整代码。
class BinarySearchTree {
constructor() {
// 根属性
this.root = null
}
// 插入节点
insert(key) {
// 创建一个新的节点
const newNode = {
key,
left: null,
right: null
}
// 如果根节点为空,则将新节点作为根节点
if (this.root == null) {
this.root = newNode
} else {
// 如果根节点不为空,则将新节点插入到二叉搜索树中
this.insertNode(this.root, newNode)
}
}
// 将新节点插入到非空二叉搜索树中
insertNode(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)
}
}
}
// 先序遍历
preOrder(node = this.root) {
if (node !== null) {
console.log(node.key)
this.preOrder(node.left)
this.preOrder(node.right)
}
}
// 中序遍历
inOrder(node = this.root) {
if (node !== null) {
this.inOrder(node.left)
console.log(node.key)
this.inOrder(node.right)
}
}
// 后序遍历
postOrder(node = this.root) {
if (node !== null) {
this.postOrder(node.left)
this.postOrder(node.right)
console.log(node.key)
}
}
// 最大值
max() {
// 判断根节点是否为空
if (this.root === null) {
return null
}
const node = this.root
while (node.right !== null) {
node = node.right
}
return node.key
}
// 最小值
min() {
// 判断根节点是否为空
if (this.root === null) {
return null
}
const node = this.root
while (node.left !== null) {
node = node.left
}
return node.key
}
// 查找
search(key) {
// 判断根节点是否为空
if (this.root === null) {
return false
}
// 创建一个临时变量,用于存储当前节点
let current = this.root
// 判断当前节点是否为空
while (current !== null) {
// 如果当前节点的键值小于要查找的键值,则向右子树查找
if (current.key < key) {
current = current.right
} else if (current.key > key) {
// 如果当前节点的键值大于要查找的键值,则向左子树查找
current = current.left
} else {
// 如果找到了要查找的键值,则返回当前节点
return current
}
}
// 如果没有找到要查找的键值,则返回 null
return null
}
// 删除
remove(key) {
let current = this.root
let parent = null
let isLeftChild = false
// 查找要删除的节点
while (key != current) {
parent = current
if (key < current.key) {
isLeftChild = true
current = current.left
} else {
isLeftChild = false
current = current.right
}
if (current == null) {
return false
}
}
// 如果要删除的节点没有左右子节点,则直接删除
if (current.left == null && current.right == null) {
if (current == this.root) {
this.root = null
} else if (isLeftChild) {
parent.left = null
} else {
parent.right = null
}
}
// 如果要删除的节点只有一个左子节点,则用左子节点替换要删除的节点
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
}
}
// 如果要删除的节点只有一个右子节点,则用右子节点替换要删除的节点
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 {
let successor = this.getSuccessor(current)
if (current == this.root) {
this.root = successor
} else if (isLeftChild) {
parent.left = successor
} else {
parent.right = successor
}
successor.left = current.left
}
}
}