二叉搜索树有哪些常见的操作呢?
insert(key):向树中插入一个新的键。search(key):在树中查找一个键,如果结点存在,则返回true;如果不存在,则返回false。preOrderTraverse:通过先序遍历方式遍历所有结点。inOrderTraverse:通过中序遍历方式遍历所有结点。postOrderTraverse:通过后序遍历方式遍历所有结点。min:返回树中最小的值/键。max:返回树中最大的值/键。remove(key):从树中移除某个键。
创建空的二叉搜索树:
class BinarySearchTree {
constructor() {
// 创建一个空树
this.root = null
}
}
创建节点元素:
class Node {
constructor(key) {
// 设置节点的内容
this.key = key;
// 设置节点的左子树、右子树的内容为空
this.left = null
this.right = null
}
}
对二叉搜索树进行操作(在二叉搜索树中添加方法):
1.向树中插入一个新的键。 insert(key)
//1.insert(key):向树中插入一个新的键。
insert(ele) {
// 创建一个新节点
var newnode = new Node(ele)
// 2.判断根节点是否有值
if (this.root == null) {
this.root = newnode
} else {
// 如果根节点不为null
this.inserNode(this.root, newnode)
}
}
// 创建一个方法inserNode(root,newnode)
// 判断新节点应该是根节点的左子树,还是右子树
// 插入非根节点
inserNode(root, newnode) {
// 如果新节点的key值小于根节点的key值
if (newnode.key < root.key) {
// 判断根节点的左子树是否为null
if (root.left == null) {
// 如果左子树为null
// 那么就让左子树等于新节点
root.left = newnode
} else {
// 如果左子树不为null
// 因为左、右子树本身也都是二叉搜索树。
// 所以就让左子树假设成一个新的根节点
this.inserNode(root.left, newnode)
}
} else {
// 如果新节点的key值大于根节点的key值
// 判断根节点的右子树是否为null
if (root.right == null) {
// 如果右子树为null
// 那么就让右子树等于新节点
root.right = newnode
} else {
// 如果右子树不为null
// 因为左、右子树本身也都是二叉搜索树。
// 所以就让右子树假设成一个新的根节点
this.inserNode(root.right, newnode)
}
}
}
2.遍历:
先序遍历:preOrderTraversal()
// 先序遍历:
preOrderTraversal() {
this.preOrderTraversalNode(this.root)
}
preOrderTraversalNode(root) {
//当根节点不等于null时
if (root != null) {
// 1.根
console.log(root.key);
// 2.前序遍历左子树
this.preOrderTraversalNode(root.left)
// 3.前序遍历右子树
this.preOrderTraversalNode(root.right)
}
}
中序遍历:inOrderTraversal()
// 中序遍历
inOrderTraversal() {
this.inOrderTraversalNode(this.root)
}
inOrderTraversalNode(root) {
if (root != null) {
// 1.中序遍历左子树
this.inOrderTraversalNode(root.left)
// 2.根
console.log(root.key);
// 3.中序遍历右子树
this.inOrderTraversalNode(root.right)
}
}
后序遍历:postOrderTraversal()
// 后序遍历
postOrderTraversal() {
this.postOrderTraversalNode(this.root)
}
postOrderTraversalNode(root) {
if (root != null) {
// 1.后序遍历左子树
this.postOrderTraversalNode(root.left)
// 2.后序遍历右子树
this.postOrderTraversalNode(root.right)
// 3.根
console.log(root.key);
}
}
3.返回树中的最值:
最小值:
思想:找到二叉树中最左边,最下面的那一个节点
// 查找最小值
getMin(){
if(this.root==null){
return
}else{
// 声明一个current值等于根节点
let current=this.root
while(current.left!=null){
// 如果current的左子树有值
// 就让current值等于current的左子树
current=current.left
}
// 直到current的左子树没有值
// 就意味着current的值就是树中最左边,最下面的值
// 也就是最小值
return current.key
}
}
最大值:
思想:找到二叉树中最右边,最下面的那一个节点
// 查找最大值
getMax(){
if(this.root==null){
return
}else{
// 声明一个current值等于根节点
let current=this.root
while(current.right!=null){
// 如果current的右子树有值
// 就让current值等于current的右子树
current=current.right
}
// 直到current的右子树没有值
// 就意味着current的值就是树中最右边,最下面的值
// 也就是最大值
return current.key
}
}
4.删除节点
// 删除节点
remove(ele){
if(this.root==null){
return
}
let current=this.root,parent,isLeftChild;
// 找到删除节点和父节点,判断是左结点吗?
while(ele!=current.key){
if(ele<current.key){
parent=current
current=current.left
isLeftChild=true
}else{
parent=current
current=current.right
isLeftChild=false
}
}
let delnode=current
// console.log(`当前删除的元素是${current.key},删除元素的父节点是${parent},${isLeftChild}`);
// 1.删除叶节点 左右子树都为空
if(delnode.left==null&&delnode.right==null){
if(delnode==this.root){
this.root=null
}else{
if(isLeftChild){
// 如果删除的节点是左子树,就让父节点删除它的左子树
parent.left=null
}else{
// 如果删除的节点是右子树,就让父节点删除它的右子树
parent.right=null
}
}
}else if(delnode.left!=null&&delnode.right==null){
//删除只有一个左子节点的节点
if(delnode==this.root){
this.root=delnode.left
}else{
if(isLeftChild){
// 如果current是parent的左子树
// 删除current指向的节点
// 让parent的左子树指向current的左结点
parent.left=delnode.left
}else{
// 如果current是parent的右子树
// 删除current指向的节点
// 让parent的右子树指向current的左结点
parent.right=delnode.left
}
}
}else if(delnode.left==null&&delnode.right!=null){
//删除只有一个右子节点的节点
if(delnode==this.root){
this.root=delnode.right
}else{
if(isLeftChild){
// 如果current是parent的左子树
// 删除current指向的节点
// 让parent的左子树指向current的右结点
parent.left=delnode.right
}else{
// 如果current是parent的右子树
// 删除current指向的节点
// 让parent的右子树指向current的右结点
parent.right=delnode.right
}
}
}else{
let huoji=this.gethouji(delnode)
if(delnode==this.root){
this.root=huoji;
}else{
if(isLeftChild){
parent.left=huoji
}else{
parent.right=huoji
}
}
huoji.left=delnode.left
}
}
//
gethouji(delnode){
// 后继
let current=delnode.right
let huojiparent=delnode
while(current.left!=null){
huojiparent=current;
current=current.left
}
if(current!=delnode.right){
huojiparent.left=current.right
current.right=delnode.right
}
return current
}
}
图解:
1.删除只有一个节点的结点:
3.删除有左右子树的节点,需要注意:该节点的左子树中最大值或该节点的右子树中最小值可以代替该节点的位置(因为二叉树必须满足节点的左子树的值必须小于根,小于右子树;
右子树的值必须大于根,大于左子树)
5.在树中查找一个键,如果结点存在,则返回true;如果不存在,则返回false。 search(key)
seacrh(ele){
if(this.root==null){
return
}
let current=this.root
while(current){
if(ele<current.key){
// 左边找
current=current.left
}else if(ele>current.key){
current=current.right
}else{
return true
}
}
return false
}
完整代码:
<script>
class Node {
constructor(key) {
// 设置节点的内容
this.key = key;
// 设置节点的左子树、右子树的内容为空
this.left = null
this.right = null
}
}
class BinarySearchTree {
constructor() {
// 创建一个空树
this.root = null
}
//1.insert(key):向树中插入一个新的键。
insert(ele) {
// 创建一个新节点
var newnode = new Node(ele)
// 2.判断根节点是否有值
if (this.root == null) {
this.root = newnode
} else {
// 如果根节点不为null
this.inserNode(this.root, newnode)
}
}
// 创建一个方法inserNode(root,newnode)
// 判断新节点应该是根节点的左子树,还是右子树
// 插入非根节点
inserNode(root, newnode) {
// 如果新节点的key值小于根节点的key值
if (newnode.key < root.key) {
// 判断根节点的左子树是否为null
if (root.left == null) {
// 如果左子树为null
// 那么就让左子树等于新节点
root.left = newnode
} else {
// 如果左子树不为null
// 因为左、右子树本身也都是二叉搜索树。
// 所以就让左子树假设成一个新的根节点
this.inserNode(root.left, newnode)
}
} else {
// 如果新节点的key值大于根节点的key值
// 判断根节点的右子树是否为null
if (root.right == null) {
// 如果右子树为null
// 那么就让右子树等于新节点
root.right = newnode
} else {
// 如果右子树不为null
// 因为左、右子树本身也都是二叉搜索树。
// 所以就让右子树假设成一个新的根节点
this.inserNode(root.right, newnode)
}
}
}
// 先序遍历:
preOrderTraversal() {
this.preOrderTraversalNode(this.root)
}
preOrderTraversalNode(root) {
//当根节点不等于null时
if (root != null) {
// 1.根
console.log(root.key);
// 2.前序遍历左子树
this.preOrderTraversalNode(root.left)
// 3.前序遍历右子树
this.preOrderTraversalNode(root.right)
}
}
// 中序遍历
inOrderTraversal() {
this.inOrderTraversalNode(this.root)
}
inOrderTraversalNode(root) {
if (root != null) {
// 1.中序遍历左子树
this.inOrderTraversalNode(root.left)
// 2.根
console.log(root.key);
// 3.中序遍历右子树
this.inOrderTraversalNode(root.right)
}
}
// 后序遍历
postOrderTraversal() {
this.postOrderTraversalNode(this.root)
}
postOrderTraversalNode(root) {
if (root != null) {
// 1.后序遍历左子树
this.postOrderTraversalNode(root.left)
// 2.后序遍历右子树
this.postOrderTraversalNode(root.right)
// 3.根
console.log(root.key);
}
}
// 查找最小值
getMin(){
if(this.root==null){
return
}else{
// 声明一个current值等于根节点
let current=this.root
while(current.left!=null){
// 如果current的左子树有值
// 就让current值等于current的左子树
current=current.left
}
// 直到current的左子树没有值
// 就意味着current的值就是树中最左边,最下面的值
// 也就是最小值
return current.key
}
}
// 查找最大值
getMax(){
if(this.root==null){
return
}else{
// 声明一个current值等于根节点
let current=this.root
while(current.right!=null){
// 如果current的右子树有值
// 就让current值等于current的右子树
current=current.right
}
// 直到current的右子树没有值
// 就意味着current的值就是树中最右边,最下面的值
// 也就是最大值
return current.key
}
}
// 删除节点
remove(ele){
if(this.root==null){
return
}
let current=this.root,parent,isLeftChild;
// 找到删除节点和父节点,判断是左结点吗?
while(ele!=current.key){
if(ele<current.key){
parent=current
current=current.left
isLeftChild=true
}else{
parent=current
current=current.right
isLeftChild=false
}
}
let delnode=current
// console.log(`当前删除的元素是${current.key},删除元素的父节点是${parent},${isLeftChild}`);
// 1.删除叶节点 左右子树都为空
if(delnode.left==null&&delnode.right==null){
if(delnode==this.root){
this.root=null
}else{
if(isLeftChild){
// 如果删除的节点是左子树,就让父节点删除它的左子树
parent.left=null
}else{
// 如果删除的节点是右子树,就让父节点删除它的右子树
parent.right=null
}
}
}else if(delnode.left!=null&&delnode.right==null){
//删除只有一个左子节点的节点
if(delnode==this.root){
this.root=delnode.left
}else{
if(isLeftChild){
// 如果current是parent的左子树
// 删除current指向的节点
// 让parent的左子树指向current的左结点
parent.left=delnode.left
}else{
// 如果current是parent的右子树
// 删除current指向的节点
// 让parent的右子树指向current的左结点
parent.right=delnode.left
}
}
}else if(delnode.left==null&&delnode.right!=null){
//删除只有一个右子节点的节点
if(delnode==this.root){
this.root=delnode.right
}else{
if(isLeftChild){
// 如果current是parent的左子树
// 删除current指向的节点
// 让parent的左子树指向current的右结点
parent.left=delnode.right
}else{
// 如果current是parent的右子树
// 删除current指向的节点
// 让parent的右子树指向current的右结点
parent.right=delnode.right
}
}
}else{
let huoji=this.gethouji(delnode)
if(delnode==this.root){
this.root=huoji;
}else{
if(isLeftChild){
parent.left=huoji
}else{
parent.right=huoji
}
}
huoji.left=delnode.left
}
}
//
gethouji(delnode){
// 后继
let current=delnode.right
let huojiparent=delnode
while(current.left!=null){
huojiparent=current;
current=current.left
}
if(current!=delnode.right){
huojiparent.left=current.right
current.right=delnode.right
}
return current
}
// 查找 seacrh(ele)
seacrh(ele){
if(this.root==null){
return
}
let current=this.root
while(current){
if(ele<current.key){
// 左边找
current=current.left
}else if(ele>current.key){
current=current.right
}else{
return true
}
}
return false
}
}
// 测试代码
var bst = new BinarySearchTree()
// 插入数据
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)
console.log(bst)
var resultString = ""
// 测试前序遍历结果
// bst.preOrderTraversal(function (key) {
// resultString += key + " "
// })
// // 测试中序遍历结果
// resultString = ""
// bst.inOrderTraversal(function (key) {
// resultString += key + " "
// })
// // 测试后续遍历结果
// resultString = ""
// bst.postOrderTraversal(function (key) {
// resultString += key + " "
// })
</script>