树之二叉树

143 阅读6分钟

树的基本概念

树是数据结构中非常重要的一个存在。在Java中对树进行了大量的使用,如TreeMap,HashMap等。并且如MySQL、MongoDB也都使用到了树,那么树到底是什么?

树是由N个节点组成的,每个节点中都会进行数据的存储。当N=0时,被称为空树。在任何一颗树中,有且只有一个根节点,在根节点下可以继续进行扩展,形成互不相交的集合。其中每个集合本身可以理解为是一棵树,他们则被称为子树。

image.png

节点树中的元素
节点的度节点拥有子树的个数,二叉树的度不能大于2
高度叶子节点的高度为1,叶子节点的父节点高度为2,依次类推
根节点树的顶部节点
子节点父节点的下层节点
叶子节点没有子节点的节点,也被称为终端节点、度为0的节点。

树的种类也非常多:二叉树、平衡二叉树、红黑树、B树、B+树、哈夫曼树、B*树等等。

二叉树

基本概念

二叉树的特点是树的每个节点最多只能有两个子节点。其中一棵树叫做根的左子树,另一根叫根的右子树。

image.png

如果节点数量超过2个,则不能叫做二叉树,而叫多路树。

image.png

特点

每个节点最多有两颗子树,所以二叉树中不存在度(该节点孩子的个数)大于2的节点。 左子树和右子树是有顺序的。

即使树中某节点只有一颗子树,也要区分它是左子树还是右子树

特殊的二叉树

满二叉树

在一棵二叉树中。如果所有分支结点都存在左子树和右子树,并且所有叶子都在同一层上,这样的二叉树被称为满二叉树。其特点如下:

  • 假设深度为K,且含有2^k-1个节点的树
  • 叶子只能出现在最后一层,出现在其它层就不能达成平衡
  • 非叶子节点的度一定是2
  • 在同样深度的二叉树中,满二叉树的节点最多,叶子节点最多

image.png

完全二叉树

完全二叉树是一颗特殊的二叉树。假设一个二叉树的高度为h,如果说它是一颗完全二叉树的话,需要满足以下规则:

  • 叶子节点只能出现在最下层和次下层
  • 最下层的叶子节点集中在树的左部
  • 倒数第二层若存在叶子节点,一定在右部连续位置
  • 如果节点度为1,那么该节点只有左孩子,即没有右子树

image.png

斜树

所有的节点都之后左子树的二叉树叫做左斜树,同理存在右斜树,相当于树结构退化为了链表,查找一个节点的时间复杂度为O(n),查询效率严重降低。

image.png

存储结构

二叉树的顺序存储结构就是使用一堆数组存储二叉树中的节点,并且节点的存储位置就是数组的索引。

image.png

当二叉树为完全二叉树时,节点数刚好填满数组。

image.png

当不为完全二叉树时,采用顺序存储又是什么样子的呢?

image.png

其中,^表示数组中此位置没有储存节点,此时可以发现,顺序存储结构中出现了空间浪费的问题,在极端的右斜树极端情况下对应的顺序存储结构。

image.png

可以看出,对于这种右斜树极端情况下,采用顺序存储的方式是十分浪费空间的,因此,顺序存储一般适用于完全二叉树。

二叉树遍历

定义

二叉树的遍历是指二叉树的根节点出发,按照某种次序访问二叉树中的所有节点,使得每个节点被访问一次,且仅被访问一次。

二叉树的访问次序可以分为四种:

  • 前序遍历
  • 中序遍历
  • 后序遍历
  • 层序遍历

前序遍历

从根节点出发,当第一次到达节点时就会输出节点数据,按照先向左在向右的方向访问。简单理解就是:父节点-->左子树-->右子树

image.png

流程:

  • 从根节点出发,则第一次到达节点A,输出A
  • 继续向左访问,第一次访问节点B,输出B
  • 按照同样的规则,输出D,输出H
  • 当到达叶子节点H,返回D,此时已经是第二次访问D了,则不输出D。继续向右,输出I
  • I为叶子节点,返回D。此时D的左右子树已经访问完毕,则返回B,访问B的右子树,输出E
  • 向E的左子树,输出J
  • 安装同样的规则,继续输出C、F、G。

最终结果为:ABDHIEJCFG

Java实现前序遍历代码:

public class Traverse {

    private static Node root;

    private static boolean flag = false;

    //数据初始化
    static {
        root = new Node("A");
        root.setLeftNode(new Node("B"));
        root.setRightNode(new Node("C"));
        root.getLeftNode().setLeftNode(new Node("D"));
        root.getLeftNode().setRightNode(new Node("E"));
        root.getLeftNode().getLeftNode().setLeftNode(new Node("H"));
        root.getLeftNode().getLeftNode().setRightNode(new Node("I"));
        root.getLeftNode().getRightNode().setLeftNode(new Node("J"));
        root.getRightNode().setLeftNode(new Node("F"));
        root.getRightNode().setRightNode(new Node("G"));
    }

    //前序遍历
    public static void prologue(Node root){
        if(root == null){
            return;
        }
        if (!flag){
            System.out.println(root.getData());
            flag = true;
        }
        //遍历左子树
        if (root.getLeftNode() != null){
            System.out.println(root.getLeftNode().getData());
            prologue(root.getLeftNode());
        }
        // 遍历右子树
        if (root.getRightNode() != null){
            System.out.println(root.getRightNode().getData());
            prologue(root.getRightNode());
        }
    }

    public static void main(String[] args) {
        prologue(root);
    }

}

image.png

中序遍历

从根节点出发,当第二次到达节点时则输出节点数据,按照先向左再向右的方向访问。简单理解就是:左子树->父节点->右子树

image.png

流程如下:

  • 从根节点出发,则第一次到达A,不能输出A,继续向左访问,第一次到达B,不能输出B,继续到达D、H都不进行输出。
  • 到达H,H左子树为空,则返回到H,此时是第二次访问H,则输出H
  • H右子树为空,则返回D,此时第二次到达D,则输出D
  • D存在右子树,则向右到达I,I的左子树为空,则返回到I,此时是第二次访问I,输出I
  • I的右子树为空,则返回D,此时是第三次到达D,不做输出
  • 继续向上,到达B,此时是第二次到达B,则输出B
  • 按照同样的规则,后续输出J、E、A、F、C、G。

最终结果为:HDIBJEAFCG

    //中序遍历 左 中 右
    public static void middle(Node root){
        if (root == null){
            return;
        }
        if (root.getLeftNode() != null){
            middle(root.getLeftNode());
        }
        System.out.print(root.getData() + " ");
        if (root.getRightNode() != null){
            middle(root.getRightNode());
        }
    }

    public static void main(String[] args) {
        middle(root);
    }

后序遍历

从根节点出发,当第三次到达节点时输出,按照先左后右的方式访问,简单理解就是左子树->右子树->父节点

image.png

流程如下:

  • 从根节点出发,则第一次到达节点A,不能输出A,继续向左访问,到达BDH
  • 到达H,H左子树为空,则返回到H,此时第二次访问H,不能做输出
  • 到达H,H右子树为空,则返回到H,此时第三次访问H,输出H,
  • H返回D,第二次到达D,不能输出
  • 继续访问I,同时I的左右子树均为空,第三次时,输出I
  • I返回到D,第三次到达D,输出D,
  • 按照同样规则输出J E B F G C A 最终结果为:HIDJEBFGCA
    //后序遍历 左 右 中
    public static void postSequence(Node root){
        if (root == null){
            return;
        }
        if (root.getLeftNode() != null){
            postSequence(root.getLeftNode());
        }
        if (root.getRightNode() != null){
            postSequence(root.getRightNode());
        }
        System.out.print(root.getData()+" ");
    }

层序遍历

按照树的层次自上而下的遍历二叉树。 最终结果为:ABCDEFGHIJ

    //层序遍历
    public static void sequence(Node root){
        Queue<Node> queue = new LinkedList<>();
        queue.offer(root);
        while (!queue.isEmpty()) {
            Node treeNode = queue.poll();
            System.out.print(treeNode.getData() + " ");
            if (treeNode.getLeftNode() != null) {
                queue.offer(treeNode.getLeftNode());
            }
            if (treeNode.getRightNode() != null) {
                queue.offer(treeNode.getRightNode());
            }
        }
    }