二十 二分搜索树完整实现:查找删除操作

Posted ltfxy

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了二十 二分搜索树完整实现:查找删除操作相关的知识,希望对你有一定的参考价值。

二分搜索树删除元素的逻辑:

技术分享图片

 

二分搜索树的完整实现:

package com.lt.datastructure.BST;

import java.util.LinkedList;
import java.util.Queue;
import java.util.Stack;

public class BST<E extends Comparable<E>> {
     
    private class Node{
        public E e;
        Node left,right;
        
        public Node(E e) {
            this.e = e;
            this.left = left;
            this.right = right;
        }
       
    }
    
    private Node root;
    private int size;
    
    public BST(){
        root = null;
        size = 0;
    }

    public int size() {
      return size;
    }
    
    public boolean isEmpty(){
        return size==0;
    }
    
    /*
     * 二分搜索树添加元素
     * 小于根结点,添加到左边,大于则添加到右边,等于根节点,则不作任何改变,二分搜索树不包含重复元素
     * 
     */
    public void add(E e){
        //如果根节点为空
        if(root == null){
            root = new Node(e);
            size ++;
        }else{
            add(root,e);
        }
    }
    //向以root为根的二分搜索树中插入元素E,递归算法
    private Node add(Node node , E e){
/*        //元素重复,不做操作
        if(e.equals(node.e)){
            return;
        }
        //小于根节点,而左子树为空,添加到左子树
        else if(e.compareTo(node.e)<0 && node.left==null){
            node.left = new Node(e);
            size ++;
            return;
        }
        //大于根节点而右子树为空,添加到右子树
        else if(e.compareTo(node.e)>0 && node.right==null){
            node.right = new Node(e);
            size ++;
            return;
        }
        
        //根节点的左右子树不为空,调用递归,直到找到孩子为空的情况
        if(e.compareTo(node.e)<0){
            add(node.left,e);
        }else{
            add(node.right,e);
        }
*/
        
        //递归的出口,找到子树为null,则必然添加,完成操作
        if(node == null){
            size++;
            return new Node(e);
        }
        
        if(e.compareTo(node.e)<0){
            //如果左子树为null,则node.left = new Node(e);如果不为空,继续递归
            node.left = add(node.left,e);
        }
        else if(e.compareTo(node.e)>0){
            ////如果右子树为null,则node.right = new Node(e);如果不为空,继续递归
            node.right = add(node.right,e);
        }
        //其他情况,比如元素相等,则返回传进来的根节点,不做操作
        return node;
    }
    
    //查询二分搜索树中是否包含元素e
    public boolean contains(E e){
        return contains(root,e);
    }    
    //看以node为根的二分搜索树中是否含有元素e,递归实现
    private boolean contains(Node node , E e){
        if(node == null){
            return false;
        }
        
        if(e.compareTo(node.e)==0){
            return true;
        }
        else if(e.compareTo(node.e)<0){
            return contains(node.left,e);
        }else{
            return contains(node.right,e);
        }
    }
    
    //查找二分搜索树的最小元素
    public E minimum(){
        if(size==0)
            throw new IllegalArgumentException("BST is Empty");
    
        return minimum(root).e;
    }
    private Node minimum(Node node) {
        //递归终点:node.ledt==null,递归调用: return minimum(node.left)
           return node.left==null ? node:minimum(node.left);
    }
    
    //查找二分搜索树的最大元素
    public E maximum(){
        if(size==0)
            throw new IllegalArgumentException("BST is Empty");
        return maximum(root).e;
    }
    private Node maximum(Node node) {
       return node.right == null ? node : maximum(node.right);     
    }
    
    //删除二分搜索树的最小值并返回
    public E removeMin(){
        E ret = minimum();
        //改变最小值所对应的根节点,达到删除的目的
        root = removeMin(root);
        return ret;
    }
    
    //删除掉以node为根的二分搜索树的最小节点
    //返回删除结点后新的二分搜索树的根,根节点依然为node
    private Node removeMin(Node node) {
        //递归终点,node.left==null
        if(node.left == null){
            //如果有右子树,将其保存,如果没有,返回null
            Node rightNode = node.right;
            node.right = null;
            size--;
            return rightNode;
        }
        //假如node.left.e是最小值,node.left = node.left.right
        //right为空,则最小值为空,right不为空,则原结点最小值删除,右子树对接保留
        node.left = removeMin(node.left);
        return node;
    }
    
    //删除二分搜索树的最大值
    public E removeMax(){
        E ret = maximum(root).e;
        //改变最小值所在根节点,达到删除的目的
        root = removeMax(root);
        return ret;
    }
    private Node removeMax(Node node) {
        //递归终点
        if(node.right==null){
            Node leftNode = node.left;
            node.right = null;
            size--;
            return leftNode;
        }
        //递归调用
        node.right = removeMax(node.right);
        return node;
    }
    
    /*
     * 删除二叉树的结点:
     * 待删除结点右子树为空:将此结点的左子树取代它的位置
     * 待删除结点左子树为空:将此结点的右子树取代它的位置
     * 待删除结点左右子树都不为空:
     *     1 找到比待删除结点大的最小节点:即待删除结点右子树的最小值minimum(node.r)取代它的位置
     *     1 找到比待删除结点小的最大节点:即待删除结点右子树的最小值maximum(node.r)取代它的位置
     */
    public void remove(E e){
      //删除以root为根结点中值为e的元素,删完之后赋给root
      root = remove(root,e);    
    }
    
    private Node remove(Node node, E e) {
        //递归终点
        if(node == null){
            return null;
        }
        //查找e相对于root的位置,
        if(e.compareTo(node.e)<0){
          //如果在左边,则继续往左深度查找,直到找到或者null
          //因为递归到null(结点不存在)或者删除(结点也不存在)才停止,所以返回的是上一个结点node    
          node.left = remove(node.left,e);
          return node;
        }
        else if(e.compareTo(node.e)>0){
          //如果在右边,则往右深度查找,直到找到或者null
            node.right = remove(node.right,e);
            return node;   
        }
        else{//e.compareTo(node.e)=0
            //此时已找到
            //如果该结点左右子树都为空,则参考1、2情况皆可
            //1 如果该结点的左子树为空,则删除该结点,使其指向原来的右子树
            if(node.left == null){
                Node rightNode = node.right;
                node.right = null;
                size--;
                return rightNode;//rightNode替代待删除结点:node.x=rightNode; return node;
            }
            //2如果该结点的右子树为空,则删除该结点,使其指向原来的左子树
            if(node.right == null){
                Node leftNode = node.left;
                node.left = null;
                size --;
                return leftNode;
            }
            //3 如果左右结点都不为空,找到大于且最接近此节点的结点,即该结点右子树的最小值minimum(node.r)
            Node successor = minimum(node.right);
            //此结点的右子树由原来的右子树(需要剔原来右子树里的此结点)接上
            successor.right = removeMin(node.right);
            //此节点的左子树由原来的左子树接上
            successor.left = node.left;
            //删除原来的树
            node.left = null;
            node.right = null;
            
            return successor; //successor替代了待删除结点
        }
        
    }

    /*
     * 二分搜索树的前序遍历(先访问结点,再访问左,右子树),最自然,最常用的遍历方式
     * 
     * */
    public void preOrder(){
        preOrder(root);
    }
    
    private void preOrder(Node node){
        //递归终止
        if(node==null){
            return;
        }
        //递归调用
        System.out.println(node.e);//首先打印根节点
        preOrder(node.left);//然后递归left,直到left为空,回溯,打印left由深到浅
        preOrder(node.right);//最后递归完了left,递归right,right打印由浅到深

    }
    
    /*
     * 二分搜索树的中序遍历(访问左子树,结点,右子树),顺序由小到大,最自然,最常用的遍历方式
     * */
    public void inOrder(){
        inOrder(root);
    }
    
    //中序遍历以node为根的二分搜索树,递归算法
    private void inOrder(Node node){
      if(node==null){
          return;
      }
      inOrder(node.left);//由深到浅打印left
      System.out.println(node.e);//每递归一次,打印当前根节点
      inOrder(node.right);//由浅到深打印right
    }
    /*
     * 二分搜索树的后序遍历(访问右子树,左子树,结点),最自然,最常用的遍历方式
     * */
    public void postOrder(){
        postOrder(root);
    }

    private void postOrder(Node node) {
        //递归的终点
        if(node == null){
            return;
        }    
        
        postOrder(node.left);//打印right由深到浅
        postOrder(node.right);//打印left由深到浅
        System.out.println(node.e);//最后打印根节点
    }
    /*
     * 先序中序后序遍历的打印特点:
     *   对于每个结点,都有三次访问,可以用三个点代表三次操作。
     *   先序遍历:打印发生在第一此访问。
     *   中序遍历:打印发生在第二次访问。
     *   后序遍历:打印发生在第三次访问。
     */
    
    /*
     * 非递归的前序遍历,栈实现
     * 将根节点入栈,定义cur接收出栈结点
     * 当栈不为null,则打印cur.e
     * 因为先进后出,所以依次入栈cur的右子树,左子树,出栈、打印栈的左子树,右子树
     */
    public void inOrderNR(){
        Stack<Node> stack = new Stack<>();
        stack.push(root);
        while(!stack.isEmpty()){
            Node cur = stack.pop();
            System.out.println(cur.e);
            
            if(cur.right!=null){
                stack.push(cur.right);
            }
            if(cur.left!=null){
                stack.push(cur.left);
            }
        }
    }
    /*
     * 二分搜索树的层序遍历(广度优先遍历),队列实现
     * 广度优先遍历优势在于更快找到想要查询的元素,主要用于搜索策略,算法设计--最短路径(无权图)
     */
    public void levelOrder(){
        Queue<Node> q  = new LinkedList<>();
        q.add(root);
        while(!q.isEmpty()){
            Node cur = q.remove();
            System.out.println(cur.e);
            
            if(cur.left!=null){
                q.add(cur.left);
            }
            if(cur.right!=null){
                q.add(cur.right);
            }
        }
    }
    
    //遍历的展示
    @Override
    public String toString() {
        StringBuilder res = new StringBuilder();
        BSTString(root,0,res);
        return res.toString();
    }
    //生成以node为根节点,深度为depth描述的字符串
    private void BSTString(Node node, int depth, StringBuilder res) {
        if(node==null){
            res.append(DepthString(depth)+"null
");
            return;
        }
        
        res.append(DepthString(depth)+node.e+"
");
        BSTString(node.left,depth+1,res);
        BSTString(node.right, depth+1, res);
    }

    private String DepthString(int depth) {
        StringBuilder res = new StringBuilder();
        for(int i=0; i<depth ; i++){
            res.append("--");
        }
        return res.toString();
    }
}

测试:

查找最大值最小值:

技术分享图片

 

删除最小值:

 技术分享图片

删除最大值:

技术分享图片

 

以上是关于二十 二分搜索树完整实现:查找删除操作的主要内容,如果未能解决你的问题,请参考以下文章

算法——二分搜索树

挖掘算法中的数据结构:二分搜索树(删除广度优先遍历顺序性)及 衍生算法问题

用二分搜索树(BST)实现平衡二叉树(AVL)

二叉搜索树

06-二分搜索树 BST

C++从入门到入土第二十一篇:二叉搜索树之AVL树