0036数据结构之二分搜索树

------------二分搜索树-------------

AVL和红黑树都属于平衡二叉树。

二叉树和链表一样,也属于动态数据结构(不需要事先定义容量)

class Node{

E e;

Node left;

Node right;

}

二叉树具有递归结构:每个节点的左子树是二叉树,每个节点的右子树也是二叉树。

满二叉树定义:除了叶子节点,每个节点都有两个孩子。

只有一个节点也可以看成是二叉树,只要满足二叉树的定义

二分搜索树:本身是二叉树,同时必须满足两个条件:1)节点的值大于其左子树的所有节点的值;2)节点的值小于其右子树的所有节点的值。相当于它的每一颗子树也都是二分搜索树。

以上描述的二叉树或者二叉搜索树都不一定是平衡的,即只有左子树或者只有右子树也可能是二分搜索树。

如果二叉树中存放自定义数据,要定义好两个自定义数据之间是如何进行大小比较的,这样二分树中才能查找数据。

1)  二分搜索树中添加元素

2)  二分搜索树中搜索元素-前序遍历、中序遍历和后序遍历(均是递归调用)(前中后序遍历都属于深度优先遍历)

3)  使用栈实现非递归调用的前序遍历

4)  层序遍历(层序遍历属于广度优先遍历):不是用递归,是借助队列实现,广度优先遍历的意义是更快的找到问题的解

5)  二分搜索树删除节点

具体代码如下:

package tree;

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

/*
*
这是定义的二分搜索树,树中存放的数据必须明确声明如何比较大小
*
树中存放的数据E必须继承Comparable接口,这样才能保证E具有可比性
*
如下实现的二分搜索树是不包含重复元素的,也可尝试自行实现可包含重复元素的二分搜索树

* */
public class BST<E extends Comparable<E>> {
    //定义匿名内部类
   
private class Node{
        public E e;
        public Node left;
        public Node right;

        public Node(E e){
            this.e = e;
            left = null;
            right  = null;
        }
    }

    //定义两个成员变量rootsize
   
private Node root;
    private int size;

    //定义二分搜索树的构造方法
   
public BST(){
        root = null;
        size = 0;
    }

    public int getSize(){
        return size;
    }

    public boolean isEmpty(){
        return size == 0;
    }

    /*//向二分搜索树中增加新的元素e
    public void add(E e){
        if(root == null){
            root = new Node(e);
            size++;
        }else{
            add(root,e);
        }
    }

    //
如果e小于node中的e,递归左子树,如果e大于node中的e,递归右子树,直到比较的node是根节点,不需要断开两个节点直接的链接
    private void add(Node node,E e){
        //
终止条件
        if(e.compareTo(node.e) == 0){
            //
如果相等,不做处理,类的头部已经说明本二叉搜索树不包含重复数据
            return;
        }else if(e.compareTo(node.e) < 0 && node.left == null){
            //
如果小于node.e并且nodeleft节点已经是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){
            //
如果小于node.e并且nodeleft节点不为null
            add(node.left,e);
            //
最终终止条件一会被执行到,终止条件中进行了size++,所以此处不要再进行size++,否则就加多了
        }else{
            add(node.right,e);
        }
    }*/

    //
优化后的,向二分搜索中加新的元素饿
   
public void add(E e){
        root = add(root,e);
    }

    //优化后的,返回插入新节点后二分搜索树的根
   
private Node add(Node node,E e){
        //如果首次调用,相当于创建根节点
       
//如果不是首次调用,相当于是创建叶子节点,拼接在父节点下
       
//终结条件
       
if(node == null){
            size++;
            return new Node(e);
        }
        //如果传进来的node不为null,则将e添加到node的左子树中
       
if(e.compareTo(node.e) < 0){
            //如果node.leftnull,相当于将enewnode做为node.left
            //
如果nodeleft不为null,相当于将enewnode递归加入nodeleft对应的左子树中
           
//注意,左节点为null是做为左节点,不为null,是加入到左子树中,是左节点和左子树的区别
           
//最后一次调用add(node.left,e),一定是node.leftnull,这样才是终结条件
           
node.left  = add(node.left,e);
        }else if(e.compareTo(node.e) > 0){
            node.right = add(node.right,e);
        }
        //最终返回node
       
return node;
    }

    //前序遍历(先遍历根节点),用户使用
   
public void preOrder(){
        preOrder(root);
    }

    //私有的前序遍历,递归使用
   
private void preOrder(Node node){
        //终止条件
       
//整个链表为null,或者子节点为null
       
if(node == null){
            return;
        }
        //输出当前节点
       
System.out.println(node.e);
        //递归调用
       
//遍历左子树右子树
       
preOrder(node.left);
        preOrder(node.right);
    }

    @Override
    //前序遍历生成toString()的字符串
   
public String toString() {
        StringBuilder sb = new StringBuilder();
        generateBSTString(root,0,sb);
        return sb.toString();
    }
    //前序遍历,先访问该节点,再遍历左子树,再遍历右子树
   
private void generateBSTString(Node node,int depth,StringBuilder sb){
        //终止条件
       
if(node == null){
            return;
        }
        //中间这部分是逻辑处理
       
for(int i= 0;i<depth;i++){
            sb.append("--");
        }
        sb.append(node.e);
        sb.append("\n");
        //递归调用
        
generateBSTString(node.left,depth+1,sb);
        generateBSTString(node.right,depth+1,sb);
    }

    //中序遍历:先遍历左子树,再遍历该节点,再遍历右子树
   
//中序遍历的输出结果是从小到的
   
public void middleOrder(){
        middleOrder(root);
    }

    //私有的中序遍历,递归使用
   
private void middleOrder(Node node){
        //终止条件
       
//整个链表为null,或者子节点为null
       
if(node == null){
            return;
        }
        //递归调用
       
//遍历左子树
       
middleOrder(node.left);
        //输出当前节点
       
System.out.println(node.e);
        //遍历右子树
       
middleOrder(node.right);
    }

    //后续遍历:先访问左子树、再访问右子树、最后访问当前节点
   
//后续遍历的典型应用是为二分搜索树释放内存,因为必须处理完左右子节点,才能处理当前节点
   
//又或者是分支算法,都是因为需要先处理完两个子节点,再根据子节点的结果来处理当前节点
   
public void postOrder(){
        postOrder(root);
    }

    //私有的中序遍历,递归使用
   
private void postOrder(Node node){
        //终止条件
       
//整个链表为null,或者子节点为null
       
if(node == null){
            return;
        }
        //递归调用
       
//遍历左子树
       
postOrder(node.left);
        //遍历右子树
       
postOrder(node.right);
        //输出当前节点
       
System.out.println(node.e);
    }

    //使用栈以非递归的形式前序遍历
   
public void preOrderUserStack(){
        Stack<Node> stack = new Stack();
        stack.push(root);
        while(!stack.isEmpty()){
            Node node = stack.pop();
            System.out.println(node.e);
            //由于用的是栈,需要右子树先入栈,左子树后入栈,这样才能左子树先出栈
           
if(node.right != null){
                stack.push(node.right);
            }
            if(node.left != null){
                stack.push(node.left);
            }
        }
    }

    //层序遍历,借助队列:先进先出
   
public void levelOrder(){
        Queue<Node> queue = new LinkedList<>();
        queue.add(root);
        while(!queue.isEmpty()){
            Node current = queue.poll();
            System.out.println(current.e);
            if(current.left != null){
                queue.add(current.left);
            }
            if(current.right != null){
                queue.add(current.right);
            }
        }
    }

    //寻找二分搜索树中的最小元素
   
public E minimum(){
        if(size == 0){
            throw  new IllegalArgumentException("BST is empty.");
        }
        //最小元素
       
Node minumum = minumum(root);
        return minumum.e;
    }

    //私有的内部递归调用的的方法
   
private Node minumum(Node node){
        //终止条件
        
if(node.left == null){
            return  node;
        }
        return minumum(node.left);
    }

    //寻找二分搜索树中的最大元素
   
public E maximum(){
        if(size == 0){
            throw  new IllegalArgumentException("BST is empty.");
        }
        //最小元素
       
Node maximum = maximum(root);
        return maximum.e;
    }

    //私有的内部递归调用的的方法
   
private Node maximum(Node node){
        //终止条件
       
if(node.right == null){
            return  node;
        }
        return maximum(node.right);
    }

    //删除二分搜索树中的最小元素
   
public E removeMin(){
        E min = minimum();
        //删除最大值后,将该二分搜索树重新赋值给root
       
root = removeMin(root);
        return min;
    }

    //返回删除最小节点后的node
   
public Node removeMin(Node node){
        //终止条件
       
if(node.left == null){
            Node rightNode = node.right;
            node.right = null;
            size--;
            return rightNode;
        }
        //相当于如果node.left是最小的节点,
       
//使node.left = node.left.right
       
node.left = removeMin(node.left);
        return node;
    }

    //删除最大根节点
   
public E removeMax(){
        E e = maximum();
        root = removeMax(root);
        return e;
    }

    //删除最大元素
   
public Node removeMax(Node node){
        //终止条件
       
if(node.right == null){
            Node leftNode = node.left;
            node.left = null;
            size--;
            return leftNode;
        }
        //如果node.right是最大值,则删除该最大值
       
//并使node.right重新指向node.right.left
       
node.right = removeMax(node.right);
        return node;
    }

    /*
    *
删除二分搜索树中的任意元素
    * 1
、终止条件:e等于node.e,
    *      
如果node.left==null,返回node.right
    *      
如果node.right==null,返回node.left
    *      
如果都不为空,则返回右子树中最小节点做为根的新的二分搜索树;
    * 2
、如果不满足终止条件,则需要递归调用
    *  
如果e<node.e,则递归调用的返回值做为新的node.left;
    *  
如果e>node.e,则递归调用的返回值做为新的node.right;
    *  * */
   
public void removeElement(E e){
        root = removeElement(root,e);
    }

    private Node removeElement(Node node,E e){
        //1终止条件
       
if(e.compareTo(node.e) == 0){
            if(node.left == null){
                size--;
                return node.right;
            }else if(node.right == null){
                size--;
                return node.left;
            }else{
                Node rightNode = node.right;
                //获取右子树的最小值
               
Node min =  minumum(rightNode);
                //删除右子树的最小值,并返回最新的右子树
               
Node newRightNode = removeMin(rightNode);
                //min为根,创建新的二分搜索树
               
min.left = node.left;
                min.right = newRightNode;
                //此步不需要size--,因为removeMin的时候已经进行了一次size--
                //size--;
               
return min;
            }
            /*//如果右子树不为null
            if(node.right != null){
                Node rightNode = node.right;
                //
获取右子树的最小值
                Node min =  minumum(rightNode);
                //
删除右子树的最小值,并返回最新的右子树
                Node newRightNode = removeMin(rightNode);
                //
min为根,创建新的二分搜索树
                min.left = node.left;
                min.right = newRightNode;
                size--;
                return min;
            }else{
                //
如果右子树为null,则返回左子树
                size--;
                return node.left;
            }*/
       
}
        //2如果不满足终止条件
       
if(e.compareTo(node.e) < 0){
            //如果e<node.e,node.left指向递归调用新城的新的二分搜索树
           
if(node.left != null){
                node.left = removeElement(node.left,e);
            }
        }else{
            //如果e>node.e,node.right指向递归调用新城的新的二分搜索树
           
if(node.right != null){
                node.right = removeElement(node.right,e);
            }
        }
        return node;
    }

}

 

测试代码如下:

package tree;

import java.util.ArrayList;
import java.util.Random;

public class Main {
    public static void main(String[] args) {
        BST<Integer> bst = new BST<>();
        int[] arr = {5,3,6,8,4,2};
        for(int i=0;i<arr.length;i++){
            bst.add(arr[i]);
        }
        //前序遍历
       
bst.preOrder();
        //前序遍历的方式写的toString()
       
System.out.println(bst);
        //中序遍历,从小到达输出
       
bst.middleOrder();
        //后续遍历
       
bst.postOrder();

        //非递归的前序遍历
       
System.out.println("非递归的前序遍:");
        bst.preOrderUserStack();

        //层序遍历,使用队列实现
        
System.out.println("层序遍历:");
        bst.levelOrder();

        //测试removeMin()方法是否正确
       
testRemoveMin();

        //测试删除指定元素
       
System.out.println("测试删除指定元素:");
        bst.removeElement(2);
        bst.levelOrder();
        //System.out.println(bst.getSize());


   
}

    public static void testRemoveMin(){
        Random random = new Random();
        BST<Integer> bst = new BST<>();
        for(int i=0;i<1000;i++){
            bst.add(random.nextInt(10000));
        }
        ArrayList<Integer> arr = new ArrayList<>();
        while(!bst.isEmpty()){
            arr.add(bst.removeMin());
        }
        System.out.println(arr);
        for(int i=0;i<arr.size()-1;i++){
            if(arr.get(i) > arr.get(i+1)){
                throw new IllegalArgumentException("Error");
            }
        }
        System.out.println("removeMin is right.");
    }
}

 小总结:栈可用在前序遍历(深度优先遍历),队列可用在层序遍历(广度优先遍历)

如有理解不到之处,望指正!

猜你喜欢

转载自www.cnblogs.com/xiao1572662/p/12106874.html
今日推荐