Java教程

Java手写平衡二叉树--以及力扣相关题目的解法--提供外界对元素的操作接口-前中后层序遍历-获取树的高度-是否为完全二叉树的判断--获取指定节点的前驱和后继节点

本文主要是介绍Java手写平衡二叉树--以及力扣相关题目的解法--提供外界对元素的操作接口-前中后层序遍历-获取树的高度-是否为完全二叉树的判断--获取指定节点的前驱和后继节点,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

基本思路

package com.mao.dataStructure.tree;

import com.mao.dataStructure.tree.printer.BinaryTreeInfo;

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

/**
 * @ClassName: BinarySearchTree
 * @Description: 二叉搜索树
 * @Author 毛毛
 * @CreateDate 2021/07/21/周三 16:00
 * @Version: v1.0
 */
public class BinarySearchTree<E> { // E extends Comparable<E> 节点存放的元素必须实现 Compareable接口
    /**
     * @param size 节点的个数
     */
    private int size;
    /**
     * @param root 根节点
     */
    private Node<E> root;

    /**
     * 定制比较 如果需要定制比较,则传递这个参数
     */
    private Comparator<E> comparator;


    public BinarySearchTree() {
    }

    public BinarySearchTree(Comparator<E> comparator) {
        this.comparator = comparator;
    }

    // 元素的数量
    public int size() {
        return size;
    }

    // 是否为空
    public boolean isEmpty() {
        return size == 0;
    }

    // 清空所有元素
    public void clear() {
        root = null;
        size = 0;
    }

    // 添加元素
    public void add(E element) {
        // 检查元素是否为null
        elementNotNullCheck(element);
        // root为null则为第一次插入节点
        if (root == null) {
            // 根节点的父亲是null
            root = new Node<>(element, null);
            return;
        }
        // 不是第一次添加节点
        /*
         * 那么需要找到当前要插入节点的父亲节点
         * 使用迭代法
         * */
        Node<E> node = root;
        // 当前插入节点的父节点
        Node<E> parent = root; // 默认情况下根节点就是父节点
        int cmp = 0; // 记录需要插入的位置
        while (node != null) {
            cmp = compare(element, node.element);
            parent = node;
            if (cmp > 0) {
                // 当前插入的节点大于父节点
                node = node.right;
            } else if (cmp < 0) {
                // 当前插入的节点小于父节点
                node = node.left;
            } else {
                // 节点相同
                // 节点相同时:建议覆盖
                node.element = element;
                return; // 不需要插入了
            }
        }
        //    要插入的节点
        Node<E> curr = new Node<>(element, parent);
        //    查看到底插入到父节点的那个位置
        if (cmp > 0) {
            // 插入到父节点的右子节点
            parent.right = curr;
        } else if (cmp < 0) {
            // 插入到父节点的左子节点
            parent.left = curr;
        } //else{
        // 要插入的节点和父节点相同
        //}
        // 元素个数加一
        size++;
    }

    /**
     * 根据元素删除节点
     *
     * @param element
     */
    // 删除元素
    public void remove(E element) {
        remove(getNode(element));
    }

    /**
     * 移出指定的节点
     *
     * @param node
     */
    private void remove(Node<E> node) {
        // 节点不存在,直接返回
        if (node == null) return;
        // 链表长度减一
        size--;
        // 左右子树都存在,说明这个节点的度为2
        if (node.hasTowChildren()) {
            // 找到当前节点的前驱节点或者后继节点,取其中的值(element)来取代要删除节点的值
            // 这里使用找后继节点
            Node<E> successor = successor(node);
            // 用后继节点的值覆盖当前节点(度为2)的值
            node.element = successor.element;
            // 删除后继节点,我们让node节点指向后继节点successor
            node = successor;
            // 我们最后让node直接赋值为它的后继节点,这样node的度也变成了1或者0,和下面的处理方式一样了
        }

        // 删除node节点(node的度必然是1或者0)
        // 如果node现在是叶子节点,度为0 则左右子树都不存在 都为null
        // 如果node是度为1的节点,左子树存在,或者右子树存在,赋值不为空的子树
        Node<E> replacement = node.left != null ? node.left : node.right;

        // 进入if语句 说明node节点度为1
        if (replacement != null) {
            // 删除度为1的节点,就是让子节点取代自己

            // 更改parent
            replacement.parent = node.parent;

            if (node.parent == null) {
                // 要删除的度为1的节点刚好是根节点
                root = replacement;
            }
            // 更改parent的left和right
            // 判断要删除的node节点是node父节点的左子树还是右子树
            else if (node == node.parent.left) {
                node.parent.left = replacement;
            }
            // 下面的if条件可以省略。可以直接else
            else if (node == node.parent.right) {
                node.parent.right = replacement;
            }
        }
        // node是叶子节点  也是根节点
        else if (node.parent == null) {
            // 要删除的节点没有父节点,且这节点还是叶子节点,说明要删除的节点就是根节点
            root = null;
        }
        // node是叶子节点  不是根节点
        else {
            // 判断当前要删除的节点是父节点的左子树还是右子树
            if (node == node.parent.left) {
                node.parent.left = null;
            } else {
                node.parent.right = null;
            }
        }
    }

    /**
     * 根据元素获取所在的节点
     *
     * @param element
     * @return
     */
    private Node<E> getNode(E element) {
        Node<E> node = root;
        while (node != null) {
            int cmp = compare(element, node.element);
            if (cmp > 0) {
                // 要删除的节点在当前节点的右子树上
                node = node.right;
            } else if (cmp < 0) {
                // 要删除的节点在当前节点的左子树上
                node = node.left;
            } else {
                // 当前节点就是要删除的节点
                return node;
            }
        }
        // node = null 说明没找到,这个元素不存在
        return null;
    }

    // 是否包含某元素
    public boolean contains(E element) {
        return getNode(element) != null;
    }


    public void preorderTraversal() {
        preorderTraversal(root);
    }

    /**
     * 前序遍历 先访问根节点,然后左子节点,然后右子节点
     *
     * @param node 当前遍历树的根节点
     */
    private void preorderTraversal(Node<E> node) {
        if (node == null) return;
        // 递归方式遍历
        System.out.println(node.element);
        preorderTraversal(node.left);
        preorderTraversal(node.right);
        //    迭代方式遍历
        // while (node!=null){
        //     System.out.println(node.element);
        //
        // }
    }

    public void preorderTraversal(Visitor<E> visitor) {
        if (root == null || visitor == null) return;
        preorderTraversal(root, visitor);
    }

    private void preorderTraversal(Node<E> node, Visitor<E> visitor) {
        if (node == null) return;
        // 递归方式遍历
        visitor.visit(node.element);
        preorderTraversal(node.left);
        preorderTraversal(node.right);
        //    迭代方式遍历
        // while (node!=null){
        //     System.out.println(node.element);
        //
        // }
    }

    /**
     * 中序遍历
     */
    public void inorderTraversal() {
        inorderTraversal(root);
    }

    /**
     * 中序遍历
     *
     * @param node
     */
    private void inorderTraversal(Node<E> node) {
        if (node == null) return;
        inorderTraversal(node.left);
        System.out.println(node.element);
        inorderTraversal(node.right);
    }

    /**
     * 后序遍历
     */
    public Queue<E> postorderTraversal() {
        Queue<E> queue = new LinkedList<>();
        postorderTraversal(root, queue);
        return queue;
    }

    /**
     * 后序遍历
     *
     * @param node
     */
    private void postorderTraversal(Node<E> node, Queue<E> queue) {
        if (node == null) return;
        // postorderTraversal(node.left);
        // postorderTraversal(node.right);
        // System.out.println(node.element);
        Stack<Node<E>> stack = new Stack<>();
        Node<E> curr = root;
        while (curr != null || !stack.isEmpty()) {
            //    左子树入栈
            while (curr != null) {
                stack.push(curr);
                curr = curr.left;
            }
            //    获取当前栈顶元素
            Node<E> top = stack.peek();
            if (top.right == null) {
                // 没有右子树,则当前元素遍历到最后了,可以入队
                queue.add(stack.pop().element);
            } else {
                // 有右子树,则先保存当前结点的右子节点并把当前结点的右子节点置为null
                // (下次查看当前结点时就会知道右子节点已经遍历过,就可以将其加入结果集了),遍历当前结点右子树
                curr = top.right;
                top.right = null;
            }
        }
    }

    /**
     * 层序遍历 使用队列的思维
     *
     * @return
     */
    public Queue<E> LevelOrderTraversal() {
        if (root == null) return null;
        Queue<E> result = new LinkedList<>();
        Queue<Node<E>> queue = new LinkedList<>();
        // 根节点入队
        queue.offer(root);
        while (!queue.isEmpty()) {
            // 出队
            Node<E> node = queue.poll();
            result.offer(node.element);
            if (node.left != null) queue.offer(node.left);
            if (node.right != null) queue.offer(node.right);
        }
        return result;
    }

    /**
     * 按照指定的需要,对存储的数据进行操作
     *
     * @param visitor 具体的操作逻辑实现类
     */
    public void levelOrder(Visitor<E> visitor) {
        if (root == null || visitor == null) return;
        Queue<Node<E>> queue = new LinkedList<>();
        queue.offer(root);
        while (!queue.isEmpty()) {
            // 出队
            Node<E> node = queue.poll();
            // 执行用户需要的操作逻辑
            visitor.visit(node.element);
            if (node.left != null) queue.offer(node.left);
            if (node.right != null) queue.offer(node.right);
        }

    }

    /**
     * 获取当前二叉搜索树的高度
     *
     * @return
     */
    public int getTreeHeight() {
        return getNodeHeight(root);
    }

    public int getTreeHeight(boolean isIteration) {
        // 递归调用
        if (!isIteration) return getNodeHeight(root);
        // 迭代调用
        return getNodeHeightByIteration(root);

    }

    /**
     * 获取某个节点的高度  递归获取
     *
     * @return
     */
    private int getNodeHeight(Node<E> node) {
        // 当前节点为null 直接返回 0
        if (node == null) return 0;
        // 当前节点的高度为其左右子树中较高的加一
        return Math.max(getNodeHeight(node.left), getNodeHeight(node.right)) + 1;
    }

    /**
     * 获取某个节点的高度  迭代获取(层序遍历的思想)
     *
     * @return
     */
    private int getNodeHeightByIteration(Node<E> node) {
        // 当前节点为null 直接返回 0
        if (node == null) return 0;
        // 存储每层元素的个数 (默认根节点不为空,也就是一层,为空的情况已经考虑了)
        // 当这层的元素个数变为 0  的时候,这一层也就被访问完了
        int levelSize = 1;
        // 当前节点的高度为其左右子树中较高的加一
        int height = 0;
        Queue<Node<E>> queue = new LinkedList<>();
        queue.offer(root);

        while (!queue.isEmpty()) {
            Node<E> curr = queue.poll();
            levelSize--;
            if (curr.left != null) queue.offer(curr.left);
            if (curr.right != null) queue.offer(curr.right);
            // 如果当前层数的元素 levelSize=0,也就是当前层访问完了
            // 那也就会进入下一层,下一层的元素个数也就是当前队列的长度
            if (levelSize == 0) {
                // 进入下一层,高度加一
                height++;
                // 该层的元素个数也就是当前队列的长度
                levelSize = queue.size();
            }
        }
        return height;
    }

    /**
     * 判断当前平衡二叉树是否是完全二叉树
     * 什么是完全二叉树: 叶子节点只会出现最后 2 层,且最后 1 层的叶子结点都靠左对齐
     *
     * @return
     */
    public boolean isComplete() {
        // 空树 不是完全二叉树
        if (root == null) return false;
        Queue<Node<E>> queue = new LinkedList<>();
        queue.offer(root);
        // 是否要求当前节点开始都为叶子节点
        boolean leaf = false;
        while (!queue.isEmpty()) {
            Node<E> curr = queue.poll();
            // 如果当前节点需要是叶子节点,但是当前节点却不是叶子节点,则肯定不是完全二叉树
            if (leaf && !curr.isLeaf()) return false;
            // 节点的度为 2 则按照顺序入队  左右子节点均存在
            // if (curr.left != null && curr.right != null) {
            if (curr.hasTowChildren()) { // 左子节点存在 右子节点存在并不会入队
                queue.offer(curr.left);
                queue.offer(curr.right);
            }
            // 存在度为1的节点,则一定不为完全二叉树
            // 存在右子树,没有左子树,肯定不是完全二叉树
            else if (curr.left == null && curr.right != null) {
                return false;
            }
            // 存在左子树,不存在右子树 则该节点后面的所有节点,全都是叶子节点
            // if (curr.left != null && curr.right == null)
            // 当前节点 没有左子树和右子树,则当前节点以及后面的所有节点,都是叶子节点
            // if(curr.left==null && curr.right==null)

            // 上面两种情况进行了合并(总共就四种可能性)
            else {
                // 接下来的所有节点都必须是叶子节点
                leaf = true;
                // 如果当前节点的左子节点存在,则入队
                if (curr.left != null) {
                    queue.offer(curr.left);
                }
            }
        }
        return true;
    }

    /**
     * 判断是否为完全二叉树的代码整理(减少了重复判断)
     *
     * @return
     */
    public boolean isComplete2() {
        // 空树 不是完全二叉树
        if (root == null) return false;
        Queue<Node<E>> queue = new LinkedList<>();
        queue.offer(root);
        // 是否要求当前节点开始都为叶子节点
        boolean leaf = false;
        while (!queue.isEmpty()) {
            Node<E> curr = queue.poll();
            // 如果当前节点需要是叶子节点,但是当前节点却不是叶子节点,则肯定不是完全二叉树
            if (leaf && !curr.isLeaf()) return false;
            // 存在左子树 直接入队
            if (curr.left != null) {
                queue.offer(curr.left);
            } else if (curr.right != null) { // curr.left == null 了
                // 左子树为空 右子树不为空,不可能是完全二叉树
                return false;
            }
            // 存在右子树 直接入队
            if (curr.right != null) {
                queue.offer(curr.right);
            } else {
                // 来到这里说明 右子节点为null 左子节点存在或者不存在
                // 则要求当前节点开始的后面的所有节点都为叶子节点
                leaf = true;
            }

        }
        return true;
    }

    /**
     * 获取当前节点的前驱节点
     * 前驱节点: 中序遍历时的当前节点的前一个节点
     * 二叉搜索树中,前驱节点就是前一个比它小的节点
     *
     * @param node
     * @return
     */
    private Node<E> predecessor(Node<E> node) {
        // 空树 直接返回
        if (node == null) return null;
        // 有左子树,前驱节点肯定在左子树上的最右的那个右子树
        Node<E> p = node.left;
        if (p != null) { // left.right.right....
            while (p.right != null) {
                p = p.right;
            }
            // p.right 为null
            return p;
        }
        // 左子树为null,则从父节点,祖父节点。。。中寻找前驱节点
        // 如果存在父节点,且当前节点是父节点的左子节点,则一直循环
        while (node.parent != null && node == node.parent.left) {
            node = node.parent;
        }
        // 来到这里,则不存在父节点 或者当前节点不是父节点的左子节点(也就是右子节点)
        // node.parent == null  || node != node.parent.left (node == node.parent.right)
        return node.parent;
    }

    /**
     * 找到当前节点的后继节点
     *
     * @param node
     * @return
     */
    private Node<E> successor(Node<E> node) {
        if (node == null) return null;
        // 右子树存在,则后继节点就是右子树的最左的那个左子节点
        // right.left.left.left...
        if (node.right != null) {
            Node<E> curr = node.right;
            while (curr.left != null) {
                curr = curr.left;
            }
            // 左子节点为null,直接返回当前节点,就是后继节点了
            return curr;
        }
        // 右子树不存在,找其父节点,如果父节点存在且当前节点是父节点的左子树
        // 则该父节点就是当前节点的后继节点
        while (node.parent != null && node == node.parent.right) {
            node = node.parent;
        }
        return node.parent;
    }


    /**
     * 参数不能为null的检查
     *
     * @param element
     */
    private void elementNotNullCheck(E element) {
        if (element == null) throw new IllegalArgumentException("参数不能为null element must not be null");
    }

    /**
     * 判断节点大小关系的方法
     *
     * @param ele1
     * @param ele2
     * @return 返回值为0 一样大 返回值小于 0 则 ele1 < ele2 返回值大于0 ele1>ele2
     */
    private int compare(E ele1, E ele2) {
        // 有比较器,则优先使用比较器的逻辑
        if (comparator != null) {
            return comparator.compare(ele1, ele2);
        }
        // 如果E这个类实现了 Comparable 接口,则调用实现类的具体比较逻辑
        // 强制转换为这个接口的实现类

        return ((Comparable<E>) ele1).compareTo(ele2);
    }


    /**
     * 访问器接口 提供一个外界对树上节点里存储的数据的操作的接口
     */
    public static interface Visitor<E> {
        void visit(E element);
    }


    /**
     * 内部类 树上的节点
     *
     * @param <E>
     */
    private static class Node<E> {
        // 节点存储的数据
        private E element;
        //    左子节点
        private Node<E> left;
        //    右子节点
        private Node<E> right;
        //    父节点
        private Node<E> parent;

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

        // 默认新节点的左子节点和右子节点都不存在
        public Node(E element, Node<E> parent) {
            this.element = element;
            this.parent = parent;
        }

        public Node() {
        }

        /**
         * 当前节点是否是叶子节点
         *
         * @return
         */
        private boolean isLeaf() {
            return left == null && right == null;
        }

        /**
         * 当前节点是否有两个子节点
         *
         * @return
         */
        private boolean hasTowChildren() {
            return left != null && right != null;
        }
    }

}

这篇关于Java手写平衡二叉树--以及力扣相关题目的解法--提供外界对元素的操作接口-前中后层序遍历-获取树的高度-是否为完全二叉树的判断--获取指定节点的前驱和后继节点的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!