集合算法基础--二叉树(上)

 点击上方「10分钟编程」关注我呦

让我们在一起每天「博学」一点点,成为更好的自己!

二叉树

1、问题描述

树是一种数据结构,也是用于存储的,在已有数组、链表的情况下,为什还需要树呢?它有什么优点?那么我们先看一下数组、链表都有什么特点。

  • 数组

优点:根据下标检索时速度很快,当序列有序时,可以采用二分查找、斐波那契查找提高检索效率

缺点:插入(扩容、移动等操作)、删除(移动)效率低下。

  • 链表

优点:删除、插入时效率高,不需要连续的存储空间

缺点:检索时效率低

那么,有没有一种数据结构同时具有数组和链表的优点呢?来,我们看一下的树的特点。

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

在学习二叉树之前,我们先学习下树。

2、树的概念

  • 结点

  • 根结点,A是所有节点的根节点,一棵树只有一个根结点

  • 父结点,A是结点B和结点C的父结点,B是结点D和结点E的父结点

  • 子结点,结点B和结点C是结点A的子结点

  • 叶子结点,没有子结点的结点

  • 结点的权,结点的值,

  • 路径,从某一个节点到另一结点的路线

  • 层,如图,共4层

  • 子树,结点D和结点H可以构成子树,其子树的根节点是D

  • 树的高度,最大层数

  • 森林,多颗子树构成森林

3.二叉树

定义:每个结点最多只能有两个子结点(左结点和右结点)的形式

3.1满二叉树

定义:所有的叶子结点都在最后一层,结点数为2^n-1,n为层数

3.2完全二叉树

定义:如果二叉树的所有叶子结点都在最后一层或者倒数第二层,最后一层的叶子结点左边连续,倒数第二层的叶子结点右边连续,称为完全二叉树

3.4二叉树遍历

  • 前序遍历

特点:先输出父结点,在输出左结点、最后输出右结点

代码实现

   public void preOrder() {
        System.out.println(this);
        if (this.leftNode != null) {
            this.leftNode.preOrder();
        }
        if (this.rightNode != null) {
            this.rightNode.preOrder();
        }
    }
  • 中序遍历

特点:先输出左结点,再输出父结点,最后输出右结点

代码实现

   public void infixOrder() {
        if (this.leftNode != null) {
            this.leftNode.infixOrder();
        }
        System.out.println(this);
        if (this.rightNode != null) {
            this.rightNode.infixOrder();
        }
    }
  • 后序遍历

特点:先输出左结点,再输出右结点,最后输出父结点

 public void postOrder() {
        if (this.leftNode != null) {
            this.leftNode.postOrder();
        }
        if (this.rightNode != null) {
            this.rightNode.postOrder();
        }
        System.out.println(this);
    }

3.4二叉树查找指定结点

  • 前序遍历查找

public PersonNode preOrderSearch(int no) {
        //判断当前节点是否为要查找的节点
        if (this.no == no) {
            return this;
        }
        //判断当前节点的左节点是否为空,不为空时继续左递归查找
        PersonNode resNode = null;
        if (this.leftNode != null) {
            resNode = this.leftNode.preOrderSearch(no);
        }
        //判断左递归后查询的节点是为空
        if (resNode != null) {
            return resNode;
        }
        //若左递归查询无果,则向右递归,直接返回结果
        if (this.rightNode != null) {
            resNode = this.rightNode.preOrderSearch(no);
        }
        return resNode;
    }
  • 中序遍历查找

public PersonNode infixOrderSearch(int no) {
        PersonNode resNode = null;
        //判断当前节点的左节点是否为空,不为空时进行中序递归判断
        if (this.leftNode != null) {
            resNode = this.leftNode.infixOrderSearch(no);
        }
        //判断递归后的值是否为空,不为空,则证明在左子树上找到
        if (resNode != null) {
            return resNode;
        }
        //判断当前结点是否为自己想要查找的
        if (this.no == no) {
            return this;
        }
        //若坐左子树和当前结点都不是我们要查询的,则判断右结点是否为空,不为空时,则进行中序查找递归
        if (this.rightNode != null) {
            resNode = this.rightNode.infixOrderSearch(no);
        }
        return resNode;
    }
  • 后序遍历查找

  public PersonNode postNodeSearch(int no) {
        PersonNode resNode = null;
        //判断当前结点的左结点是否存在,若存在则进行左结点的后续遍历查找递归
        if (this.leftNode != null) {
            resNode = this.leftNode.postNodeSearch(no);
        }
        //左子树查找到
        if (resNode != null) {
            return resNode;
        }
        //若没有左子树查找到,则判断右结点是否存在,存在时进行后序遍历查找递归
        if (this.rightNode != null) {
            resNode= this.rightNode.postNodeSearch(no);
        }
        //右子树查找到
        if(resNode!=null){
            return resNode;
        }
        //判断当前节点是否为要查找的结点
        if (this.no == no) {
            resNode= this;
        }
        return resNode;
    }

3.5二叉树指定结点的删除

前提:先判断根结点,根结点的判断不在此代码中。

public void delNode(int no){
        //判断左边结点是否为空,非空时判断是否为要查找的结点
        if(this.leftNode!=null&&this.leftNode.no==no){
            this.leftNode=null;
        }
        //判断右边结点是否为空,非空时判断是否为要查找的结点
        if(this.rightNode!=null&&this.rightNode.no==no){
            this.rightNode=null;
        }
        //判断左子结点是非为空,不为空时进行递归查找删除
        if(this.leftNode!=null){
            this.leftNode.delNode(no);
        }
        //判断右子结点是非为空,不为空时进行递归查找删除
        if(this.rightNode!=null){
            this.rightNode.delNode(no);
        }
    }

3.5顺序存储二叉树

概念:从数据存储上看,数组存储的方式与树的存储方式可以相互转换

给定一数组,完成二叉树的遍历

顺序存储二叉树通常只考虑完全二叉树,n为该节点在数组中的下标。

1、第n个元素的左子结点为n*2+1

2、第n个元素的右子结点为n*2+2

3、第n个元素的父结点为(n-1)/2

  • 前序遍历

public void preOrder(int index) {
        if (arr != null && arr.length > 0) {
            System.out.println(arr[index]);
            if (index * 2 + 1 < arr.length) {
                //在左结点递归
                preOrder(index * 2 +1);
            }
            if (index * 2 + 2 < arr.length) {
                //在右结点递归
                preOrder(index * 2 +2);
            }
        }
    }
  • 中序遍历

  public void infixOrder(int index) {
        if (arr != null && arr.length > 0) {
            if (index * 2 + 1 < arr.length) {
                //在左结点递归
                infixOrder(index * 2 +1);
            }
            System.out.print(arr[index]);
            if (index * 2 + 2 < arr.length) {
                //在右结点递归
                infixOrder(index * 2 +2);
            }
        }
    }
  • 后序遍历

 public void postOrder(int index) {
        if (arr != null && arr.length > 0) {
            if (index * 2 + 1 < arr.length) {
                //在左结点递归
                postOrder(index * 2 +1);
            }
            if (index * 2 + 2 < arr.length) {
                //在右结点递归
                postOrder(index * 2 +2);
            }
            System.out.print(arr[index]);
        }
    }

3.6线索化二叉树的构造

概念:有N个结点的二叉树会存在N+1个空指针,可以将这些空指针理解为线索(每个结点有2个指针,共2N个指针,将N个结点连接起来需要N-1个指针,所以剩余指针为:2N-(N-1)=N+1)。而二叉树线索化的过程中,会利用树中的空指针作为寻找当前结点的前驱和后继的线索。为了区分树中的指针是指向子树还是线索,定义了leftType和rightType标识符,其意义如下:

  • 如果leftType为0,则表示指向左子结点;若leftType=1,则表示指向前驱结点。

  • 如果rightType为0,则表示指向右子结点;若leftType=1,则表示指向后继结点。

图解如下:图解采用采用中序遍历的方式构造线索化二叉树,同理,有前序构造二叉树和后序构造二叉树

蓝色实线代表左右子树,红色虚线代表前继节点,绿色虚线代表后继结点。

  //定义当前结点的前驱结点
    PersonNode pre=null;
    //参数中的node为需要线索化的结点
    public void threadedNodes(PersonNode node){
        if(node==null){
            return;
        }
        //线索化左子树
        threadedNodes(node.getLeftNode());
        //线索化当前结点
        if(node.getLeftNode()==null){
            //让当前结点的左指针指向前驱结点
            node.setLeftNode(pre);
            //修改当前结点的左指针类型
            node.setLeftNodeType(1);
        }
        //处理后继结点
        if(pre!=null&&pre.getRightNode()==null){
            //让前驱结点的右指针指向当前结点
            pre.setRightNode(node);
            //修改前驱结点的右指针类型
            pre.setRightNodeType(1);
        }
        //每处理一个结点后,让当前结点是下一个结点的前驱结点。
        pre=node;
        //线索化右子树
        threadedNodes(node.getRightNode());
    }

3.7线索化二叉树的查找

线索化二叉树后,各个结点的指向均会发生变化,原来的遍历(前序、中序和后序)方式不能在使用。我们需要使用新的遍历方法。可以采用线性查找的遍历的方法,不需要再使用递归的方法。

修改中序遍历方法遍历线索化二叉树

 public void listThreadedBinaryTree() {
        PersonNode node = root;
        while (node != null) {
            while (node.getLeftNodeType() == 0) {
                node = node.getLeftNode();
            }
            System.out.println(node);

            while (node.getRightNodeType() == 1) {
                node = node.getRightNode();
                System.out.println(node);
            }
            node = node.getRightNode();
        }

    }

笔者组建了技术交流群,关注公众号,欢迎大家加入,一起进步。

在公众号回复success领取独家整理的学习资源

看了这篇文章,你是否「博学」了

「扫码关注我」,每天博学一点点。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值