0
点赞
收藏
分享

微信扫一扫

Day21二叉树快速入门学习(Java)

elvinyang 2022-01-23 阅读 158

一、为什么要使用树这种数据结构

1)数组存储方式的分析

优点:通过下标方式访问元素,速度快。对于有序数组,还可使用二分查找提高检索速度。

缺点:如果要检索具体某个值,或者插入值(按一定顺序)会整体移动,效率较低 [示意图]

2)链式存储方式的分析

优点:在一定程度上对数组存储方式有优化(比如:插入一个数值节点,只需要将插入节点,链接到链表中即可, 删除效率也很好)。

缺点:在进行检索时,效率仍然较低,比如(检索某个值,需要从头节点开始遍历) 【示意图】

3)存储方式的分析

能提高数据存储,读取的效率, 比如利用 二叉排序树(Binary Sort Tree),既可以保证数据的检索速度,同时也可以保证数据的插入,删除,修改的速度。

【示意图,后面详讲】

案例: [7, 3, 10, 1, 5, 9, 12]

二、什么是二叉树

image-20220122232012082

image-20220122232110347

image-20220122232432418

三、二叉树前中后序遍历

image-20220122232623897

前序遍历: 先输出父节点,再遍历左子树和右子树

中序遍历: 先遍历左子树,再输出父节点,再遍历右子树

后序遍历: 先遍历左子树,再遍历右子树,最后输出父节点

小结: 看输出父节点的顺序,就确定是前序,中序还是后序

image-20220122232716077

package com.fafa.tree;

/**
 * 二叉树
 *
 * @author Sire
 * @version 1.0
 * @date 2022-01-22 16:51
 */
public class BinaryTreeDemo {
    public static void main(String[] args) {
        // 创建节点
        HeroNode root = new HeroNode(1, "宋江");
        HeroNode node2 = new HeroNode(2, "吴用");
        HeroNode node3 = new HeroNode(3, "卢俊义");
        HeroNode node4 = new HeroNode(4, "林冲");
        HeroNode node5 = new HeroNode(5, "吴胜");
        // 先手动生成二叉树(后面会用递归来生成,暂时先手动)
        root.setLeft(node2);
        root.setRight(node3);
        node3.setRight(node4);
        node3.setLeft(node5);
        // 创建二叉树
        BinaryTree tree = new BinaryTree(root);
        // 测试前序遍历
        System.out.println("前序遍历");
        tree.preOrder();
        // 测试中序遍历
        System.out.println("中序遍历");
        tree.infixOrder();
        // 测试后序遍历
        System.out.println("后序遍历");
        tree.postOrder();

    }

}

/**
 * 创建二叉树
 */
class BinaryTree {

    HeroNode root = null;

    public BinaryTree(HeroNode root) {
        this.root = root;
    }

    /**
     * 前序遍历
     */
    public void preOrder() {
        if (this.root != null) {
            this.root.preOrder();
        } else {
            System.out.println("这是一颗空树");
        }
    }

    /**
     * 中序遍历
     */
    public void infixOrder() {
        if (this.root != null) {
            this.root.infixOrder();
        } else {
            System.out.println("这是一颗空树");
        }
    }

    /**
     * 后序遍历
     */
    public void postOrder() {
        if (this.root != null) {
            this.root.postOrder();
        } else {
            System.out.println("这是一颗空树");
        }
    }


}

/**
 * 英雄节点
 */
class HeroNode {
    private int id;
    private String name;
    private HeroNode left;
    private HeroNode right;

    /**
     * 构造方法,默认做左右节点为空
     *
     * @param id
     * @param name
     */
    public HeroNode(int id, String name) {
        this.id = id;
        this.name = name;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public HeroNode getLeft() {
        return left;
    }

    public void setLeft(HeroNode left) {
        this.left = left;
    }

    public HeroNode getRight() {
        return right;
    }

    public void setRight(HeroNode right) {
        this.right = right;
    }

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

    /**
     * 前序遍历
     * 思路:
     * 1、再打印父节点
     * 2、然后判断左子树是否为空,不为空的话,递归打印
     * 3、判断右子树是否为空,不为空的话,递归打印
     */
    public void preOrder() {
        // 为什么可以直接打印,不用判断根节点呢,因为传进来的肯定是一个有效的节点,所以不用判断(在上一调用层已经进行判断了)
        System.out.println(this);
        if (this.left != null) {
            this.left.preOrder();
        }
        if (this.right != null) {
            this.right.preOrder();
        }
    }

    /**
     * 中序遍历
     * 思路:
     * 1、先判断左子树是否为空,不为空的话,递归打印
     * 2、再打印父节点
     * 3、判断右子树是否为空,不为空的话,递归打印
     */
    public void infixOrder() {
        if (this.left != null) {
            this.left.infixOrder();
        }
        System.out.println(this);
        if (this.right != null) {
            this.right.infixOrder();
        }
    }

    /**
     * 后序遍历
     * 思路:
     * 1、先判断左子树是否为空,不为空的话,递归打印
     * 2、判断右子树是否为空,不为空的话,递归打印
     * 3、再打印父节点
     */
    public void postOrder() {
        if (this.left != null) {
            this.left.postOrder();
        }
        if (this.right != null) {
            this.right.postOrder();
        }
        System.out.println(this);
    }
}

四、二叉树前中后序查找

image-20220122233549339

添加到HeroNode类里

/**
     * 前序遍历查找
     *
     * @param no 编号
     * @return
     */
public HeroNode preOrderSearch(int no) {
    System.out.println("get into preSearch~");
    // 比较当前节点是不是
    if (this.getId() == no) {
        return this;
    }
    // 结果节点
    HeroNode resNode = null;
    // 比较左子节点
    // 和遍历的思路大同小异
    // 1、判断当前的做自己额点是否为空,如不为空则继续遍历查找(递归)
    // 2、如左子节点找到结果,则直接返回
    if (this.left != null) {
        resNode = this.left.preOrderSearch(no);
    }
    // 如果不为空,说明在左子树找到了
    if (resNode != null) {
        return resNode;
    }
    // 同上,只不过最后直接返回resNode即可,因为无论找到没,都是resNode
    if (this.right != null) {
        resNode = this.right.preOrderSearch(no);
    }
    return resNode;
}

/**
     * 中序遍历查找
     *
     * @param no 编号
     * @return
     */
public HeroNode infixOrderSearch(int no) {
    // 结果节点
    HeroNode resNode = null;
    // 比较左子节点
    // 和遍历的思路大同小异
    // 1、判断当前的做自己额点是否为空,如不为空则继续遍历查找(递归)
    // 2、如左子节点找到结果,则直接返回
    if (this.left != null) {
        resNode = this.left.infixOrderSearch(no);
    }
    // 如果不为空,说明在左子树找到了
    if (resNode != null) {
        return resNode;
    }
    // 要放在比较语句前面,不能单纯的写在最前面(因为统计的是比较次数)
    System.out.println("get into infixSearch~");
    // 比较当前节点是不是
    if (this.getId() == no) {
        return this;
    }
    // 同上,只不过最后直接返回resNode即可,因为无论找到没,都是resNode
    if (this.right != null) {
        resNode = this.right.infixOrderSearch(no);
    }
    return resNode;
}

/**
     * 后序遍历查找
     *
     * @param no 编号
     * @return
     */
public HeroNode postOrderSearch(int no) {

    // 结果节点
    HeroNode resNode = null;
    // 比较左子节点
    // 和遍历的思路大同小异
    // 1、判断当前的做自己额点是否为空,如不为空则继续遍历查找(递归)
    // 2、如左子节点找到结果,则直接返回
    if (this.left != null) {
        resNode = this.left.postOrderSearch(no);
    }
    // 如果不为空,说明在左子树找到了
    if (resNode != null) {
        return resNode;
    }
    // 同上,只不过最后直接返回resNode即可,因为无论找到没,都是resNode
    if (this.right != null) {
        resNode = this.right.postOrderSearch(no);
    }
    if (resNode != null) {
        return resNode;
    }
    // 要放在比较语句前面,不能单纯的写在最前面(因为统计的是比较次数)
    System.out.println("get into postSearch~");
    // 如果左右子树都没找到
    if (this.getId() == no) {
        return this;
    }
    return null;
}

五、二叉树删除节点

image-20220122233634014

在HeroNode类里添加

/**
     * 递归删除节点
     * 1、如果需要删除的节点是叶子结点的话,就直接删除该节点即可
     * 2、如果删除的节点是非叶子节点,则删除该子树
     */
public void delNode(int no) {
    //思路
    /*
		 * 	1. 因为我们的二叉树是单向的,所以我们是判断当前结点的子结点是否需要删除结点,而不能去判断当前这个结点是不是需要删除结点.
			2. 如果当前结点的左子结点不为空,并且左子结点 就是要删除结点,就将this.left = null; 并且就返回(结束递归删除)
			3. 如果当前结点的右子结点不为空,并且右子结点 就是要删除结点,就将this.right= null ;并且就返回(结束递归删除)
			4. 如果第2和第3步没有删除结点,那么我们就需要向左子树进行递归删除
			5.  如果第4步也没有删除结点,则应当向右子树进行递归删除.

		 */
    if (this.left != null && this.left.getId() == no) {
        // 置为空
        this.setLeft(null);
        return;
    }
    if (this.right != null && this.right.getId() == no) {
        this.setRight(null);
        return;
    }
    if (this.left != null) {
        this.left.delNode(no);
    }
    if (this.right != null) {
        this.right.delNode(no);
    }
}

在BinaryTree类里添加

/**
     * 删除节点
     * @param no
     */
public void delNode(int no) {
    if (root != null) {
        // 这要判断待删除节点是否为根节点
        if (root.getId() == no) {
            root = null;
        } else {
            root.delNode(no);
        }
    } else {
        System.out.println("空数,无法删除~");
    }
}

image-20220122234108368

举报

相关推荐

0 条评论