二叉树算法 JAVA

二叉算法通常指的是应用于二叉树这一数据结构的各种操作与算法。二叉树是一种特殊的树形结构,其中每个节点最多有两个子节点,分别称为左子节点和右子节点。二叉树在计算机科学中有广泛应用,如二叉查找树(Binary Search Tree, BST)、平衡二叉搜索树(如AVL树)、二叉堆(包括最大堆和最小堆)、二叉表达式树等。

节点树
先定义一个类用于放置节点信息树,该类就只包含三个元素val:根节点;left:左节点;rigth:右节点;其中左右节点下方又是相同的。

/**
 * 二叉树节点
 */
public class TreeNode {
    int val;
    TreeNode left;
    TreeNode right;

    TreeNode() {
    }

    TreeNode(int val) {
        this.val = val;
    }

    TreeNode(int val, TreeNode left, TreeNode right) {
        this.val = val;
        this.left = left;
        this.right = right;
    }

    @Override
    public String toString() {
        return "TreeNode{" +
                "val=" + val +
                ", left=" + left +
                ", right=" + right +
                '}';
    }
}

一、二叉树遍历

二叉树遍历:
前序遍历(根-左-右):首先访问根节点,然后递归地访问左子树,最后访问右子树。
中序遍历(左-根-右):先递归地访问左子树,接着访问根节点,最后访问右子树。对于二叉搜索树(BST),中序遍历结果为有序序列。
后序遍历(左-右-根):先递归地访问左子树和右子树,最后访问根节点。这种遍历方式常用于计算一个表达式树的值或释放树中的资源。

给一个数据模型,如下:

TreeNode treeNode = new TreeNode(38,
                new TreeNode(13, 
                        new TreeNode(11),
                        new TreeNode(35, 
                                    new TreeNode(32), 
                                    new TreeNode(37)
                        )
                ),
                new TreeNode(45, 
                        new TreeNode(43), 
                        new TreeNode(53)
                )
);

在这里插入图片描述

1、前序遍历(根-左-右)

前序遍历(根-左-右):首先访问根节点,然后递归地访问左子树,最后访问右子树。

/**
 * 前序遍历(根-左-右):首先访问根节点,然后递归地访问左子树,最后访问右子树。
 *
 * @param root 数据
 */
private void preOrderHelper(TreeNode root, List<Integer> list) {
    if (root != null) {
        list.add(root.val);
        preOrderHelper(root.left, list);
        preOrderHelper(root.right, list);
    }
}

2、中序遍历(左-根-右)

中序遍历(左-根-右):先递归地访问左子树,接着访问根节点,最后访问右子树。对于二叉搜索树(BST),中序遍历结果为有序序列。

/**
 * 中序遍历(左-根-右):先递归地访问左子树,接着访问根节点,最后访问右子树。对于二叉搜索树(BST),
 * 中序遍历结果为有序序列。
 *
 * @param root 数据
 */
private void inOrderHelper(TreeNode root, List<Integer> list) {
    if (root != null) {
        inOrderHelper(root.left, list);
        list.add(root.val);
        inOrderHelper(root.right, list);
    }
}

3、后序遍历(左-右-根)

后序遍历(左-右-根):先递归地访问左子树和右子树,最后访问根节点。这种遍历方式常用于计算一个表达式树的值或释放树中的资源。

/**
 * 后序遍历(左-右-根):先递归地访问左子树和右子树,最后访问根节点。这种遍历方式常用于计算一个表达式树的值或释放树中的资源。
 *
 * @param root 数据
 */
private void postOrderHelper(TreeNode root, List<Integer> list) {
    if (root != null) {
        postOrderHelper(root.left, list);
        postOrderHelper(root.right, list);
        list.add(root.val);
    }
}

总结:二叉树遍历的顺序:前序遍历、中序遍历、后序遍历。总体思路是一致的,只是优先查找的顺序不同,前序就先从根节点,中序就先从左节点,根次之,后序就先从叶节点左右再到根节点。

二、二叉查找树(BST)操作

二叉查找树(BST)操作:
二叉查找树(BST,Binary Search Tree)是一种特殊的二叉树数据结构,其每个节点包含一个值以及指向左子节点和右子节点的引用。BST具有以下性质:
节点值特性:对于任意节点,其左子树中所有节点的值均小于该节点的值,而右子树中所有节点的值均大于该节点的值。
基于这些特性,二叉查找树提供了高效的查找、插入和删除操作。
插入:将新节点按其值与现有节点的大小关系插入到适当位置,保持左子节点小于根节点,右子节点大于根节点的特性。
删除:删除一个节点可能涉及替换其值、将其子节点提升为父节点,或者合并两个子树。需要处理三种情况:删除无子节点的节点、删除有一个子节点的节点,以及删除有两个子节点的节点。
查找:从根节点开始,按照值的大小关系逐层向下比较,直到找到目标节点或遇到空节点。

1、插入节点

将一整个节点拆分成多个小节点,如新插入的数值小于根节点,则插入左节点,又再次执行当前操作,直到节点为空直接赋值,大于根节点类似。

/**
 * 插入节点
 *
 * @param root 二叉树
 * @param val  插入数值
 * @return
 */
private TreeNode insertRecursive(TreeNode root, int val) {
    if (root == null) {
        return new TreeNode(val);
    } else if (val < root.val) {
        root.left = insertRecursive(root.left, val);
    } else if (val > root.val) {
        root.right = insertRecursive(root.right, val);
    }
    return root;
}

2、查找节点

查找节点
和插入操作类似:查找该二叉树是否为空或者是否找到目标值,如果找到就返回该节点。
没有找到就接着叶节点,大于往右边找,小于往左边找。

private TreeNode searchRecursive(TreeNode root, int val) {
    if (root == null || root.val == val) {
        return root;
    } else if (val < root.val) {
        return searchRecursive(root.left, val);
    } else {
        return searchRecursive(root.right, val);
    }
}

// 数据源
TreeNode treeNode = new TreeNode(38,
        new TreeNode(13, new TreeNode(11),
                new TreeNode(35, new TreeNode(32), new TreeNode(37))),
        new TreeNode(45, new TreeNode(43), new TreeNode(53)));

// 结果
TreeNode resultNode = binarySearchTree.searchTree(treeNode, 38);
if (resultNode == null) {
    System.out.println("没找到");
} else {
    System.out.println("找到了");
}

3、删除节点

删除一个节点可能涉及替换其值、将其子节点提升为父节点,或者合并两个子树。
需要处理三种情况:

  • 删除无子节点的节点、删除有一个子节点的节点,以及删除有两个子节点的节点。
  • 删除操作相对插入和查找来说更为复杂,因为它涉及到节点的移除以及可能的结构调整以保持BST的性质。

删除操作步骤:
1 查找待删除节点:首先使用查找算法找到要删除的节点(目标节点)。若未找到,则无需执行删除操作。
2 处理三种不同情况:

  • 目标节点没有子节点(叶节点):直接删除目标节点。
  • 目标节点只有一个子节点:将目标节点替换为其唯一子节点。
  • 目标节点有两个子节点:找到目标节点的后继节点(右子树中最小节点或左子树中最大节点),将其值复制到目标节点,然后删除后继节点。这样做的目的是确保删除操作仅涉及至多一个子节点的情况。
    3 调整结构:删除目标节点(或其后继节点)后,如果其有子节点,需要将其子节点连接到父节点或更新根节点以保持BST性质。
private TreeNode delete(TreeNode root, int val) {
    // 查找,找到才删除,没找到就不动
    TreeNode treeNode = this.searchRecursive(root, val);
    if (treeNode == null) {
        return null;
    }
    return deleteRecursive(root, val);
}

private TreeNode deleteRecursive(TreeNode root, int val) {
    if (root == null) {
        return null;
    }

    if (val < root.val) {
        root.left = deleteRecursive(root.left, val);
    } else if (val > root.val) {
        root.right = deleteRecursive(root.right, val);
    } else {
        // 找到目标节点
        // 1、目标节点没有子
        if (root.left == null && root.right == null) {
            return null;
        } else if (root.left != null && root.right == null) {
            // 2、目标节点只有一个子
            return root.left;
        } else if (root.left == null) {
            return root.right;
        }

        TreeNode minNode = findMin(root.right);
        root.val = minNode.val;
        root.right = deleteRecursive(root.right, minNode.val);
    }
    return root;
}

private TreeNode findMin(TreeNode node) {
    while (node.left != null) {
        node = node.left;
    }
    return node;
}

图解删除的两种情况:(归结起来就是两种情况)
1、目标节点只有一个子节点或者没有节点:将子节点替换到当前节点或者直接删除;
在这里插入图片描述在这里插入图片描述

2、目标节点又左右节点,找到右侧最小节点替换到当前节点,删除右侧最小节点;
在这里插入图片描述

源文件:

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

/**
 * 二叉算法通常指的是应用于二叉树这一数据结构的各种操作与算法。二
 * 叉树是一种特殊的树形结构,其中每个节点最多有两个子节点,分别称为左子节点和右子节点。
 * 二叉树在计算机科学中有广泛应用,如二叉查找树(Binary Search Tree, BST)、
 * 平衡二叉搜索树(如AVL树)、二叉堆(包括最大堆和最小堆)、二叉表达式树等。
 * <p>
 * 1、二叉树遍历:
 * <p>
 * 前序遍历(根-左-右):首先访问根节点,然后递归地访问左子树,最后访问右子树。
 * 中序遍历(左-根-右):先递归地访问左子树,接着访问根节点,最后访问右子树。对于二叉搜索树(BST),中序遍历结果为有序序列。
 * 后序遍历(左-右-根):先递归地访问左子树和右子树,最后访问根节点。这种遍历方式常用于计算一个表达式树的值或释放树中的资源。
 * <p>
 * 2、二叉查找树(BST)操作:
 * <p>
 * 插入:将新节点按其值与现有节点的大小关系插入到适当位置,保持左子节点小于根节点,右子节点大于根节点的特性。
 * 删除:删除一个节点可能涉及替换其值、将其子节点提升为父节点,或者合并两个子树。需要处理三种情况:删除无子节点的节点、删除有一个子节点的节点,以及删除有两个子节点的节点。
 * 查找:从根节点开始,按照值的大小关系逐层向下比较,直到找到目标节点或遇到空节点。
 * <p>
 * 3、平衡二叉搜索树(如AVL树)的调整:
 * <p>
 * 旋转操作:当插入或删除导致树失去平衡时,通过旋转操作(单旋、双旋)重新调整树的结构以恢复平衡。单旋包括左旋和右旋,用于处理局部失衡;双旋通常由两次单旋组合而成,用于处理更复杂的失衡情况。
 * <p>
 * 4、二叉堆操作:
 * <p>
 * 构建堆:给定一个无序数组,通过从底部向上调整节点(下沉操作)构建一个满足堆性质的完全二叉树。
 * 堆排序:基于堆的插入和删除操作实现的一种排序算法,首先构建堆,然后反复删除堆顶元素(最大堆则得到降序序列,最小堆则得到升序序列)并重新调整堆。
 * 插入:在堆末尾添加新元素后,可能破坏堆性质,需要自下而上进行调整(上升操作)。
 * 删除(删除堆顶元素):移除堆顶元素后,将堆尾元素移动到堆顶,再自顶向下调整堆(下沉操作)。
 * <p>
 * 5、其他二叉树算法:
 * <p>
 * 层次遍历:按照树的层级顺序访问节点,通常使用队列辅助实现。
 * 深度优先搜索(DFS)与广度优先搜索(BFS):虽然不是针对二叉树特有的,但它们常用于解决二叉树问题,如节点计数、路径查找、节点间最短距离等。
 * 二叉树剪枝:根据特定条件去除不满足要求的子树,如题目中提到的剪除所有节点值全为0的子树。
 *
 * @author hexionly
 * @datetime 2024/04/17 11:13:35
 */
public class BinaryTree {

    public static void main(String[] args) {
        // 二叉遍历
        TreeNode treeNode = new TreeNode(38,
                new TreeNode(13, new TreeNode(11),
                        new TreeNode(35, new TreeNode(32), new TreeNode(37))),
                new TreeNode(45, new TreeNode(43), new TreeNode(53)));
        // BinaryTreeTraversal binaryTreeTraversal = new BinaryTreeTraversal();
        // binaryTreeTraversal.preOrder(treeNode);

        // 二叉查找树操作
        BinarySearchTree binarySearchTree = new BinarySearchTree();
        // Random rand = new Random();
        // TreeNode resultNode = null;
        // for (int i = 0; i < 10; i++) {
        //     int nextInt = rand.nextInt(100);
        //     resultNode = binarySearchTree.searchTree(resultNode, nextInt);
        // }
        // System.out.println(resultNode);

        // TreeNode resultNode = binarySearchTree.searchTree(treeNode, 38);
        // if (resultNode == null) {
        //     System.out.println("没找到");
        // } else {
        //     System.out.println("找到了");
        // }

        binarySearchTree.deleteRecursive(treeNode, 13);
        System.out.println(treeNode);
    }

    /**
     * 二叉树遍历:
     * <p>
     * 前序遍历(根-左-右):首先访问根节点,然后递归地访问左子树,最后访问右子树。
     * 中序遍历(左-根-右):先递归地访问左子树,接着访问根节点,最后访问右子树。对于二叉搜索树(BST),中序遍历结果为有序序列。
     * 后序遍历(左-右-根):先递归地访问左子树和右子树,最后访问根节点。这种遍历方式常用于计算一个表达式树的值或释放树中的资源。
     * </p>
     * <p>
     * 总结:二叉树遍历的顺序:前序遍历、中序遍历、后序遍历。总体思路是一致的,只是优先查找的顺序不同。
     * </p>
     */
    public static class BinaryTreeTraversal {

        private void preOrder(TreeNode root) {
            List<Integer> list = new ArrayList<>();
            // 前序遍历
            preOrderHelper(root, list);
            // 中序遍历
            // inOrderHelper(root, list);
            // 后序遍历
            // postOrderHelper(root, list);
            list.forEach(System.out::println);
        }

        /**
         * 前序遍历(根-左-右):首先访问根节点,然后递归地访问左子树,最后访问右子树。
         *
         * @param root 数据
         */
        private void preOrderHelper(TreeNode root, List<Integer> list) {
            if (root != null) {
                list.add(root.val);
                preOrderHelper(root.left, list);
                preOrderHelper(root.right, list);
            }
        }

        /**
         * 中序遍历(左-根-右):先递归地访问左子树,接着访问根节点,最后访问右子树。对于二叉搜索树(BST),
         * 中序遍历结果为有序序列。
         *
         * @param root 数据
         */
        private void inOrderHelper(TreeNode root, List<Integer> list) {
            if (root != null) {
                inOrderHelper(root.left, list);
                list.add(root.val);
                inOrderHelper(root.right, list);
            }
        }

        /**
         * 后序遍历(左-右-根):先递归地访问左子树和右子树,最后访问根节点。这种遍历方式常用于计算一个表达式树的值或释放树中的资源。
         *
         * @param root 数据
         */
        private void postOrderHelper(TreeNode root, List<Integer> list) {
            if (root != null) {
                postOrderHelper(root.left, list);
                postOrderHelper(root.right, list);
                list.add(root.val);
            }
        }
    }

    /**
     * 二叉查找树(BST)操作:
     * <p>
     * 插入:将新节点按其值与现有节点的大小关系插入到适当位置,保持左子节点小于根节点,右子节点大于根节点的特性。
     * 删除:删除一个节点可能涉及替换其值、将其子节点提升为父节点,或者合并两个子树。需要处理三种情况:删除无子节点的节点、删除有一个子节点的节点,以及删除有两个子节点的节点。
     * 查找:从根节点开始,按照值的大小关系逐层向下比较,直到找到目标节点或遇到空节点。
     *
     * <p>
     * 二叉查找树(BST,Binary Search Tree)是一种特殊的二叉树数据结构,其每个节点包含一个值以及指向左子节点和右子节点的引用。BST具有以下性质:
     * 节点值特性:对于任意节点,其左子树中所有节点的值均小于该节点的值,而右子树中所有节点的值均大于该节点的值。
     * 基于这些特性,二叉查找树提供了高效的查找、插入和删除操作。
     */
    public static class BinarySearchTree {

        private TreeNode searchTree(TreeNode root, int val) {
            // return insertRecursive(root, val);
            // return searchRecursive(root, val);
            return delete(root, val);
        }

        /**
         * 插入节点
         * 每次就顺着根节点往下找,如果比根节点小,就往左找,如果比根节点大,就往右找,如果不存在为空了就插入该位置。
         *
         * @param root 二叉树
         * @param val  插入数值
         * @return TreeNode
         */
        private TreeNode insertRecursive(TreeNode root, int val) {
            if (root == null) {
                return new TreeNode(val);
            } else if (val < root.val) {
                root.left = insertRecursive(root.left, val);
            } else if (val > root.val) {
                root.right = insertRecursive(root.right, val);
            }
            return root;
        }

        /**
         * 查找节点
         * 和插入操作类似:查找该二叉树是否为空或者是否找到目标值,如果找到就返回该节点。
         * 没有找到就接着叶节点,大于往右边找,小于往左边找
         *
         * @param root 二叉树
         * @param val  查找值
         * @return TreeNode
         */
        private TreeNode searchRecursive(TreeNode root, int val) {
            if (root == null || root.val == val) {
                return root;
            } else if (val < root.val) {
                return searchRecursive(root.left, val);
            } else {
                return searchRecursive(root.right, val);
            }
        }

        /**
         * 删除一个节点可能涉及替换其值、将其子节点提升为父节点,或者合并两个子树。需要处理三种情况:
         * 删除无子节点的节点、删除有一个子节点的节点,以及删除有两个子节点的节点。
         * 删除操作相对插入和查找来说更为复杂,因为它涉及到节点的移除以及可能的结构调整以保持BST的性质。
         * 删除操作步骤:
         * 1、查找待删除节点:首先使用查找算法找到要删除的节点(目标节点)。若未找到,则无需执行删除操作。
         * 2、处理三种不同情况:
         * 目标节点没有子节点(叶节点):直接删除目标节点。
         * 目标节点只有一个子节点:将目标节点替换为其唯一子节点。
         * 目标节点有两个子节点:找到目标节点的后继节点(右子树中最小节点或左子树中最大节点),将其值复制到目标节点,然后删除后继节点。这样做的目的是确保删除操作仅涉及至多一个子节点的情况。
         * 3、调整结构:删除目标节点(或其后继节点)后,如果其有子节点,需要将其子节点连接到父节点或更新根节点以保持BST性质。
         *
         * @param root 二叉树
         * @param val  删除值
         * @return boolean
         */
        private TreeNode delete(TreeNode root, int val) {
            // 查找,找到才删除,没找到就不动
            TreeNode treeNode = this.searchRecursive(root, val);
            if (treeNode == null) {
                return null;
            }
            return deleteRecursive(root, val);
        }

        private TreeNode deleteRecursive(TreeNode root, int val) {
            if (root == null) {
                return null;
            }

            if (val < root.val) {
                root.left = deleteRecursive(root.left, val);
            } else if (val > root.val) {
                root.right = deleteRecursive(root.right, val);
            } else {
                // 找到目标节点
                // 1、目标节点没有子
                if (root.left == null && root.right == null) {
                    return null;
                } else if (root.left != null && root.right == null) {
                    // 2、目标节点只有一个子
                    return root.left;
                } else if (root.left == null) {
                    return root.right;
                }

                TreeNode minNode = findMin(root.right);
                root.val = minNode.val;
                root.right = deleteRecursive(root.right, minNode.val);
            }
            return root;
        }

        private TreeNode findMin(TreeNode node) {
            while (node.left != null) {
                node = node.left;
            }
            return node;
        }
    }
}

/**
 * 二叉树节点
 */
class TreeNode {
    int val;
    TreeNode left;
    TreeNode right;

    TreeNode() {
    }

    TreeNode(int val) {
        this.val = val;
    }

    TreeNode(int val, TreeNode left, TreeNode right) {
        this.val = val;
        this.left = left;
        this.right = right;
    }

    @Override
    public String toString() {
        return "TreeNode{" +
                "val=" + val +
                ", left=" + left +
                ", right=" + right +
                '}';
    }
}

  • 24
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值