手动实现二叉搜索树【基本写法】(详细注释)

/**
 * 思路:
 *  1、对于二叉搜索树,没有索引的的概念
 *  2、因为二叉树的每个数值都可以看成一个节点,因此可以在内部维护一个Node节点类
 *  3、由于二叉树是从一个根节点开始然后一直分叉,所以需要定义一个全局变量root节点对象,用于存储根节点位置
 *  4、由于二叉树是通过比较值来判断放左边还是右边,所以需要一个比较器,
 *     并且要这个比较器可以由外部提供,使用组合添加一个比较器变量
 *
 * @author 长静有希
 * @date 2021-12-05 15:38
 */
public class BinarySearchTree<E> {
	
    // 元素的数量
    private int size;

    // 存储根节点
    private Node<E> root;

    // 比较器
    private Comparator<E> comparator;


    /**
     * 元素的数量
     * 思路:
     *  1、由于需要获取元素数量,所以可以定义一个全局变量size存储
     * @return: int
     **/
    public int size() {
        return size;
    }

    /**
     * 是否为空
     * 思路:
     *  1、由于全局变量size中记录了元素个数,所以当元素个数为0时,表示二叉树为空
     * @return: boolean
     **/
    public boolean isEmpty() {
        return size == 0;
    }

    /**
     * 清空所有元素
     * 思路:
     *  1、将root节点赋值为空
     *      理:因为当根节点都为空了,等于源头断掉了
     *  2、将size赋值为0
     *      理:size等于则表示没有一个元素
     * @return: void
     **/
    public void clear() {
        root = null;
        size = 0;
    }

    /**
     * 添加元素
     * 思路:
     *  1、 添加元素不能为空
     *      理:由于二叉树搜索树是需要进行比较值,然后在决定该元素存放的位置,所以添加的元素不能为空
     *  2、 抽取元素为空检查
     *      理:可能在多处用到,先抽取
     *  3、 特殊处理第一个元素
     *      理:当二叉树元素为0时,传入的第一个元素,需要将其做为根节点,便可结束第一次添加
     *  4、 添加比较器,通过比较器来判断传入元素值应该放左边还是右边
     *      理:
     *          1、处理当传入元素为对象的情况,需要该对象自定义比较规则
     *          2、该对象需要实现Comparable接口,并在compareTo方法中定义比较规则,
     *              传入俩个参数,e1: 当前节点值,e2: 添加的值,使用 e1 - e2 = number
     *              1、当 number = 0 表示俩个元素相等
     *                  处理:可不做处理,也可以覆盖
     *              2、当 number > 0 表示当前节点值大于添加元素值
     *                  处理:如果为叶子节点,放在当前节点左边
     *              3、当 number < 0 表示当前节点值小于添加元素值
     *                  处理:如果为叶子节点,放在当前节点右边
     * @return: void
     **/
    public void add(E element) {
        // 元素非空检查
        elementNotNullCheck(element);

        // 判断根节点是否为空
        if(root == null) {
            // 根节点的父节点是不存在的
            root = new Node<>(element, null);
            size++;
            return;
        }

        // 能来到这里,表示根节点已存在,那么获取根节点和父节点对象
        Node<E> node = root;
        Node<E> parentNode = root;

        // 用于记录最后一次比较的结果
        int cmp = 0;

        // 结束循环条件为获取到的节点不能为空,为空代表已经到达叶子节点
        while(node != null){
            // 父节点,用于后续创建节点
            parentNode = node;

            // 使用当前节点的值比较传入元素的值,依次判断元素存储位置
            cmp = compareTo(node.element,element);

            if(cmp < 0){
                // 说明添加的值比当前节点的值要大
                node = node.right;
            }else if(cmp > 0){
                // 说明添加的值比当前节点的值要小
                node = node.left;
            }else{ // 相等,最好是覆盖掉
                node.element = element;
                return;
            }
        }

        // 创建新节点
        Node<E> newNode = new Node<>(element, parentNode);

        // 根据cmp的值判断放在左边还是右边
        if(cmp < 0){
            parentNode.right = newNode;
        }else{
            parentNode.left = newNode;
        }
        // 元素个数加一
        size++;
    }

    // 删除元素
    public void remove(E element) {
    }

    // 是否包含某元素
    public boolean contains(E element) {
        return false;
    }

	/**
     * 内部类 -> 节点
     **/
    private static class Node<E> {
        E element;      // 元素
        Node<E> left;   // 左节点
        Node<E> right;  // 右节点
        Node<E> parent; // 父节点

        public Node(E element, Node<E> parent) {
            this.element = element;
            this.parent = parent;
        }

        /**
         * 判断当前节点是否是叶子节点
         * @return: boolean
         **/
        public boolean isLeaf() {
            // 当左右节点都为空,表示为叶子节点
            return left == null && right == null;
        }

        /**
         * 判断当前节点是否有俩个子节点
         * @return: boolean
         **/
        public boolean involveTowLeaf() {
            return left != null && right != null;
        }
    }

  /**
     * 节点元素比较
     * 思路:
     *  传入了比较器就按照传入的比较器来比较,
     *  没有传入比较器就认为实现了比较器接口,然后来进行比较
     * @return: int
     * @param e1 :节点的值
     * @param e2 :添加的值
     **/
    private int compareTo(E e1, E e2) {
        // 传入比较器则通过比较器比较
        if (comparator != null) {
            return comparator.compare(e1, e2);
        }
        // 没传比较器,元素内部必须自行实现了 Comparable 接口
        return ((Comparable<E>)e1).compareTo(e2);
    }

    /**
     * 检测传入的节点是否为空
     *  当元素为空时抛出异常,好处可自定义提示信息
     * @return: void
     **/
    private void elementNotNullCheck(E element) {
        if (element == null) {
            throw new IllegalArgumentException("元素不能为空!");
        }
    }

    /*********** 遍历写死的写法 *************/

    /**
     * 前序遍历二叉搜索树
     * 思路:
     *  1、使用递归
     * @return: void
     **/
    public void preorderTraversal() {
        // 调用递归方法,传入根节点
        preorderTraversal(root);
    }

    /**
     * 访问顺序:
     *  1、根节点
     *  2、左子树
     *  3、右子树
     * 结束递归条件:传入的node节点对象为null
     * @return: void
     **/
    private void preorderTraversal(Node<E> node) {
        if (node == null)  return;

        System.out.println(node.element);
        preorderTraversal(node.left);
        preorderTraversal(node.right);
    }

    /**
     * 中序遍历二叉搜索树
     * 思路:
     *  1、递归
     * @return: void
     **/
    public void inorderTraversal() {
        // 调用递归方法,传入根节点
        inorderTraversal(root);
    }

    /**
     * 访问顺序:
     *  1、左子树
     *  2、根节点
     *  3、右子树
     *
     *  以下为二叉搜索树的特点
     *  如果先访问左子树,那么遍历出的数据是从小到大排列
     *  如果先访问右子树,那么遍历出的数据的从大到小排列
     *
     * @return: void
     **/
    private void inorderTraversal(Node<E> node) {
        if (node == null)  return;

        preorderTraversal(node.left);
        System.out.println(node.element);
        preorderTraversal(node.right);
    }

    /**
     * 后序遍历
     * 思路:
     *  1、递归
     * @return: void
     **/
    public void postorderTraversal() {
        // 调用递归方法,传入根节点
        postorderTraversal(root);
    }

    /**
     * 访问顺序:
     *  1、左子树
     *  2、右子树
     *  3、根节点
     * 结束递归条件:传入的node节点对象为null
     * @return: void
     **/
    private void postorderTraversal(Node<E> node) {
        if (node == null)  return;

        postorderTraversal(node.left);
        postorderTraversal(node.right);
        System.out.println(node.element);
    }

    /**
     * 层序遍历
     * @return: void
     **/
    public void levelOrderTraversal() {
        levelOrderTraversal(root);
    }

    /**
     * 访问顺序:
     *  1、从上到下,从左到右依次访问每一个元素
     * 思路:
     * 1、首先判断根节点是否为空,如果为空那么就什么都不做
     * 2、创建一个队列,使用linkedList实现
     * 3、将根节点入队
     * 4、创建循环,如果队里不为空就一直循环
     * 	1、获取出队的节点对象
     * 	2、输出节点对象中的值
     * 	3、判断左或右节点是否不为空,如果不为空就入队
     * @return: void
     **/
    private void levelOrderTraversal(Node<E> node) {
        if (node == null) return;

        Queue<Node<E>> queue = new LinkedList<>();
        queue.offer(node);

        while (!queue.isEmpty()) {
            Node<E> poll = queue.poll();
            System.out.println(poll.element);

            if (poll.left != null) {
                queue.offer(poll.left);
            }
            if (poll.right != null) {
                queue.offer(poll.right);
            }
        }

    }

}

你可能感兴趣的:(数据结构手动实现,java,数据结构,开发语言)