二叉搜索树

本文介绍了二叉查找树(BST)的基本性质,包括其左子树上的节点值小于根节点值,右子树上的节点值大于根节点值。详细阐述了插入新节点、查找特定节点以及删除节点(包括无子节点、单子节点和双子节点的情况)的过程。此外,还提到了对BST进行中序遍历可得到递增序列的特点。
摘要由CSDN通过智能技术生成

二叉查找树

二叉查找树(Binary Search Tree,BST),又叫做二叉排序树、二叉搜索树

性质

  1. 若它的左子树不空,则左子树上所有节点的值均小于它的根节点的值;
  2. 若它的右子树不空,则右子树上所有节点的值均大于它的根节点的值;
  3. 它的左右子树也分别为二叉排序树。

插入操作:

  1. 如果该树是空树,将该结点作为根结点插入

  2. 如果该树不是空树,按照查找逻辑(大的放左边,小的放右边)确定插入位置,插入新结点,

    插入结点的位置一定是叶子结点。

     public void insert(int id, T data) {
            Node<T> newNode = new Node<T>();
            newNode.setIndex(id);
            newNode.setData(data);
            if (root == null) {
                root = newNode;
            } else {
                //从根节点开始查找
                Node<T> current = root;
                //声明父节点的引用
                Node<T> parent;
                while (true) {
                    //父节点的引用指向当前节点
                    parent = current;
                    //如果角标小于当前节点,插入到左节点
                    if (id < current.getIndex()) {
                        current = current.getLeftChild();
                        //节点为空才进行赋值,否则继续查找
                        if (current == null) {
                            parent.setLeftChild(newNode);
                            return;
                        }
                    } else {
                        //否则插入到右节点
                        current = current.getRightChild();
                        if (current == null) {
                            parent.setRightChild(newNode);
                            return;
                        }
                    }
                }
    
            }
        }
    
    

查找操作:

  1. 如果树为空树,返回空,查找失败
  2. 如果查找值x比根结点值大,往右子树找。
  3. 如果查找值x比根结点值小,往左子树找
  4. 如果查找值x等于根节点值,返回该结点

由于左子树结点值 < 根结点值 < 右子树结点值,对二叉排序树进行中序遍历,可以得到一个递增的有序序列。

针对数据 62, 88, 58, 47, 35, 73, 51, 99, 37, 93 对于的二叉排序树如图所示。

删除操作:

删除节点是二叉搜索树常用的一般操作中最复杂的。但是,删除节点在很多树的应用中又非常重要,所以要详细研究并总结特点。

找到节点后,这个要删除的节点要分三种情况讨论:

  1. 该结点是叶节点(没有子结点)。
  2. 该结点有一个子结点。
  3. 该结点有两个子结点。

情况一:删除没有子节点的节点(叶子结点)

要删除叶节点,只需要改变该节点的父节点的对应子字段的值,由指向该节点、改为null就可以了。

情况二: 删除只有一个子节点的节点

删除的这个节点只有两个连接:连向父节点的和连向它惟一的子节点的。(所以分四种情况,leftChildren和rightChildren为左子树和右子树)
在这里插入图片描述

情况三: 删除有两个子节点的节点

如果要删除的节点有两个子节点,就不能只是用它的一个子节点代替它。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rpDIld4z-1673233854421)(img/twoNode.png)]

假设要删除节点25,并且用它的根是35的右子树取代它。那么35的左子节点应该是谁呢?是要删除节点25的左子节点15,还是35原来的左子节点30?然而在这两种情况中30都会被放得不对,但又不能删掉它。

对每一个节点来说,比该节点的关键字值次高的节点是它的中序后继,可以简称为该节点的后继。在上图中,节点30就是节点25的后继。

img

    public Node<T> find(int key) {
        if (root == null) {
            return null;
        }


        Node<T> current = root;
        //如果不是当前节点while (current.getIndex() != key) {
        while (current.getIndex() != key) {

            if (key < current.getIndex()) {
                current = current.getLeftChild();
            } else {
                current = current.getRightChild();
            }

            //如果左右节点均为null,查找失败
            if (current == null) {
                return null;
            }
        }

        return current;
}

​ Node类

package tree2;

public class Node<T> {

    /**
     * 角标
     */
    private Integer index;
    /**
     * 数据
     */
    private T data;
    /**
     * 左节点
     */
    private Node leftChild;
    /**
     * 右节点
     */
    private Node rightChild;


    public Node() {
    }

    /**
     * 构造函数
     *
     * @param index 角标
     * @param data 数据
     */
    public Node(Integer index, T data) {
        this.index = index;
        this.data = data;
        this.leftChild = null;
        this.rightChild = null;
    }

    public Integer getIndex() {
        return index;
    }

    public void setIndex(Integer index) {
        this.index = index;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }

    public Node getLeftChild() {
        return leftChild;
    }

    public void setLeftChild(Node leftChild) {
        this.leftChild = leftChild;
    }

    public Node getRightChild() {
        return rightChild;
    }

    public void setRightChild(Node rightChild) {
        this.rightChild = rightChild;
    }


    @Override
    public String toString() {
        return "Node{" +
                "index=" + index +
                ", data=" + data +
                '}';
    }
}

​ BinarySearchTree类

package tree2;

public class BinarySortTree<T> {

    private Node<T> root;


    public Node<T> find(int key) {
        if (root == null) {
            return null;
        }


        Node<T> current = root;
        //如果不是当前节点while (current.getIndex() != key) {
        while (current.getIndex() != key) {

            if (key < current.getIndex()) {
                current = current.getLeftChild();
            } else {
                current = current.getRightChild();
            }

            //如果左右节点均为null,查找失败
            if (current == null) {
                return null;
            }
        }

        return current;
}








    public void inOrder(Node<T> localRoot) {
        if (localRoot != null) {
            inOrder(localRoot.getLeftChild());
            System.out.print(localRoot.getIndex() + " ");
            inOrder(localRoot.getRightChild());
        }
    }





    public void insert(int index, T data) {
        Node<T> newNode = new Node<T>();
        newNode.setIndex(index);
        newNode.setData(data);
        if (root == null) {
            root = newNode;
        } else {
            //从根节点开始查找
            Node<T> current = root;
            //声明父节点的引用
            Node<T> parent;
            while (true) {
                //父节点的引用指向当前节点
                parent = current;
                //如果角标小于当前节点,插入到左节点
                if (index < current.getIndex()) {
                    current = current.getLeftChild();
                    //节点为空才进行赋值,否则继续查找
                    if (current == null) {
                        parent.setLeftChild(newNode);
                        return;
                    }
                } else {
                    //否则插入到右节点
                    current = current.getRightChild();
                    if (current == null) {
                        parent.setRightChild(newNode);
                        return;
                    }
                }
            }
        }
    }







    public Node<T> getSuccessor(Node<T> delNode) {
        Node<T> successorParent = delNode;
        Node<T> successor = delNode;

//go to rightChild
        Node<T> current = delNode.getRightChild();

        while (current != null) {
//一直往下找左节点
            successorParent = successor;
            successor = current;
            current = current.getLeftChild();
        }

//跳出循环,此时successor为最后的一个左节点,也就是被删除节点的后继节点

//        System.out.println(current);
        return successor;
    }






    public Node<T> delete(int key) {
        if (root == null) {
            return null;
        }


        Node<T> parent = root;
        //要删除的结点
        Node<T> current = root;
        boolean isLeftChild = true;

        //删除操作第一步,查找要删除的节点
        while (current.getIndex() != key) {

            parent = current;
            if (key < current.getIndex()) {
                isLeftChild = true;
                current = current.getLeftChild();
            } else {
                isLeftChild = false;
                current = current.getRightChild();
            }

            //如果左右节点均为null,没有找到要删除的元素
            if (current == null) {
                return null;
            }

        }


        //跳出循环,找到要删除的元素:current

//        System.out.println(current);
//        System.out.println(isLeftChild);





        // 情况一:删除的结点是叶子结点,没有子结点的时候
        if (current.getLeftChild() == null && current.getRightChild() == null) {

            if (current == root) {
                //如果当前节点是根节点,将树清空
                root = null;
                return current;
            } else if (isLeftChild) {
          //如果当前节点是其父节点的做节点,将父节点的左节点清空
                parent.setLeftChild(null);
            } else {
                parent.setRightChild(null);
            }


            //情况二:删除的结点有左子树和右子树的时候
        } else if (current.getLeftChild() != null && current.getRightChild() != null) {

            //查找后继节点
            Node<T> successor = getSuccessor(current);
            Node<T> successorParent = getSuccessorParent(current);
//            System.out.println(successor);
            if (current.getRightChild() != successor) {
                current.setData(successor.getData());
                current.setIndex(successor.getIndex());
                successorParent.setLeftChild(successor.getRightChild());
            } else {
//情况3.1 如果如果删除节点有两个子节点且后继节点是删除节点的右子节点
                if (isLeftChild) {
                    parent.setLeftChild(successor);
                    successor.setLeftChild(current.getLeftChild());
                }else {
                    parent.setRightChild(successor);
                    successor.setLeftChild(current.getLeftChild());
                }


//                parent.setRightChild(current.getRightChild());
//
            }

//

            //情况三:删除的结点只有一个子节点时
        }else {

           //3.1如果删除节点只有一个子节点且没有右节点
         if (current.getRightChild() == null) {

                if (current == root) {
                    root = current.getLeftChild();
                } else if (isLeftChild) {
                    parent.setLeftChild(current.getLeftChild());
                } else {
                    parent.setRightChild(current.getLeftChild());
                }

            } else if (current.getLeftChild() == null) {

           //3.2 如果删除节点只有一个子节点且没有左节点
                if (current == root) {
                    root = current.getRightChild();
                } else if (isLeftChild) {
                    parent.setLeftChild(current.getRightChild());
                } else {
                    parent.setRightChild(current.getRightChild());
                }

            }

        }

        return current;
    }












    public Node getRootNode(){
        return root;
    }




    public Node<T> getSuccessorParent(Node<T> delNode) {
        Node<T> successorParent = delNode;
        Node<T> successor = delNode;

//go to rightChild
        Node<T> current = delNode.getRightChild();

        while (current != null) {
//一直往下找左节点
            successorParent = successor;
            successor = current;
            current = current.getLeftChild();
        }

//跳出循环,此时successor为最后的一个左节点,也就是被删除节点的后继节点

//如果successor是要删除节点右子节点的左后代
        if (delNode.getRightChild() != successor) {
            delNode.setData(successor.getData());
            delNode.setIndex(successor.getIndex());
            successorParent.setLeftChild(successor.getRightChild());


把后继节点的父节点的leftChild字段置为successor的右子节点
//            successorParent.setLeftChild(successor.getRightChild());
把successor的rightChild字段置为要删除节点的右子节点。
//           successor.setRightChild(delNode.getRightChild());
        }

        return successorParent;
    }




}

​ 测试类

package tree2;

import org.junit.Before;
import org.junit.Test;


public class TestBinaryTree2<T> {
    BinarySortTree<Integer> tree = new BinarySortTree<Integer>();

    @Before
    public void init() {
        int[] arr = {62, 88, 58, 47, 35, 73, 51, 99, 37, 93};
        for (int i = 0; i < arr.length; i++) {
            tree.insert(arr[i],i);
        }
    }


    @Test
    public void testMidOrder() {
      tree.inOrder(tree.getRootNode());
//        System.out.println(tree.getRootNode());
    }




    @Test
    public void testFind() {
        Node<Integer> node = tree.find(47);

        if (node == null) {
            System.out.println("-1");
        }else {
            System.out.println(node.getData());
        }
    }



    @Test
    public void testDeleteNode() {
        tree.inOrder(tree.getRootNode());
        System.out.println();
        tree.delete(58);
        tree.inOrder(tree.getRootNode());
    }



    @Test
    public void testSuccessor() {
        tree.inOrder(tree.getRootNode());
        Node<Integer> node = tree.find(62);
        Node successor = tree.getSuccessor(node);
        System.out.println(successor);

    }


}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值