二叉树的创建、遍历、查找、删除

目录

二叉树结构的创建:

二叉树的遍历:

二叉树的查找:

二叉树的删除:


我们最初学习数据结构的时候,肯定是先从线性结构和链式结构讲起,回顾一下他们的特点。

线性结构以数组为例,它通过下标的方式访问元素,访问速度很快,但是当我们向数组中插入或删除某个元素时,会将插入位置的元素整体移动,从而造成效率低下。

链式结构以单链表为例,它在插入或删除元素时,只改变链表的指向并且不移动元素,能够解决线性结构插入或删除元素效率不足的问题,但是当我们需要访问某个元素时,只能从单链表头依次循环直到找到待访问元素为止,因此访问元素效率低下。

那么有没有哪种数据结构,既可以提高访问元素的速度,又可以提高插入或删除元素的效率呢?

它来了,能够提高数据存储和读取效率的树来了。以二叉排序树为例,它既可以保证数据的检索速度,又能够保证数据的插入、删除、修改的速度。

那么什么是二叉树呢? 每个节点最多只能有两个子节点的一种树称为二叉树。

二叉树又有满二叉树和完全二叉树:

有了二叉树的概念,就要用代码来实现它。

二叉树结构的创建

二叉树拥有左右结点和父结点,根据这个特点来创建下图所示的二叉树结构。

  创建具有左右结点的单个结点:

class HeroNode{
    private int no;
    private String name;
    private HeroNode left; // 左结点
    private HeroNode right; // 右结点

    
    // 单个二叉树结点的操作
    // ...
}

创建带有头结点的二叉树:

class BinaryTree{
    private HeroNode root; // 头结点
    
    // 将单个结点的二叉树结点连接起来的操作
    // ...
}

二叉树的遍历

前序遍历、中序遍历、后续遍历。三种遍历的区别在于,父结点所在的位置,父结点在前为前序遍历、在中为中序遍历、在后为后续遍历。前序遍历:父--->左--->右;中序遍历:左--->父--->右;后续遍历:右--->左--->父;

三种遍历的前提条件是初始化父结点root。

 前序遍历思路:

  1. 先输出当前结点(初始为root结点);
  2. 如果当前结点的左子结点不为空,则递归前序遍历;
  3. 如果当前结点的右子结点不为空,则递归前序遍历;

中序遍历思路:

  1. 如果当前结点的左子结点不为空,则递归中序遍历;
  2. 输出当前结点(初始为root结点);
  3. 如果当前结点的右子结点不为空,则递归中序遍历;

后序遍历思路:

  1. 如果当前结点的左子结点不为空,则递归后序遍历;
  2. 如果当前结点的右子结点不为空,则递归后序遍历;
  3. 输出当前结点(初始为root结点); 
class BinaryTree{
    private HeroNode root; // 根结点

    public BinaryTree(HeroNode root){ // 指定根结点
        this.root = root;
    }

    // 前序遍历
    public void preOrder(){
        if (this.root != null){
            this.root.preOrder(); // 调用HeroNode中的前序遍历方法
        }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 no;
    private String name;
    private HeroNode left; // 左结点
    private HeroNode right; // 右结点

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

    // 省略getter、setter方法以及toString方法

    // 前序遍历
    public void preOrder(){
        // 1.输出当前父结点
        System.out.println(this);
        // 2.如果当前结点的左子结点不为空,前序遍历
        if (this.left != null){
            this.left.preOrder();
        }
        // 3.如果当前结点的右子结点不为空,前序遍历
        if (this.right != null){
            this.right.preOrder();
        }
    }

    // 中序遍历
    public void infixOrder(){
        // 1.如果当前结点的左子结点不为空,中序遍历
        if (this.left != null){
            this.left.infixOrder();
        }
        // 2.输出当前父结点
        System.out.println(this);
        // 3.如果当前结点的右子结点不为空,中序遍历
        if (this.right != null){
            this.right.infixOrder();
        }
    }

    // 后序遍历
    public void postOrder(){
        // 1.如果当前结点的左子结点不为空,中序遍历
        if (this.left != null){
            this.left.infixOrder();
        }
        // 2.如果当前结点的右子结点不为空,中序遍历
        if (this.right != null){
            this.right.infixOrder();
        }
        // 3.输出当前父结点
        System.out.println(this);
    }
}
public class BinaryTreeDemo {
    public static void main(String[] args) {
        HeroNode heroNode1 = new HeroNode(1,"宋江"); 
        HeroNode heroNode2 = new HeroNode(2,"吴用"); 
        HeroNode heroNode3 = new HeroNode(3,"武松"); 
        HeroNode heroNode4 = new HeroNode(4,"林冲"); 
        BinaryTree binaryTree = new BinaryTree(heroNode1);// 指定根结点
        heroNode1.setLeft(heroNode2);    // 根节点的左子结点
        heroNode1.setRight(heroNode3);    // 根节点的右子结点
        heroNode3.setRight(heroNode4);    // 根节点的右子结点的右子结点
        // 遍历
        System.out.println("前序遍历:");
        binaryTree.preOrder(); //前序遍历
        System.out.println("中序遍历:");
        binaryTree.infixOrder(); //中序遍历
        System.out.println("后序遍历:");
        binaryTree.postOrder(); //后序遍历
    }
}

二叉树的查找:

查找的思路和遍历的思路类似,只是多了一步判断查找条件是否相等。查找也分为三种,前序查找、中序查找和后序查找,三种查找的区别还是根据处理父结点的顺序。

前序查找:

  1. 先判断当前结点的no值是否等于要查找的no值,如果相等,就返回当前结点;
  2. 如果不等,先判断当前结点的左结点是否为空,不为空就向左递归前序查找,如果找到,返回当前结点,
  3. 如果没找到,先判断当前结点的右子结点是否为空,不为空就向右子结点递归前序查找,如果找到,就返回当前结点,
  4. 没找到就返回null。
  5. 注:一定要有先判断左右结点是否为空这个条件,否则会出现空指针异常。

中序查找:

  1. 先判断当前结点的左子结点是否为空,不为空则向左结点递归中序查找,如果相等,就返回当前结点,
  2. 如果不等,就和当前结点比较,
  3. 如果当前结点和要查找的no值相等,就返回,不等就先判断右子节点是否为空,不为空则向右结点递归中序查找,
  4. 向右结点递归中序查找,找到就返回当前结点的右子结点,找不到返回null。

后序查找思路:

  1. 先判断当前结点的左子结点是否为空,不为空则向左结点递归后序查找,如果相等,就返回当前结点的左子结点,
  2. 如果不等,就判断当前结点的右子节点是否为空,如果不为空就向右子节点递归后序查找,
  3. 如果找到就返回,没找到就和当前结点比较,如果找到就返回,没找到就返回null。

class HeroNode{
    private int no;
    private String name;
    private HeroNode left; // 左结点
    private HeroNode right; // 右结点

    // 省略getter、setter和构造方法等

    // 查找:
    // 前序查找
    public HeroNode preOrderSearch(int id){
        // 1.判断id和当前结点是否相等
        if (this.no == id){
            return this;
        }
        // 2.当前结点没有找到,向左子结点递归前序查找
        HeroNode temp = null; // 用来接收递归是否找到相等元素
        if (this.left != null){
            temp = this.left.preOrderSearch(id);
        }
        // 向左子结点递归找到了
        if (temp != null){
            return temp;
        }
        // 3.当前左子结点没有找到,向右子结点递归前序查找
        if (this.right != null){
            temp = this.right.preOrderSearch(id);
        }
        // 返回右子结点是否找到,不用判断temp是否为null,可以在函数调用的地方来判断
        return temp;
    }
    // 中序查找
    public HeroNode infixOrderSearch(int id){
        // 1.当前结点的左子结点是否相等,判断是否找到
        HeroNode temp = null; // 用来判断递归结果是否找到
        if (this.left != null){
            temp =  this.left.infixOrderSearch(id);
        }
        if (temp != null){
            return temp;
        }
        // 2.当前结点是否相等
        if (this.no == id){
            return this;
        }
        // 3.当前结点的右子节点是否相等,判断是否找到
        if (this.right != null){
            temp = this.right.infixOrderSearch(id);
        }
        return temp;
    }

    // 后序查找
    public HeroNode postOrderSearch(int id){
        // 1.当前结点的左子结点是否相等,判断是否找到
        HeroNode temp = null; // 用来判断递归结果是否找到
        if (this.left != null){
            temp =  this.left.infixOrderSearch(id);
        }
        if (temp != null){
            return temp;
        }
        // 2.当前结点的右子节点是否相等,判断是否找到
        if (this.right != null){
            temp = this.right.infixOrderSearch(id);
        }
        if (temp != null){
            return temp;
        }
        // 3.当前结点是否相等
        if (this.no == id){
            return this;
        }
        return temp;
    }
}
class BinaryTree{
    private HeroNode root; // 根结点

    public BinaryTree(HeroNode root){ // 指定根结点
        this.root = root;
    }

    // 前序查找
    public HeroNode preOrderSearch(int id){
        if (this.root != null){
            return this.root.preOrderSearch(id);
        }
        return null;
    }
    // 中序查找
    public HeroNode infixOrderSearch(int id){
        if (this.root != null){
            return this.root.infixOrderSearch(id);
        }
        return null;
    }
    // 后序查找
    public HeroNode postOrderSearch(int id){
        if (this.root != null){
            return this.root.postOrderSearch(id);
        }
        return null;
    }
}
public class BinaryTreeDemo {
    public static void main(String[] args) {
        HeroNode heroNode1 = new HeroNode(1,"宋江"); // 根结点
        HeroNode heroNode2 = new HeroNode(2,"吴用"); // 根节点的左子结点
        HeroNode heroNode3 = new HeroNode(3,"武松"); // 根节点的右子结点
        HeroNode heroNode4 = new HeroNode(4,"林冲"); // 根节点的右子结点的右子结点
        BinaryTree binaryTree = new BinaryTree(heroNode1);
        heroNode1.setLeft(heroNode2);
        heroNode1.setRight(heroNode3);
        heroNode3.setRight(heroNode4);
       
        // 查找
        System.out.println("前序查找:");
        if (binaryTree.preOrderSearch(2) != null){
            System.out.println("找到了,"+binaryTree.preOrderSearch(2));// 前序查找
        }else {
            System.out.println("没有找到");
        }

        System.out.println("中序查找:");
        if (binaryTree.preOrderSearch(2) != null){
            System.out.println("找到了,"+binaryTree.infixOrderSearch(2));// 中序查找
        }else {
            System.out.println("没有找到");
        }

        System.out.println("后序查找:");
        if (binaryTree.preOrderSearch(2) != null){
            System.out.println("找到了,"+binaryTree.postOrderSearch(2)); // 后序查找
        }else {
            System.out.println("没有找到");
        }

    }
}

如果想要比较这三种查找的优越性,可以在if(this.no = id)上面添加一个变量来判断它们各自递归了几次,这里就不演示了。注意的是一定是在if(this.no = id)上,因为不论怎么递归最终目的都是判断当前结点是否相等。

二叉树的删除:

这里的删除指的是递归删除结点,我们暂且规定,(1)如果删除的结点是叶子结点,那么删除该节点;(2)如果删除的结点不是叶子结点,那么删除该子树。(当然我们可以把子节点转为根节点,这里先不讨论那种情况)

满足上面两个条件的删除结点的思路:

  1. 首先考虑,如果树是空树或者只有一个根节点root,直接将root置空即可,否则递归删除。
  2. 因为二叉树是单向的,所以我们判断当前结点的子结点是否为需要删除的结点,而不能去判断当前结点是不是需要删除的结点。(这是因为,二叉树单向,所以子结点找不到父结点,比如我们要删除5号,我们找不到5号的父结点,没法置为null)
  3. 如果当前结点的左子结点不为空,并且左子结点就是要删除的结点,就将this.left = null,然后返回,递归结束,
  4. 如果当前结点的右子结点不为空,并且右子结点就是要删除的结点,就将this.right = null,然后返回,递归结束,
  5. 如果3和4没有删除掉,就需要向左子树递归删除,
  6. 如果5还没有删掉,就需要向右子树递归删除。

class HeroNode{
    private int no;
    private String name;
    private HeroNode left; // 左结点
    private HeroNode right; // 右结点

    // 省略getter、setter和构造方法等

    
    /*  删除结点要求:(1)如果删除的结点是叶子结点,那么删除该节点;
                     (2)如果删除的结点不是叶子结点,那么删除该子树。
        0.首先考虑,如果树是空树或者只有一个根节点root,直接将root置空即可。
        我们把0放到BinaryTree中    */

    public void delNode(int id){
        // 1.如果当前结点的左子结点不为空,并且左子结点就是要删除的结点,就将this.left=null,然后递归结束
        if (this.left != null && this.left.no == id){
            this.left = null;
            return;
        }
        // 2.如果当前结点的右子结点不为空,并且右子结点就是要删除的结点,就将this.right=null,然后递归结束
        if (this.right != null && this.right.no == id){
            this.right = null;
            return;
        }
        // 3.如果1和2没有删除掉,就需要向左子树递归删除
        if (this.left != null){
            this.left.delNode(id);
        }
        // 4.如果3还没有删掉,就需要向右子树递归删除
        if (this.right != null){
            this.right.delNode(id);
        }
    }
}
class BinaryTree{
    private HeroNode root; // 根结点

    public BinaryTree(HeroNode root){ // 指定根结点
        this.root = root;
    }

   
    // 删除结点
    public void delNode(int id){
        // 0.首先考虑,如果树是空树或者只有一个根节点root,直接将root置空即可,否则递归删除
        if (this.root != null){
            if (root.getNo() == id){ //只有一个根节点root
                root = null;
            }else {
                root.delNode(id);
            }
        }else{
            System.out.println("二叉树为空,无法删除");
        }
    }
}
public class BinaryTreeDemo {
    public static void main(String[] args) {
        HeroNode heroNode1 = new HeroNode(1,"宋江"); // 根结点
        HeroNode heroNode2 = new HeroNode(2,"吴用"); // 根节点的左子结点
        HeroNode heroNode3 = new HeroNode(3,"武松"); // 根节点的右子结点
        HeroNode heroNode4 = new HeroNode(4,"林冲"); // 根节点的右子结点的右子结点
        HeroNode heroNode5 = new HeroNode(5,"李逵"); // 根节点的右子结点的右子结点
        BinaryTree binaryTree = new BinaryTree(heroNode1);
        heroNode1.setLeft(heroNode2);
        heroNode1.setRight(heroNode3);
        heroNode3.setLeft(heroNode4);
        heroNode3.setRight(heroNode5);

        int delNode = 2;
        System.out.println("删除二叉树中的结点:"+delNode);
        binaryTree.delNode(delNode);
        System.out.println("删除后的二叉树遍历:");
        binaryTree.preOrder();

    }
}

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值