目录
🌟前言
二叉搜索树(Binary Search Tree)简称BST。BST主要有三个特性:
(1)对于BST的每一个节点node,左子树节点值<根节点值<右子树节点值;
(2)对于BST的每一个节点node,其左侧子树和右侧子树都是BST;
(3)BST的中序遍历结果是有序的 -> 升序!(非常重要的性质!)
而且BST中不存在重复的值!
🌟基础实现
首先肯定是先定义一个类TreeNode。主要包括根节点,树中节点的数量。节点的值,该节点的左孩子,右孩子,以及构造方法。
private TreeNode root;//根节点
private int size;//有效节点个数
private static class TreeNode{
int val;
TreeNode left;
TreeNode right;
public TreeNode() {
}
public TreeNode(int val) {
this.val = val;
}
1.1 增
要添加一个新节点,一定是保存在叶子节点的位置。至于是树的哪一边,需要判断该值与根节点的大小:如果该节点值小于根节点,则在左子树插入;如果该值大于根节点值,则在右子树插入。直到碰见空节点为止。
代码实现:
//外部调用
public void add(int val){
//内部维护一个addProx函数
root = addProx(root,val);
}
private TreeNode addProx(TreeNode root, int val) {
//树为空
if(root == null){
//将新传入的val构建为一个新的节点
TreeNode node = new TreeNode(val);
size++;
return node;//返回新的树的根节点
} else if (val < root.val) {//在左子树插入
root.left = addProx(root.left,val);
return root;
}
root.right = addProx(root.right,val);
return root;
}
代码的调用过程:
1.2 查
查找这里我们主要实现三部分:分别是查找最大值,查找最小值。
✍ 查找最大值
主要思路:
因为BST的特点,左子树节点值<根节点值<右子树节点值,所以最大值一定在右子树部分。那我们不断的向BST的右侧遍历,直到遇见第一个右孩子为空的根节点,则该节点保存的就一定是最大值。
代码实现: 也是递归的方式(也很好理解)
public int searchMax(){
if(root == null){
throw new NoSuchElementException("no element");
}
TreeNode maxNode = findMax(root);
return maxNode.val;
}
private TreeNode findMax(TreeNode root) {
if(root.right == null){
return root;
}
return findMax(root.right);
}
递归的过程:
✍ 查找最小值
同理,如果要找到该BST的最小值,则最小值一定在树的左侧,所以不断在左子树遍历,直到遇见第一个左孩子为空的根节点,则该节点的值就是最小值。
代码实现:
public int searchMin(){
if(root == null){
throw new NoSuchElementException("no element");
}
TreeNode minNode = findMin(root);
return minNode.val;
}
private TreeNode findMin(TreeNode root) {
if(root.left == null){
return root;
}
return findMin(root.left);
}
递归过程:
1.3 删
删除这里我们主要实现三部分:分别是删除最大值,删除最小值,删除任意值。
✍ 删除最大值
主要思路: 先找到最大值节点,然后记录下来该节点的左孩子值(此时肯定不存在右孩子),然后将该左孩子拼接到原树中。
代码实现:
public int removeMax(){
TreeNode maxNode = findMax(root);
root = removeMax(root);
return maxNode.val;
}
//内部删除最大值函数
private TreeNode removeMax(TreeNode root) {
if(root.right == null){
//当前的root节点就是最大值
TreeNode left = root.left;
root.left = root = null;
size--;
return left;
}
root.right = removeMax(root.right);
return root;
}
递归过程:
✍ 删除最小值
删除最小值也是同理:先递归的找到最小值节点,然后保存该节点的右孩子,删除最小值节点之后,将右孩子与原根节点拼接。
代码实现:
public int removeMin(){
TreeNode node = findMin(root);//找到最小值的节点
root = removeMin(root);
return node.val;
}
//内部删除最小值函数
private TreeNode removeMin(TreeNode root) {
if(root.left == null){//root就是要被删除的节点
TreeNode right = root.right;//先保存该节点的右孩子
root.right = root = null;//清除该节点
return right;//返回该右孩子
}
root.left = removeMin(root.left);//递归调用
return root;
}
✍ 删除任意值
删除任意值是这三种里面情况最复杂的。函数目标:在以当前root为根的BST中删除值为val的节点,并返回删除后的树根节点。
代码实现:
删除一个值,主要存在以下三种情况:
🌼 待删除值小于根节点的值,则要在左子树中删除;
🌼 待删除值大于根节点的值,则要在右子树中删除;
🌼 待删除值等于根节点的值,则该根节点就是要被删除的节点:而这种情况中,又存在以下三种情况:
(1)待删除节点只有左孩子,没有右孩子:则为最大值的删除
(2)待删除节点只有右孩子,没有左孩子,则为最小值的删除
(3)待删除节点既有左孩子,又有右孩子,则这种情况最为复杂。我们来重点分析一下:这种情况的关键在于删除了该节点之后,该节点位置就是空,那么我该从哪里找一个新的节点代替这个被删除节点呢?看下图:
分析:首先我需要一个节点顶替30节点的位置,而且这个节点还要满足BST的特性:左子树节点值<根节点值<右子树值:以50为节点的这棵树为例:新节点的值一定在39和58之间,所以只有该被删除节点的左子树的最大值42和被删除节点右子树的最小值53满足条件,可以作为新的后节点。以53为例,接下来我们看一下怎么将53这个节点拼接到原来50的位置上。
大致过程:(原谅我这个手残党😭图画的有点丑)注意步骤23的顺序不能交换,否则在拼接右子树的时候,要先删除successor,此时删除的就不是successor为53这个节点了。
代码实现:
public void remove(int val) {
remove(root,val);
}
public TreeNode remove(TreeNode root, int val){
//base case
if(root == null){
return null;
} else if (val < root.val) {
//在左子树的删除操作:递归调用
root.left = remove(root.left,val);
return root;
} else if (val > root.val) {
//在右子树的删除:递归调用
root.right = remove(root.right,val);
return root;
} else{
//当前节点就是要被删除的节点
//(1)当该节点只有左孩子,没有右孩子->相当于最大值的删除
if(root.right == null){
TreeNode left = root.left;
root.left = root = null;
size --;
return left;
}
//(2)当该节点只有右孩子,没有左孩子->相当于最小值的删除
if(root.left == null){
TreeNode right = root.right;
root.right = root = null;
size --;
return right;
}
//(3)当前节点左右孩子都有,此时删除该节点,则需要一个新的节点顶替过来:
//后来发现:该节点要么是该删除节点的左子树的最大值,要么是该删除节点的右子树的最小值
TreeNode successor = findMin(root.right);//找到该顶替的节点:右子树的最小值
successor.right = removeMin(root.right);//断开successor节点的右子树分支
successor.left = root.left;
root.left = root.right = root = null;//清空该根节点以及所有的子树分支
return successor;//successor就是新的树根
}
}
1.4 改
一般BST中不使用修改这个功能,因为BST中不存在重复的值。如果要修改,实际上是先删除后增加。不能直接修改(要保证BST的性质不能变)。
1.5 打印BST
能读懂打印出来的含义:28为根节点,16和40为28的左右节点。16的左孩子为13,右孩子为19;13的左孩子为Null,右孩子为15...以此类推。
完整代码实现
public class MyBST {
private TreeNode root;//根节点
private int size;//有效节点个数
private static class TreeNode{
int val;
TreeNode left;
TreeNode right;
public TreeNode() {
}
public TreeNode(int val) {
this.val = val;
}
}
/**
* 增:插入新节点一定保存在叶子节点:不断和根节点比较大小,若新的值小于根节点,则放在左子树的插入;否则在右子树的插入。直到碰到空节点为止。
*/
//外部调用
public void add(int val){
//内部维护一个addProx函数
root = addProx(root,val);
}
private TreeNode addProx(TreeNode root, int val) {
//树为空
if(root == null){
//将新传入的val构建为一个新的节点
TreeNode node = new TreeNode(val);
size++;
return node;//返回新的树的根节点
} else if (val < root.val) {//在左子树插入
root.left = addProx(root.left,val);
return root;
}
root.right = addProx(root.right,val);
return root;
}
/**
* 查:查找最大最小和任意值。 最大值:不断向BST右侧遍历,碰到的第一个root.right为空的情况,则该根节点的值为最大值;最小值:左,第一个root.left==null
* 任意值的查询就是二分查找。若走到空树还没找到特定值,则说明该值在BST中不存在。
* */
public int searchMax(){
if(root == null){
throw new NoSuchElementException("no element");
}
TreeNode maxNode = findMax(root);
return maxNode.val;
}
private TreeNode findMax(TreeNode root) {
if(root.right == null){
return root;
}
return findMax(root.right);
}
//查找最小值
public int searchMin(){
if(root == null){
throw new NoSuchElementException("no element");
}
TreeNode minNode = findMin(root);
return minNode.val;
}
private TreeNode findMin(TreeNode root) {
if(root.left == null){
return root;
}
return findMin(root.left);
}
//查找任意值
public boolean searchNum(int val){
if(root == null){
throw new NoSuchElementException("root is null,no such element!");
}else{
return searchNum(root,val);
}
}
//内部定义searchNum函数
private boolean searchNum(TreeNode root, int val) {
if(val == root.val){
return true;
} else if (val < root.val) {
searchNum(root.left,val);
return true;
} else {
searchNum(root.right,val);
return true;
}
}
/**
* 判断SBT中是否包含某个值
*/
public boolean contains(int val){
boolean result = containsProx(root,val);
return result;
}
private boolean containsProx(TreeNode root, int val) {
if(root == null){
return false;
}
if(root.val == val){
return true;
} else if (root.val > val) {
return containsProx(root.left,val);
}
return containsProx(root.right,val);
}
/**
* 删除:最大值,最小值
*/
public int removeMax(){
TreeNode maxNode = findMax(root);
root = removeMax(root);
return maxNode.val;
}
//内部删除最大值函数
private TreeNode removeMax(TreeNode root) {
if(root.right == null){
//当前的root节点就是最大值
TreeNode left = root.left;
root.left = root = null;
size--;
return left;
}
root.right = removeMax(root.right);
return root;
}
public int removeMin(){
TreeNode node = findMin(root);//找到最小值的节点
root = removeMin(root);
return node.val;
}
//内部删除最小值函数
private TreeNode removeMin(TreeNode root) {
if(root.left == null){//root就是要被删除的节点
TreeNode right = root.right;//先保存该节点的右孩子
root.right = root = null;//清除该节点
return right;//返回该右孩子
}
root.left = removeMin(root.left);//递归调用
return root;
}
/**
* 删除任意值:在当前以root为根的BST中删除值为val的节点,返回删除后的树根节点
*/
public void remove(int val) {
remove(root,val);
}
public TreeNode remove(TreeNode root, int val){
//base case
if(root == null){
return null;
} else if (val < root.val) {
//在左子树的删除操作:递归调用
root.left = remove(root.left,val);
return root;
} else if (val > root.val) {
//在右子树的删除:递归调用
root.right = remove(root.right,val);
return root;
} else{
//当前节点就是要被删除的节点
//(1)当该节点只有左孩子,没有右孩子->相当于最大值的删除
if(root.right == null){
TreeNode left = root.left;
root.left = root = null;
size --;
return left;
}
//(2)当该节点只有右孩子,没有左孩子->相当于最小值的删除
if(root.left == null){
TreeNode right = root.right;
root.right = root = null;
size --;
return right;
}
//(3)当前节点左右孩子都有,此时删除该节点,则需要一个新的节点顶替过来:
//后来发现:该节点要么是该删除节点的左子树的最大值,要么是该删除节点的右子树的最小值
TreeNode successor = findMin(root.right);//找到该顶替的节点:右子树的最小值
successor.right = removeMin(root.right);//断开successor节点的右子树分支
successor.left = root.left;
root.left = root.right = root = null;//清空该根节点以及所有的子树分支
return successor;//successor就是新的树根
}
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
printBST(root,0,sb);
return sb.toString();
}
/**
* 在以当前root为根的BST中,将当前节点的层次和值,拼接到sb对象中
*/
private void printBST(TreeNode root, int level, StringBuilder sb) {
//base case
if(root == null){
sb.append(printLine(level));
sb.append("Null\n");//树为空,直接打印Null
return;
}
//当前树不为空
sb.append(printLine(level));//先根据层数拼接--的个数
sb.append(root.val);//拼接值
sb.append("\n");//换行
//递归的打印左树
printBST(root.left,level+1,sb);
//递归的打印右树
printBST(root.right,level+1,sb);
}
private String printLine(int level) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < level; i++) {
sb.append("--");//一层打印一次--
}
return sb.toString();
}
}