【数据结构与算法】【数据结构】详解二叉树

举报
huahua.Dr 发表于 2021/09/25 19:55:41 2021/09/25
【摘要】 一、什么是二叉树树是由许多节点使用使用一条条边连接而成的,一个节点可以由许多子节点,如果一个节点只有两个子节点,那么这种树就叫做二叉树,二叉树是树中最简单的一种数据结构存在。二、为什么需要二叉树除了二叉树这种数据结构,我们最先熟悉的数据结构有:数组和链表,这两个数据结构是我们经常使用的;但是在我们使用的过程中,会发现数组和链表的优缺点:数组:有序数组查找某个元素会很快(使用二分查找法,其时间...

一、什么是二叉树

树是由许多节点使用使用一条条边连接而成的,一个节点可以由许多子节点,如果一个节点只有两个子节点,那么这种树就叫做二叉树,二叉树是树中最简单的一种数据结构存在。

二、为什么需要二叉树

除了二叉树这种数据结构,我们最先熟悉的数据结构有:数组和链表,这两个数据结构是我们经常使用的;但是在我们使用的过程中,会发现数组和链表的优缺点:

  • 数组:有序数组查找某个元素会很快(使用二分查找法,其时间复杂度为O(logN)),但插入和删除某个元素很慢;无序数组除了插入某个元素很快之外,其余操作都很慢。
  • 链表:插入和删除都很快(其时间复杂度为O(1)),只需变化某个元素的引用即可,但查找效率很慢。

从上面可以看到数组和链表的优缺点若是能互补,那么这种数据结构就能应对各种场景而不用考虑效率问题;二叉树就是集合数组和链表这两种数据结构的优点:

二叉树的一个节点的左节点的数据值比父节点的数据值小,右节点比父节点的数据值大(类似有序数组),这样搜索就快;插入和删除也只需要改变边引用即可,也很快。

三、二叉树的特征

二叉树是树结构中的一种特例,二叉树的特征也是树的特征,二叉树是由根、路径、父节点、子节点、叶节点、层和子树等特征构成。

根:树的顶端节点就称为根

路径:连接两个节点的边就叫做路径

父节点:除了根节点外,还存在其他节点,并且这些节点还存在子节点,那么这些节点就称之为父节点。

子节点:每个节点都可以向下有一个或者两个节点,那么这些节点就称之为子节点。

叶节点:没有子节点的节点就称之为叶节点

层:层的划分是根据节点下面还有没有子节点,有子节点,那么子节点就划分为一层,以此类推。

子树:有根就可以看成一棵树,每个节点都可以看做是子树的根,该节点根它下面的节点共同构成一棵树。

特别说明:上面的特征概念都是相对而言的,只需要理解即可。重点是节点概念,节点就是接下来我们需要用来存储数据的,保存的数据可以是基本数据类型也可以是对象引用类型等等。我们根据二叉树的每个节点最多只有两个子节点(称之为左节点和右节点)的特征去操作二叉树。

重要特征:二叉树的一个节点的左节点的数据值比父节点的数据值小,右节点比父节点的数据值大

四、二叉树的如何实现

实现一颗二叉树,首先需要定义一个Node对象,用来表示一个个节点。

 class Node {    // 节点对象

    public  int value;

    public Node leftNode;

    public Node rightNode;

}

public class Tree {// 二叉树对象

    private Node root;//根节点

    public Tree(Node root) {

        this.root = root;

    }

}

还有二叉树还需要具备基本操作:查找节点、插入节点和删除节点基本操作

插入节点:

插入一个节点数据需要分配保存位置,从根节点开始,分别比较现有节点的数据值,如果比该节点小就往左节点继续比较,如果比该节点大就往右节点继续比较,直到比较的节点是叶节点,那下面就是插入的位置了。

    public void insert(int value) {

        Node newNode = new Node();

        newNode.value = value;

        if (root == null) {

            root = newNode;

            return;

        }

        Node currentNode = root;

        Node parentNode;

        while (true) {

            parentNode = currentNode;

            if (value < currentNode.value) {

                currentNode = currentNode.leftNode;

                if (currentNode == null) {

                    parentNode.leftNode = newNode;

                    return;

                }

            } else {

                currentNode = currentNode.rightNode;

                if (currentNode == null) {

                    parentNode.rightNode = newNode;

                    return;

                }

            }

        }

    }

 

查找节点:

从根节点开始分别左右节点搜索值,如果相等则返回,否则返回null

 

   public Node find(int value) {

        Node currentNode = root;

        while (currentNode.value != value) {

            if (value < currentNode.value) {

                currentNode = currentNode.leftNode;

            } else {

                currentNode = currentNode.rightNode;

            }

            if (currentNode == null) {

                return null;

            }

        }

        return currentNode;

    }

删除节点:

删除节点比较复杂,涉及到三种情况:

  1. 删除的节点是叶节点,直接删除引用即可
  2. 删除的节点只有一个子节点,将删除后的位置用子节点代替即可
  3. 删除的节点有两个子节点,需要寻找后继节点代替删除节点的位置

删除节点的操作很复杂,如果我们没必要的话就不要真正的删除节点,只是在每个节点上放置一个delete的标志位,如果删除某个节点,就直接find到这个节点,将这个标志位置为true即可,完成删除的操作。真正删除节点的话需要花费的代价有点大,例如公司的员工离职,需要删除这个员工,只需要将delete标记为true即可,还可以保留员工档案,这样比较划算。

 

   public boolean delete(int value) {

        Node currentNode = root;

        Node parentNode = root;

        boolean isLeftNode= true;

        while (currentNode.value != value) {

            parentNode = currentNode;

            if ( value < currentNode.value) {

                isLeftNode = true;

                currentNode = currentNode.leftNode;

            } else {

                isLeftNode = false;

                currentNode = currentNode.rightNode;

            }

            if (currentNode == null) {

                return false;

            }

            // 第一种情况:删除的节点为叶节点

            if (currentNode.leftNode == null && currentNode.rightNode == null) {

                if (currentNode== root) {

                    root = null;

                    return true;

                }

                if (isLeftNode) {

                    parentNode.leftNode = null;

                    return true;

                }

                parentNode.rightNode = null;

                return true;

            }

            // 第二种情况:删除的节点只有一个节点。

            if (currentNode.rightNode == null) {//只有左节点

                if (currentNode == root) {

                    root = currentNode.leftNode;

                    return true;

                }

                if (isLeftNode) {

                    parentNode.leftNode =currentNode.leftNode;

                    return true;

                }

                parentNode.rightNode = currentNode.leftNode;

                return  true;

            }

            if (currentNode.leftNode == null) {//只有右节点

                if (currentNode == root) {

                    root = currentNode.rightNode;

                    return true;

                }

                if (isLeftNode) {

                    parentNode.leftNode =currentNode.rightNode;

                    return true;

                }

                parentNode.rightNode = currentNode.rightNode;

                return  true;              

            }

            //第三种情况:删除的节点两个节点

            Node next = getNextNode(currentNode);

            if (currentNode == root) {

                root = next;

                return true;

            }

            if (isLeftNode) {

                parentNode.leftNode = next;

                return true;

            }

            parentNode.rightNode = next;

            next.leftNode = currentNode.leftNode;

            return true;

        }

        return false;

    }

    // 获取后继节点

    public Node getNextNode(Node delNode){

        Node parent = delNode;

        Node next = delNode;

        Node current = delNode.rightNode;

        while (current != null) {

            parent = next;

            next = current;

            current = current.leftNode;

        }

        if (next != delNode.rightNode) {

            parent.leftNode = next.rightNode;

            next.rightNode = delNode.rightNode;

        }

        return next;

    }

 

遍历节点:

有三种简单的方法遍历树,基本方法就是采用递归思想去遍历

前序遍历

    1. 访问该节点
    2. 调用自身遍历节点的左子树
    3. 调用自身遍历节点的右子树
    public void preOrder(Node localNode) {

        if (localNode != null) {

            System.out.println(localNode.value);

            preOrder(localNode.leftNode);

            preOrder(localNode.rightNode);

        }

    }

中序遍历

    1. 调用自身遍历节点的左子树
    2. 访问该节点
    3. 调用自身遍历节点的右子树
    public void inOrder(Node localNode) {

        if (localNode != null) {

            inOrder(localNode.leftNode);

            System.out.println(localNode.value);

            inOrder(localNode.rightNode);

        }

    }

后序遍历

    1. 调用自身遍历节点的左子树
    2. 调用自身遍历节点的右子树
    3. 访问该节点
    public void afterOrder(Node localNode) {

        if (localNode != null) {

            afterOrder(localNode.leftNode);

            afterOrder(localNode.rightNode);

            System.out.println(localNode.value);

        }

    }

五、二叉树的优缺点

优点就是:二叉树查找、插入、删除的效率都是O(logN);

缺点的话就是比较复杂。其他一切还好,想要好用的数据结构那必须是需要付出代价的。

 

 

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。