数据结构与算法-二叉树

二叉树的两种存储方式:链式存储法和基于数组的顺序存储法

顺序存储法:位置为i的节点的左节点为2i,右节点为2i+1,根节点存在下标为1的位置上,顺序存储会存在空白空间,因此会浪费空间,如果是一颗完全二叉树,那么就可以用数组的存储方式。

满二叉树:所有叶子节点都在最底层,并且除了叶子节点以外的节点都有左右节点。

完全二叉树:所有叶子节点都在最底下两层,最后一层的叶子节点都靠左排列,并且除了最后一层,其他层的节点个数都要达到最大。

二叉查找树:如果遇到支持存储重复数据的二叉查找树,那么有两种方法解决:一是相同的值存储在同一个节点上,该节点通过链表或者支持动态扩容的数据解决;二是遇到相同的值,把这个要插入的值放到这个节点的右子树,也是就是当做比当前值大的数插入。

二叉查找树比散列表的优势:

1、散列表的数据是无序存储的,二叉查找树使用中序遍历就可以输出有序数据

2、散列表扩容耗时很多,性能不稳定,二叉查找树性能稳定,时间复杂度稳定在O(logn)

3、散列表由于有哈希冲突的存在,因此查找速度可能不一定比平衡二叉查找树高

4、散列表构造比二叉查找树复杂,需要考虑散列函数的设计,冲突解决方法,扩容,缩容等,平衡二叉查找树只需要考虑平衡型这个问题,而且解决方法比较成熟,固定。

以下是二叉查找树简单操作的代码

package com.freshbin.dataStructAndAlgo.chapter24.mycode;

/**
 * 二叉查找树,没有重复值
 * @author freshbin
 * @date 2020/4/20 16:16
 */
public class BinarySearchTree {
    private Node tree;

    /**
     * 插入步骤:
     * 1、根节点为空,直接插入
     * 2、循环判断当前节点是否为空以及需要插入的值与当前节点的大小,
     * 大,则判断右节点是否为空,为空,直接把当前节点的右指针指向需要插入的节点即可,否则,将当前节点赋为右节点;
     * 小,则判断右节点是否为空,为空,直接把当前节点的右指针指向需要插入的节点即可,否则,将当前节点赋为左节点;
     * 等于,就直接退出插入方法,表示该值已经存在
     * @param value
     */
    public void insert(int value) {
        Node node = new Node(value);

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

        Node pHead = tree;
        while(pHead != null) {
            if(value > pHead.data) {
                if(pHead.right == null) {
                    pHead.right = node;
                    return;
                }
                pHead = pHead.right;
            } else if(value < pHead.data) {
                if(pHead.left == null) {
                    pHead.left = node;
                    return;
                }
                pHead = pHead.left;
            } else {
                System.out.println("该值" + value + "已经存在,不在插入!");
                return;
            }
        }
    }

    /**
     * 查找操作
     * 步骤:1、将跟节点赋给当前节点,循环判断当前节点不等于空,并且不等于目标值
     * 2、如果目标值大于当前节点值,那么当前节点就等于右节点
     * 3、小于,那么当前节点就等于左节点
     * 4、返回该节点
     * @param targetValue
     * @return
     */
    public Node find(int targetValue) {
        Node pHead = tree;
        while(pHead != null && pHead.data != targetValue) {
            if(targetValue > pHead.data) {
                pHead = pHead.right;
            } else {
                pHead = pHead.left;
            }
        }

        return pHead;

        /*while(pHead != null) {
            if(targetValue > pHead.data) {
                pHead = pHead.right;
            } else if(targetValue < pHead.data){
                pHead = pHead.left;
            } else {
                return pHead;
            }
        }
        return null;
        */
    }

    /**
     * 删除操作
     * 三种删除情况:
     * 1、需要删除的节点为叶子节点,那么直接将父节点指向空即可;
     * 2、需要删除的节点只有一个左节点或者一个右节点,那么直接将父节点指向删除节点的子节点即可
     * (父节点指向的时候,需要先判断删除节点是父节点的左节点指针还是右节点指针,然后再相应的左右指针指向子节点);
     * 3、需要删除的节点有左右节点,那么查找该节点右子节点中的最小值节点,
     * 将要删除的节点赋值为该最小值节点,同时要删除的节点的父节点赋值为最小值节点的父节点
     * 这时候,最小值节点的位置就成为了要删除的节点,因为最小值节点肯定没有左子节点,
     * 所以这时候又可以转变成第1种情况和第2种情况,即删除叶子节点或者只有一个子节点的情况。
     *
     * 因此,综上,我们先判断第3种情况。
     *
     * @param targetValue
     */
    public void deleteNode(int targetValue) {
        if(tree == null) {
            return;
        }
        Node deleteNode = tree;
        Node deleteParentNode = null;
        // 查找需要删除的节点
        while(deleteNode != null && deleteNode.data != targetValue) {
            deleteParentNode = deleteNode;
            if(targetValue > deleteNode.data) {
                deleteNode = deleteNode.right;
            } else {
                deleteNode = deleteNode.left;
            }
        }
        if(deleteNode == null) {
            return;
        }

        // 第3种情况
        if(deleteNode.left != null && deleteNode.right != null) {
            // 查找右节点最小值
            Node miniNode = deleteNode.right;
            Node miniParentNode = deleteNode;
            while(miniNode.left != null) {
                miniParentNode = miniNode;
                miniNode = miniNode.left;
            }
            deleteNode.data = miniNode.data;
            deleteNode = miniNode;
            deleteParentNode = miniParentNode;
        }

        // 第1,2种情况
        Node childNode;
        if(deleteNode.right != null) childNode = deleteNode.right;
        else if(deleteNode.left != null) childNode = deleteNode.left;
        else childNode = null;

        // 删除节点
        if(deleteParentNode == null) tree = childNode;
        else if(deleteParentNode.left == deleteNode) deleteParentNode.left = childNode;
        else deleteParentNode.right = childNode;
    }

    public class Node {
        private int data;
        private Node left;
        private Node right;

        public Node(int value) {
            this.data = value;
        }

        @Override
        public String toString() {
            StringBuffer sb = new StringBuffer();
            sb.append("data:" + data);
            return sb.toString();
        }
    }

    /**
     * 前序遍历
     * @param tree
     */
    public void preOrder(Node tree) {
        if(tree == null) return;
        System.out.println(tree.data);
        preOrder(tree.left);
        preOrder(tree.right);
    }

    public void inOrder(Node tree) {
        if(tree == null) return;

        preOrder(tree.left);
        System.out.println(tree.data);
        preOrder(tree.right);
    }

    public void postOrder(Node tree) {
        if(tree == null) return;

        preOrder(tree.left);
        preOrder(tree.right);
        System.out.println(tree.data);
    }

    public static void main(String[] arg) {
        BinarySearchTree binarySearchTree = new BinarySearchTree();
        binarySearchTree.insert(1);
        binarySearchTree.insert(1);
        binarySearchTree.insert(5);
        binarySearchTree.insert(3);
        binarySearchTree.insert(2);
        binarySearchTree.insert(4);
        binarySearchTree.insert(0);
        int targetValue = 1;
        System.out.println("删除"+targetValue+"之前前序遍历:");
        binarySearchTree.preOrder(binarySearchTree.tree);
        System.out.println("查找"+targetValue+"的值:" + binarySearchTree.find(targetValue));
        binarySearchTree.deleteNode(targetValue);
        System.out.println("删除"+targetValue+"之后前序遍历:");
        binarySearchTree.preOrder(binarySearchTree.tree);
        System.out.println("查找"+targetValue+"的值:" + binarySearchTree.find(targetValue));
    }
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 适合毕业设计、课程设计作业。这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。 所有源码均经过严格测试,可以直接运行,可以放心下载使用。有任何使用问题欢迎随时与博主沟通,第一时间进行解答!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值