数据结构——二叉树(基础)

巧了我就是萌 提交于 2020-02-04 05:02:30

1、树存在意义:

  • 1)数组的存储方式的分析
    优点:通过下标方式访问元素,速度快,对于有序数组,还可使用二分查找提高检索速度。
    缺点:如果要检索具体某个值,或者插入值(按一定顺序)会整体移动,效率低。
  • 2)链式存储方式的分析
    优点:在一定程度上对数组存储方式有优化(比如:插入一个数值节点,只需要将插入节点,连接到链表即可,删除效率也很好)。
    缺点:在进行检索时,效率仍然很低,检索某个值,需要从头节点开始遍历。
  • 3)树的存储方式分析
    能提高存储,读取的效率,比如利用二叉排序树(Binary Sort Tree),既可以保证数据的检索速度,同事也可以保证数据的插入,删除,修改的速度

2、树的常用术语:

  • 节点:如A,B,F分别是树的节点;
  • 根节点:如A是整棵树的根节点;
  • 父节点:如B是E的父节点;
  • 子节点:如E是B的子节点;
  • 叶子节点:没有子节点的节点,如D.H,F,G都是叶子节点;
  • :若将树中节点赋给一个有着某种含义的数值,则这个数值称为该结点的权;
  • 路径:从root结点(根节点)找到该节点的路线,如H节点的路径为{A,B,E,H};
  • :从根开始定义起,根为第一层,根的孩子为第二层。若某节点在第k层,则其子树的就在第k+1 层。如节点E的层数为3;
  • 子树:如集合{B,D,E,H}为A的一颗子树;
  • 树的高度:树的最大层数,如示例图树的高度为4;
  • 森林:是m (m>=0) 棵互不相交的树的集合。对树中每个结点而言,其子树的集合即为森林。
    在这里插入图片描述

3、二叉树的概念:

  • 树的种类有很多,每个节点最多只能两个子节点的一种形式称为二叉树。
    二叉树的节点分为左节点和右节点。
    以下三种情况都是二叉树:
    在这里插入图片描述
  • 如果二叉树的所有叶子节点都在最后一层,并且节点总数为2^n - 1,n为层数,则我们称为满二叉树
    在这里插入图片描述
  • 如果该二叉树的所有叶子几点都在最后一层或者倒数第二层,而且最后一层的叶子节点在左边连续,倒数第二层的叶子节点在右边连续,我们称为完全二叉树(如果删除F节点则不是完全二叉树):
    在这里插入图片描述

4、二叉树的遍历:

  • 前序遍历:先输出父节点,再遍历左子树和右子树;
    1)、创建一棵二叉树;
    2)、先输出当前节点(初始节点为root节点);
    3)、如果左子树不为空,则递归继续前序遍历;
    4)、如果右子树不为空,则递归继续前序遍历。
  • 中序遍历:先遍历左子树,再输出父节点,最后遍历右子树;
    1)、创建一棵二叉树;
    2)、如果左子树不为空,则递归继续中序遍历;
    3)、输出当前节点;
    4)、如果右子树不为空,则递归继续中序遍历。
  • 后序遍历:先遍历左子树,再遍历右子树,最后输出父节点;
    1)、创建一棵二叉树;
    2)、如果左子树不为空,则递归继续后序遍历;
    3)、如果右子树不为空,则递归继续后序遍历。
    4)、输出当前节点。

4、二叉树的查找:

  • 前序查找
    1)、先判断当前节点的no是否等于要查找的no;
    2)、如果相等则返回当前节点;
    3)、如果不等,则判断当前节点的左子节点是否为空,如果不为空,则递归前序查找;
    4)、如果左递归前序查找,找到节点,则返回,否则继续判断右子节点是否为空,如果不为空,则继续向右递归前序查找。
  • 中序查找
    1)、先则判断当前节点的左子节点是否为空,如果不为空,则递归中序查找;
    2)、如果左递归前序查找,找到节点,则返回,否则继续判断当前节点的no是否等于要查找的no,如果相等则返回当前节点;
    3)、否则继续判断右子节点是否为空,如果不为空,则继续向右递归中序查找。
  • 后序查找
    1)、先则判读当前节点的左子节点是否为空,如果不为空,则递归后序查找;
    2)、如果左递归后序查找,找到节点,则返回,否则继续判断右子节点是否为空,如果不为空,则继续向右递归后序查找。
    3)、如果右递归后序查找,找到节点,则返回,否则继续判断当前节点的no是否等于要查找的no,如果相等则返回当前节点。

5、二叉树节点的删除:

  • 约定
    1)、如果删除的节点是叶子节点,则删除该节点;
    2)、如果删除的节点是非叶子节点,则删除该子树。
  • 思路
    1)、因为二叉树是单向的,我们需要判断当前节点的子节点是否需要删除的节点,而不能去判断当前节点是否需要删除的节点;
    2)、如果当前节点的左子节点不为空,并且左子节点就是要删除的节点,将当this.left = null,返回结束递归删除即可;
    3)、如果当前节点的右子节点不为空,并且右子节点就是要删除的节点,将当this.right= null,返回结束递归删除即可;
    4)、如果前面均未找到待删除的节点,左子树递归删除操作即可;
    5)、如果左子树未找到待删除的节点,右子树递归删除操作即可。

6、代码示例

  • 节点类
@Data
class HeroNode {
    private int no;
    private String name;
    /*左子树*/
    private HeroNode left;
    /*右子树*/
    private HeroNode right;

    public HeroNode(int no, String name) {
        this.no = no;
        this.name = name;
    }

    @Override
    public String toString() {
        return "HeroNode{" +
                "id=" + no +
                ", name='" + name + '\'' +
                '}';
    }

    /*前序遍历*/
    public void preOrder() {
        /*1、当前节点*/
        System.out.println(this);
        /*2、递归左子树前序遍历*/
        if (this.left != null) {
            this.left.preOrder();
        }
        /*3、递归右子树前序遍历*/
        if (this.right != null) {
            this.right.preOrder();
        }
    }

    /**
     * 前序遍历查找节点
     *
     * @param no 待查找的编号
     * @return 返回指定节点数据,如果没有就返回null
     */
    public HeroNode preOrderSearch(int no) {
        /*比较当前节点是否与待查找的节点*/
        if (this.no == no) {
            return this;
        }
        HeroNode resNode = null;
        /*如果左子树不为空,递归遍历左子树进行节点查找*/
        if (this.left != null) {
            resNode = preOrderSearch(no);
        }
        /*如果左子树找到节点,直接返回,毋需再进行右子树遍历*/
        if (resNode != null) {
            return resNode;
        }
        /*如果右子树不为空,递归遍历右子树进行节点查找*/
        if (this.right != null) {
            resNode = preOrderSearch(no);
        }
        return resNode;
    }

    /*中序遍历*/
    public void midOrder() {
        /*1、递归左子树中序遍历*/
        if (this.left != null) {
            this.left.midOrder();
        }
        /*2、当前节点*/
        System.out.println(this);
        /*3、递归右子树中序遍历*/
        if (this.right != null) {
            this.right.midOrder();
        }
    }

    /**
     * 中序查找
     *
     * @param no 待查找的编号
     * @return 返回指定节点数据,如果没有就返回null
     */
    public HeroNode midOrderSearch(int no) {
        HeroNode resNode = null;
        /*如果左子树不为空,递归遍历左子树进行节点查找*/
        if (this.left != null) {
            resNode = midOrderSearch(no);
        }
        /*如果左子树找到节点,直接返回,毋需再进行当前节点和右子树遍历*/
        if (resNode != null) {
            return resNode;
        }
        /*如果当前节点即为待查找节点,直接返回当前节点即可*/
        if (this.no == no) {
            return this;
        }
        /*如果右子树不为空,递归遍历右子树进行节点查找*/
        if (this.right != null) {
            resNode = midOrderSearch(no);
        }
        return resNode;
    }

    /*后序遍历*/
    public void postOrder() {
        /*1、递归左子树后序遍历*/
        if (this.left != null) {
            this.left.postOrder();
        }
        /*2、递归右子树后序遍历*/
        if (this.right != null) {
            this.right.postOrder();
        }
        /*3、当前节点*/
        System.out.println(this);
    }

    /**
     * 后序查找
     *
     * @param no 待查找的编号
     * @return 返回指定节点数据,如果没有就返回null
     */
    public HeroNode postOrderSearch(int no) {
        HeroNode resNode = null;
        /*如果左子树不为空,递归遍历左子树进行节点查找*/
        if (this.left != null) {
            resNode = postOrderSearch(no);
        }
        /*如果左子树找到节点,直接返回,毋需再进行右子树和前节点遍历*/
        if (resNode != null) {
            return resNode;
        }
        /*如果右子树不为空,递归遍历右子树进行节点查找*/
        if (this.right != null) {
            resNode = postOrderSearch(no);
        }
        /*如果当前节点在右子树找到,毋需再进行当前节点的判断*/
        if (resNode != null) {
            return resNode;
        }
        /*进行当前节点的判断*/
        if (this.no == no) {
            resNode = this;
        }
        return resNode;
    }

    /**
     * 根据编号删除节点
     * 1、如果是叶子节点直接删除该节点
     * 2、如果是非叶子节点直接删除该子树
     *
     * @param no 待删除的节点编号
     */
    public void delHeroNode(int no) {
        /*判断左节点是否是待删除节点,如果是直接删除*/
        if (this.left != null && this.left.no == no) {
            this.left = null;
            return;
        }
        /*判断右节点是否是待删除的节点,如果是直接删除*/
        if (this.right != null && this.right.no == no) {
            this.right = null;
            return;
        }
        /*递归左子树*/
        if (this.left != null) {
            this.left.delHeroNode(no);
        }
        /*递归右子树*/
        if (this.right != null) {
            this.right.delHeroNode(no);
        }
    }

    /**
     * 根据编号删除节点
     * 1、如果是叶子节点直接删除该节点
     * 2、如果是非叶子节点且只有一个子节点,将子节点置为当前节点
     * 3、如果是非叶子节点且有两个子节点,将左节点置为当前节点
     *
     * @param no 待删除的节点编号
     */
    public void delHeroNodeLeft(int no) {
        if (this.left != null && this.left.no == no) {
            if(left.left != null){
                this.left = left.left;
            }else {
                this.left = left.right;
            }
            return;
        }
        if (this.right != null && this.right.no == no) {
            if(right.left != null){
                this.right = right.left;
            }else {
                this.right = right.right;
            }
            return;
        }
        /*递归左子树*/
        if (this.left != null) {
            this.left.delHeroNode(no);
        }
        /*递归右子树*/
        if (this.right != null) {
            this.right.delHeroNode(no);
        }
    }
}
  • 二叉树类
@Data
@AllArgsConstructor
class BinaryTree {
    private HeroNode root;

    public void preOrder() {
        System.out.println("==========前序遍历==========");
        if (root != null) {
            root.preOrder();
        } else {
            System.out.println("空二叉树");
        }
    }

    public void midOrder() {
        System.out.println("==========中序遍历==========");
        if (root != null) {
            root.midOrder();
        } else {
            System.out.println("空二叉树");
        }
    }

    public void postOrder() {
        System.out.println("==========后序遍历==========");
        if (root != null) {
            root.postOrder();
        } else {
            System.out.println("空二叉树");
        }
    }

    public void delNeroNode(int no) {
        if (root == null) {
            System.out.println("空树,不能进行删除操作!!!");
            return;
        }
        /*如果根节点的编号与待删除节点的编号一致。直接将root节点置null即可*/
        if (root.getNo() == no) {
            root = null;
            return;
        }
        /*递归删除指定节点*/
        root.delHeroNode(no);
    }

    public void delNeroNodeLeft(int no) {
        if (root == null) {
            System.out.println("空树,不能进行删除操作!!!");
            return;
        }
        /*如果根节点的编号与待删除节点的编号一致。直接将root节点置null即可*/
        if (root.getNo() == no) {
            if(root.getLeft() != null){
                root = root.getLeft();
            }else {
                root = root.getRight();
            }
            return;
        }
        /*递归删除指定节点*/
        root.delHeroNodeLeft(no);
    }
}
  • 测试类
public class BinaryTreeDemo {
    public static void main(String[] args) {
        HeroNode root = new HeroNode(1, "宋江");
        HeroNode node1 = new HeroNode(2, "吴用");
        HeroNode node2 = new HeroNode(3, "卢俊义");
        HeroNode node3 = new HeroNode(4, "林冲");
        HeroNode node4 = new HeroNode(5, "武松");
        root.setLeft(node1);
        root.setRight(node2);
        node2.setRight(node3);
        node2.setLeft(node4);
        BinaryTree binaryTree = new BinaryTree(root);
        binaryTree.preOrder();
//        binaryTree.midOrder();
//        binaryTree.postOrder();
//        binaryTree.delNeroNode(1);
        binaryTree.delNeroNodeLeft(2);
        binaryTree.preOrder();
    }
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!