Java实现二分搜索树

1.什么是二叉树

在计算机科学中,二叉树是每个结点最多有两个子树的树结构。通常子树被称作“左子树”(left subtree)和“右子树”(right subtree)。



2.二叉树的性质

(1)二叉树具有唯一根结点

(2)二叉树中每个结点多只有2个孩子

(3)二叉树每个结点最多只有一个父节点

(4)二叉树具有天然递归结构,表现为:

       ①每个节点的左子树(若存在),也是二叉树

       ②每个结点的右子树(若存在),也是二叉树

(5) 在非空二叉树中,第i层的结点总数不超过 (2^i-1) , i>=1;

(6)深度为h的二叉树最多有(2^h) - 1  个结点(h>=1),最少有h个结点;

(7)对于任意一棵二叉树,如果其叶结点数为N0,而度数为2的结点总数为N2,则N0=N2+1;

(8) 具有N个结点的完全二叉树的深度为[log2N]  + 1(注:[ ]表示向下取整)


3.二叉树的代码结构(Java实现)

class Node{
  E e;//表示二叉树结点元素的值
  Node left;//表示左子树
  Node right;//表示右子树
}

4.二叉树的遍历(递归与非递归)-Java语言实现

递归方式:https://blog.csdn.net/hoji_james/article/details/80601375

非递归方式:https://blog.csdn.net/hoji_james/article/details/80601720


5.二叉树的扩展-二分搜索树Binary Search Tree(BST)

二分搜索树是一种特殊的二叉树,它的特殊之处在于:它的每个结点的值大于其左子树(若存在)的所有结点的值,而小于其右子树(若存在)的所有结点的值。

它的代码实现如下(Java语言实现)(注意:这里考虑的是整棵树中无重复元素的情况)

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

public class BST<E extends Comparable<E>>  {//类型E需要具有可比较性

    //私有内部类(二分搜索树的结点具体是什么样子,对用户是屏蔽的,所在在这里为私有内部类)
    private class Node{//结点类
        public E e;//存放的元素
        public Node left;//左孩子
        public Node right;//右孩子

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

    public Node root;//根结点
    private int size;//当前二分搜索数存储了多少个元素

    public BST(){
        root = null;
        size = 0;
    }
	
	//获得二分搜索树含有的元素个数
    public int size(){
        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
    //返回插入新结点后二分搜索树的根
    private void add(Node node, E e){
		
        if(e.equals(node.e)){
            //相当于要插入的元素在二分搜索树中已经有了
            return;
        }else if(e.compareTo(node.e) < 0 && node.left == null){
            //左子树为空时直接把e插入node的左子树
            node.left = new Node(e);
            size ++;
            return;
        }else if(e.compareTo(node.e) > 0 && node.right == null){
			//右子树为空时直接把e插入node的右子树
            node.right = new Node(e);
            size ++;
            return;
        }
		
		//若左子树或右子树不为空则需要递归调用插入元素的过程
        if(e.compareTo(node.e) < 0){
            add(node.left, e);
        }else{ //e.compareTo(node.e) > 0
            add(node.right, e);
        }
    }

    //对于查询元素来说,我们只需要不停地看每一个node里面存的元素就好了
    //看二分搜索树中是否包含元素e
    public boolean contains(E 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 void preOrder(){
        preOrder(root);
    }

    //递归前序遍历
    //前序遍历以node为根的二分搜索树,递归算法
    private void preOrder(Node node){

        if(node == null){
            return;
        }

        System.out.println(node.e);//先遍历根节点
        preOrder(node.left);//然后遍历左子树(递归)
        preOrder(node.right);//最后遍历右子树(递归)


    }

    //二分搜索树的非递归前序遍历
    //借助栈,栈用于帮助我们记录下面需要访问的结点
    public void preOrderNR(){

        Stack<Node> stack = new Stack<>();

        //因为根结点是我们第一个访问到的结点,所以首先将根结点压栈
        stack.push(root);//将根结点压栈
        while(stack.isEmpty() == false){
            //不为空的时候,则栈顶记录了下面要访问哪个结点,相应地我们就要去访问这个结点
            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);
            }
        }
    }

    @Override
    public String toString(){
        StringBuilder res = new StringBuilder();
        generateBSTString(root, 0, res);//根结点,深度
        return res.toString();
    }

    //生成以node为根结点,深度为depth的描述二叉树的字符串
    private void generateBSTString(Node node, int depth, StringBuilder res) {

        if(node == null){
            res.append(generateDepthString(depth) + "null\n");
            return;
        }

        res.append(generateDepthString(depth) + node.e + "\n");
        generateBSTString(node.left,depth + 1, res);
        generateBSTString(node.right, depth + 1,res);
    }

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

    //二分搜索树的中序遍历
    public void inOrder(){
        inOrder(root);
    }

    //中序遍历以node为根的二分搜索树,递归算法
    private void inOrder(Node node) {

        if(node == null){
            return;
        }

        inOrder(node.left);//先遍历左子树
        System.out.println(node.e);//然后遍历根节点
        inOrder(node.right);//最后遍历右子树
    }

    //二分搜索树的后续遍历
    public void postOrder(){
        postOrder(root);
    }

    //后续遍历以node为根的二分搜索树,递归算法
    private void postOrder(Node node){

        if(node == null){
            return;
        }

        inOrder(node.left);//先遍历左子树
        inOrder(node.right);//然后遍历右子树
        System.out.println(node.e);//最后遍历根结点
    }

    //二分搜索树的层序遍历(广度优先遍历)
    //借助于队列这种数据结构
    public void levelOrder(){

        Queue<Node> queue = new LinkedList<>();
        queue.add(root);//根结点添加进队列

        while(queue.isEmpty() == false){
            Node cur = queue.remove();//当前遍历的结点
            System.out.println(cur.e);

            if(cur.left != null) {
                queue.add(cur.left);
            }
            if(cur.right != null) {
                queue.add(cur.right);
            }
        }
    }

    //寻找二分搜索树的最小元素
    public E miniNum(){
        if(size == 0){
            throw new IllegalArgumentException("树为空");
        }

        return miniNum(root).e;
    }

    //返回以node为根的二分搜索树的最小键值所在的节点
    private Node miniNum(Node node){
        if(node.left == null){//向左走再也走不动了
            return node;
        }

        return miniNum(node.left);
    }

    //寻找二分搜索树的最大元素
    public E maxNum(){
        if(size == 0){
            throw new IllegalArgumentException("树为空");
        }

        return maxNum(root).e;
    }

    //返回以node为根的二分搜索树的最大键值所在的节点
    private Node maxNum(Node node){
        if(node.right == null){//向右再也走不动了
            return node;
        }

        return maxNum(node.right);
    }

    //从二分搜索树中删除最小值所在结点,返回最小值
    public E removeMin(){
        E ret = miniNum();

        root = removeMin(root);//从根结点开始,尝试删除最小值所在的结点

        return ret;
    }

    //删除掉以node为根的二分搜索树中的最小结点
    //返回删除结点后新的二分搜索树的根
    private Node removeMin(Node node){

        //递归到底(叶子结点),删除最小元素
        if(node.left == null){//不能再向左走了,我们要删的就是这个结点
            Node rightNode = node.right;//保存当前结点的右子树
            node.right = null;
            size --;//这一步不要忘了
            return rightNode;
        }

        //未递归到底(非叶子结点),删除左子树中的最小元素
         node.left = removeMin(node.left);//这样才能真正改变整个二分搜索树的结构
         return node;
    }

    //从二分搜索树中删除最大值所在结点,返回最大值
    public E removeMax(){
        E ret = maxNum();

        root = removeMax(root);

        return ret;
    }

    //删除以node为根的二分搜索树中的最大结点
    //返回删除结点后新的二分搜索树的根
    private Node removeMax(Node node){

        if(node.right == null){//叶子结点
            Node leftNode = node.left;//保存当前结点的左子树
            node.left = null;
            size --;//不要忘记维护size
            return leftNode;
        }

        node.right = removeMax(node.right);
        return node;
    }

    //从二分搜索树中删除元素为e的结点
    public void remove(E e){
        root = remove(root,e);//将删除元素后的二叉树的根节点
    }

    //删除以node为根的二分搜索树中值为e的结点,递归算法
    //返回删除结点后新的二分搜索树的根
    private Node remove(Node node,E e){

        if(node == null){
            return null;
        }

        if(e.compareTo(node.e) < 0){//e小于当前node的e
            //要去node的左子树找到待删除的元素
            node.left = remove(node.left, e);
            return node;
        }else if(e.compareTo(node.e) > 0){
            //要删除的元素e比当前node上的元素e要大
            //到当前node的右子树中尝试将e删除
            node.right = remove(node.right, e);
            return node;
        }else{
            //node.e == e
            //此时要删除node这个结点

            //待删除结点左子树为空的情况
            if(node.left == null){
                Node rightNode = node.right;//保存一下node的右子树
                node.right = null;//将node和这棵树断开关系
                size --;
                return rightNode;//返回右子树的根节点
            }
            //待删除结点右子树为空的情况
            if(node.right == null){
                Node leftNode = node.left;//保存当前node的左孩子
                node.left = null;//把node和二叉树断掉关系
                size --;
                return leftNode;
            }

            //待删除结点左右子树均不为空
            //思路:找到比待删除结点大的最小结点,即待删除结点右子树的最小结点
            //用这个结点顶替待删除结点的位置
            Node successor = miniNum(node.right);
            successor.right = removeMin(node.right);
            size ++;//?? 6-12

            successor.left = node.left;

            node.left = node.right = null;//让node结点与二分搜索树脱离关系
            size --;//?? 6-12

            return successor;
        }
    }

}


测试类:

public class Main {

    public static void main(String[] args) {
        BST<Integer> bst = new BST<>();
        int[] nums = {5,3,4,6,8,4,2};

        for(int num : nums){
            //每次将nums中的一个数取出来,插入到bst中
            bst.add(num);
        }

            ////////////////////
            //      5         //
            //     / \        //
            //    3    6      //
            //   / \    \     //
            //  2   4    8    //
            ///////////////////
        //测试前序遍历
        bst.preOrder();
        /*
            遍历结果:
            5
            3
            2
            4
            6
            8
         */

        System.out.println();
        System.out.println(bst);
        /*
         *  5
            --3
            ----2
            ------null
            ------null
            ----4
            ------null
            ------null
            --6
            ----null
            ----8
            ------null
            ------null
         */


        //测试中序遍历
        bst.inOrder();
        System.out.println();
        /*
         * 遍历结果
         *   2
             3
             4
             5
             6
             8
            正好是排好序的
         */


        //测试后序遍历
        bst.postOrder();
        System.out.println();
        /*
            遍历结果:
         *  2
            3
            4
            6
            8
            5
         */



        //测试非递归方式下的前序遍历
        bst.preOrderNR();
        System.out.println();
        /*
                遍历结果:
                5
                3
                2
                4
                6
                8
        */


        //测试二分搜索树的层序遍历
        bst.levelOrder();
        System.out.println();
        /*
            遍历结果:
            5
            3
            6
            2
            4
            8
         */
    }
}


猜你喜欢

转载自blog.csdn.net/hoji_James/article/details/80656393
今日推荐