目录
🐉今日良言:要诚恳,要坦然,要慷慨,要宽容,要有平常心。
🐳1.介绍
在创建二叉搜索树之前,先来了解一下什么是二叉搜索树?
二叉搜索树又叫二叉排序树,它或者是一棵空树,或者具有以下性质:
左子树不为空,则左子树节点的值都小于根节点的值
右子树不为空,则右子树节点的值都大于根节点的值
左右子树也是一棵二叉搜索树
如下图:
🐇2.实现步骤
这里主要实现三部分代码:查找数据、添加节点、删除节点
查找数据:
当一棵二叉搜索树构建完成后,如何在这棵树中查找数据呢?
其实我们可以类比二叉树的三种遍历,首先创建一个辅助节点cur,让cur指向根节点,先判断cur的值与查找数据之间的大小关系,如果查找数据的值大于cur的值,说明该数据可能在cur的右子树,修改cur为:cur = cur.right
,如果查找数据的值小于cur的值,说明该数据可能在cur的左子树,修改cur为:cur = cur.left;
如果找到直接到返回cur即可,不难看出,上述操作是一个循环,直到cur为空,结束循环。没找到就返回null。
图解:以找6为例:
查找操作代码:
// 查找val值
public TreeNode search(int val) {
TreeNode cur = root;
while (cur != null) {
if (cur.val < val) {
cur = cur.right;
} else if (cur.val > val) {
cur = cur.left;
} else {
return cur;
}
}
return null;
}
添加节点:
首先,如果当前二叉搜索树根节点为空,则直接创建一个节点作为根节点即可。
其次,如果二叉搜索树根节点不为空。先想到的都是找到待插入数据的位置,那么如何找到这个位置呢?显而易见,通过遍历就可以找到,以下图为例:
如果想添加一个节点4
创建一个辅助节点cur,从根节点开始,cur != null 进行循环查找,
如果查找数据的值大于cur的值,说明该数据可能在cur的右子树,修改cur为:cur = cur.right
如果查找数据的值小于cur的值,说明该数据可能在cur的左子树,修改cur为:cur = cur.left;
如果找到直接到返回false即可,因为一棵二叉搜索树中的数据值不同,
当循环结束后,此时cur的位置如下图:
所以,应当将节点4插入到3节点的右子树,很明显,如果仅仅只靠一个辅助节点cur,是无法完成添加节点操作的,所以,需要再创建一个辅助节点parent,记录上一次的cur值,那么,在每次cur值被修改之前,先将当前的cur赋给parent,当cur为空时,判断当前parent值与待插入节点值的大小关系,从而确定待插入节点的位置。
添加节点代码:
// 添加结点
public boolean insert(int val) {
if (root == null) {
root = new TreeNode(val);
return true;
}
TreeNode cur = root;
TreeNode parent = null;
while (cur != null) {
if (cur.val < val) {
parent = cur;
cur = cur.right;
} else if (cur.val > val) {
parent = cur;
cur = cur.left;
} else {
// 二叉搜索树不可能相同,返回false
return false;
}
}
// 此时判断当前val值和parent的大小关系
TreeNode node = new TreeNode(val);
if (val < parent.val) {
parent.left = node;
} else {
parent.right = node;
}
return true;
}
删除节点:
删除节点操作比较复杂,删除节点也需要两个辅助节点,仅靠一个是不足以完成删除操作的,创建一个parent和cur辅助节点,cur != null 开始循环,如果cur.val == key(待删除节点值),就进行删除操作,如果小于或者大于与添加节点操作相同,parent先记录当前cur,然后修改cur值。
具体删除操作:
根据cur的左右子树是否为空,可以分为三种情况:
1.cur.left == null
此时,先判断cur是不是根节点,如果是的话,只需要修改root = cur.right;即可完成删除操作。如果不是根节点,判断cur是parent的左孩子节点还是右孩子节点。
如果是左孩子节点,直接让parent.left = cur.right即可。如图:
如果是parent的右孩子节点,直接让parent.right = cur.right即可。如图:
2.如果cur.right == null
此时,先判断cur是不是根节点,如果是,则修改root = cur.left,即可完成删除操作,如果不是,
判断cur是parent的左孩子节点还是右孩子节点。
如果是左孩子节点,直接让parent.left = cur.left即可。如图:
如果是右孩子节点,直接让parent.right = cur.left即可。如图:
3.如果cur的左右孩子节点都不为空
此时,有两种删除方法:
第一种,找cur的右子树的最小值,将该值作为cur的新值,然后删除这个最小值节点。
第二种,找cur的左子树的最大值,将该值作为cur的新值,然后删除这个最大值节点。
这里采用的是第一种删除方法。由于要删除最后找到的那个节点,所以说,这里需要再创建两个辅助节点,targetParent的初值为cur,target的初值为cur.right,然后开始遍历找到target的左孩子节点为空的节点,每次让targetParent记录上一次的target.
当找到这个节点后,首先修改cur的值:cur.val = target.val ,然后判断target是targetParent的左孩子还是右孩子节点。
如果是右孩子节点,targetParent.right = target.right; 如图:
如果是左孩子节点,targetParent.left = target.right; 如图:
🐂3.完整代码
public class BinarySearchTree {
static class TreeNode {
public int val;
public TreeNode left;
public TreeNode right;
public TreeNode(int val) {
this.val = val;
}
}
public TreeNode root = null;
public static void main(String[] args) {
int[] arr = {5,3,4,1,7,8,2,6,0,9};
BinarySearchTree binarySearchTree = new BinarySearchTree();
for (int i = 0; i < arr.length; i++) {
binarySearchTree.insert(arr[i]);
}
binarySearchTree.inorder(binarySearchTree.root);
binarySearchTree.remove(7);
binarySearchTree.inorder(binarySearchTree.root);
}
// 中序遍历
public void inorder(TreeNode root) {
if (root == null) {
return;
}
inorder(root.left);
System.out.print(root.val+" ");
inorder(root.right);
}
// 查找val值
public TreeNode search(int val) {
TreeNode cur = root;
while (cur != null) {
if (cur.val < val) {
cur = cur.right;
} else if (cur.val > val) {
cur = cur.left;
} else {
return cur;
}
}
return null;
}
// 添加结点
public boolean insert(int val) {
if (root == null) {
root = new TreeNode(val);
return true;
}
TreeNode cur = root;
TreeNode parent = null;
while (cur != null) {
if (cur.val < val) {
parent = cur;
cur = cur.right;
} else if (cur.val > val) {
parent = cur;
cur = cur.left;
} else {
// 二叉搜索树不可能相同,返回false
return false;
}
}
// 此时判断当前val值和parent的大小关系
TreeNode node = new TreeNode(val);
if (val < parent.val) {
parent.left = node;
} else {
parent.right = node;
}
return true;
}
// 删除节点
public void remove(int key) {
TreeNode cur = root;
TreeNode parent = null;
while (cur != null) {
if (cur.val == key) {
removeNode(parent,cur);
} else if (cur.val < key) {
parent = cur;
cur = cur.right;
} else {
parent = cur;
cur = cur.left;
}
}
}
private void removeNode(TreeNode parent,TreeNode cur) {
// 根据左右子树是否为空分三种情况
if (cur.left == null) {
// 判断是不是根节点
if (cur == root) {
root = cur.right;
} else if (cur == parent.left) {
parent.left = cur.right;
} else {
parent.right = cur.right;
}
} else if (cur.right == null) {
if (cur == root) {
root = cur.left;
} else if (cur == parent.left) {
parent.left = cur.left;
} else {
parent.right = cur.left;
}
} else {
// 替罪羊删除法
TreeNode target = cur.right;
TreeNode targetParent = cur;
while (target.left != null) {
targetParent = target;
target = target.left;
}
cur.val = target.val;
// 开始判断target是targetParent的左孩子还是右孩子
if (target == targetParent.left) {
targetParent.left = target.right;
} else {
targetParent.right = target.right;
}
}
}
}