树及二叉树数据结构

Posted 一朵花花

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了树及二叉树数据结构相关的知识,希望对你有一定的参考价值。

树形结构

我们之前研究的基本都是一对一的问题,而树是一种非线性数据结构,它是由n(n≥0)个结点组成的具有有层次关系的有限集

特点:
当 n=0 时,称为空树,在任何一个非空树中:

  • 有且只有一个根结点
  • 当 n>1 时,其余结点可分为 m(m>0) 个互不相交的有限集T1,T2,…Tm,其中每一个集合本身又是一棵树,并且称为根的子树(SubTree),每棵子树的根节点有且只有一个前驱,可以有0个或多个后继
    如图所示:
    .
  • 树是递归定义的

概念

名称概念
节点的度:一个节点含有的子树的个数称为该节点的度; 如上图:A的为2,D的为3
树的度:一棵树中,最大的节点的度称为树的度; 如上图:树的度为3
叶子节点或终端节点:度为 0 的节点称为叶子节点; 如上图:G、H、I、F…等节点为叶节点
双亲节点或父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点; 如上图:A是B的父节点
孩子节点或子节点:一个节点含有的子树的根节点称为该节点的子节点; 如上图:B是A的孩子节点
根节点:一棵树中,没有双亲节点的结点;如上图:A
节点的层次:从根开始定义起,根为第1层,根的子节点为第2层,以此类推
树的高度或深度:树中节点的最大层次; 如上图:树的高度为4
非终端节点或分支节点:度不为0的节点; 如上图:B、C、D、E…等节点为分支节点
兄弟节点:具有相同父节点的节点互称为兄弟节点; 如上图:B、C是兄弟节点
堂兄弟节点:双亲在同一层的节点互为堂兄弟;如上图:D、E互为兄弟节点
节点的祖先:从根到该结点所经分支上的所有节点;如上图:A是所有节点的祖先
子孙:以某节点为根的子树中任一节点都称为该节点的子孙。如上图:所有节点都是A的子孙
森林:由 m(m≥0) 棵互不相交的树的集合称为森林

树和非树(图)的区别

  • 子树是不相交的
  • 除了根节点外,每个节点有且仅有一个父节点
  • 一颗 n 个节点的树,有 n-1 条边

树的表示形式

最常用的方法—孩子兄弟表示法:

class Node{
    int value;  //树中存储的数据
    Node firstChild;  //树中第一个孩子引用
    Node nextBrother; //下一个兄弟引用
}

画图表示:

二叉树

概念和特点

概念:

对于在某个阶段都是两种结果的情形,比如:0和1、真和假、上和下、对与错、正和反等,都适合用树状结构来建模,而这种树是很特殊的树状结构,叫做二叉树

二叉树(Binary Tree)是 n(n≥0) 个节点的有限集合,该集合或者为空集(称为空二叉树),或者由一个根节点和两棵互不相交的、分别称为根节点的左子树和右子树的二叉树组成

特点:

  • 每个节点最多有两棵子树,二叉树的度大能超过 2
    注意不是只有两棵子树,而是最多有,没有子树或者有一棵子树也是可以的
  • 二叉树的子树有左右之分,其子树的次序不能任意颠倒,因此二叉树是有序树
  • 即使树中某节点只有一棵子树,也要区分它是左子树还是右子树

二叉树的基本形态:

  • 空二叉树
  • 只有一个根节点
  • 根节点只有左子树
  • 根节点只有右子树
  • 根结点既有左子树又有右子树

特殊的二叉树:

满二叉树:

一个二叉树,若每一个层的节点数都达到最大值(即:2),则就为满二叉树
一个二叉树的层数为 n,且节点总数是 2n-1


满二叉树的特点:

  • 叶子只出现在最下一层,出现在其他层就不可能达到平衡
  • 非叶子节点的度一定是2
  • 在同样深度的二叉树中,满二叉树的节点个数最大,叶子个数也最多

完全二叉树:

直观来说:相比于满二叉树来说,完全二叉树右小角缺了一块

完全二叉树是效率很高的数据结构,对于深度为K的,有n个节点的二叉树,当且仅当其每一个节点都与深度为K的满二叉树中编号从1至n的节点一一对应时称之为完全二叉树,满二叉树是一种特殊的完全二叉树

完全二叉树的特点:

  • 叶子节点只能出现在最下两层
  • 最下层的叶子一定集中在左边连续位置
  • 倒数第二层,如果有叶子节点,一定出现在右边连续位置
  • 若节点度为1,则该节点只有左子树
  • 同样节点数的二叉树,完全二叉树的深度最小

二叉树的相关操作

二叉树的表示

可通过左右孩子来表示

class Node{
    int val;      //存储的数据
    Node left;    //左子树根节点的引用
    Node right;   //右子树根节点的引用
    Node parent;  //双亲的引用(可选)
}

二叉树的遍历🔺

遍历: 遍历按照一定的顺序访问到集合中的每个元素,不重复不遗漏
由于二叉树不是线性结构,故遍历起来相对复杂

手动构造一棵树:

class Node{
    char val;      //存储的数据
    Node left;    //左子树根节点的引用
    Node right;   //右子树根节点的引用
    Node parent;  //双亲的引用(可选)

    //提供构造方法
    public Node(char val) {
        this.val = val;
    }
}

//构建一棵树
public static Node buildTree(){
    Node a = new Node('A');
    Node b = new Node('B');
    Node c = new Node('C');
    Node d = new Node('D');
    Node e = new Node('E');
    
    a.left = b;
    a.right = c;
    b.left = d;
    b.right = e;

    return a;
}

1.先序遍历

先访问根节点,递归遍历左子树,递归遍历右子树 (根左右)

例图:


代码实现:

public static void preOrder(Node root){
    //若为空树,直接返回
    if(root == null){
        return;
    }
    //先访问根节点
    System.out.print(root.val+" ");
    
    //递归访问左子树
    preOrder(root.left);
    //递归访问右子树
    preOrder(root.right);
}

2.中序遍历

先递归遍历左子树,访问根节点,再递归遍历右子树 (左根右)

例图:

public static void inOrder(Node root){
    //若为空树,直接返回
    if(root == null){
        return;
    }
    //递归访问左子树
    inOrder(root.left);
    //访问根节点
    System.out.print(root.val+" ");
    //递归访问右子树
    inOrder(root.right);
}

3.后序遍历

先递归遍历左子树,再递归遍历右子树,最后访问根节点 (左右根)

例图:

public static void postOrder(Node root){
    //若为空树,直接返回
    if(root == null){
        return;
    }
    //递归访问左子树
    postOrder(root.left);
    //递归访问右子树
    postOrder(root.right);

    //访问根节点
    System.out.print(root.val+" ");
}

4.层序遍历

一层一层往下遍历,每层从左向右访问,为非递归操作

public static void levelOrder(Node root){
    //创建一个空队列并把根节点加入队列中
    Queue<Node> queue = new LinkedList<>();
    queue.offer(root);
    while( !queue.isEmpty()){
        //队列非空,取出队首元素
        Node cur = queue.poll();
        //访问元素
        System.out.print(cur.val + " ");

        //把左右子树入队列
        if(cur.left != null){
            queue.offer(cur.left);
        }
        if(cur.right != null){
            queue.offer(cur.right);
        }
    }
}

例图:

遍历规律:

  • 先序遍历,第一个访问的节点一定是根节点
  • 后续遍历,最后一个访问的节点一定是根节点
  • 中序遍历和后序遍历,第一个访问的节点是树的最左侧节点
  • 针对先 / 后序遍历来说,子树的遍历结果是嵌套在整个遍历结果中的
  • 中序遍历,左子树的遍历结果在根节点的左侧,右子树的遍历结果在根节点的右侧

输出结果:

求二叉树节点个数

public static int size(Node root){
    if(root == null){
        return 0;
    }
    //树节点总数 = 根节点个数(1) + 左子树节点个数 + 右子树节点个数
    return 1 + size(root.left) + size(root.right);
}

输出结果:5

画图分析:

执行顺序和"后序遍历"一模一样

求二叉树叶子节点的个数

叶子节点:即度为0的节点

public static int leafSize(Node root){
    //空树
    if(root == null){
        return 0;
    }
    //只有根节点时,根节点就是唯一的叶子节点
    if(root.left == null && root.right == null){
        return 1;
    }
    // root叶子节点个数 = root.left叶子节点个数 + root.right叶子节点个数
    return leafSize(root.left) + leafSize(root.right);
}

输出结果:3

画图分析:

求第 K 层节点个数

public static int kLevelSize(Node root,int k){
    //若 k<1 或root为空,则为空树
    if(k < 1 || root == null){
        return 0;
    }
    //若 k=1,即为根节点
    if(k == 1){
        return 1;
    }
    // k 层节点个数 = 左子树的(k-1)层节点个数 + 右子树的(k-1)层节点个数
    return kLevelSize(root.left,k-1) + kLevelSize(root.right,k-1);
}

输出结果:2

画图分析:

在二叉树中寻找指定元素

public static Node find(Node root,char toFind){
    if(root == null){
        return null;
    }
    if(root.val == toFind){
        return root;
    }
    //递归查找左右子树
    Node result = find(root.left,toFind);
    if(result != null){
        return result;
    }
    return find(root.right,toFind);
}

画图分析:

完全二叉树判定

public boolean isCompleteTree(TreeNode root){
    //空树判断
    if(root == null){
        return true;
    }
    //第二阶段初始为 false
    boolean secondStep = false;

    //层序遍历
    Queue<TreeNode> queue = new LinkedList<>();
    queue.offer(root);
    while( !queue.isEmpty()){
        TreeNode cur = queue.poll();
        //判断是否满足完全二叉树的要求

        //第一阶段
        if( !secondStep){
            if(cur.left != null && cur.right != null){
                queue.offer(cur.left);
                queue.offer(cur.right);
            }
            //只有右子树
            else if(cur.left == null && cur.right != null){
                return false;
            }
            //只有左子树
            else if(cur.left != null && cur.right == null){
                //进入第二阶段
                secondStep = true;
                queue.offer(cur.left);
            }
            //左右子树都为空
            else{
                secondStep = true;
            }
        }

        //第二阶段
        else{
            //任何一个节点都没有子树
            if(cur.left != null || cur.right != null){
                return false;
            }
        }
    }
    return true;
}

画图分析:

下篇会讨论二叉树的相关面试题~~

以上是关于树及二叉树数据结构的主要内容,如果未能解决你的问题,请参考以下文章

数据结构与算法笔记(十四)—— 二叉树及二叉树遍历

(王道408考研数据结构)第五章树-第二节1:二叉树的定义特殊的二叉树及二叉树性质

一文学会树及二叉树的操作

复原二叉树 题解

[DataStructure]哈希表二叉树及多叉树 Java 代码实现

数据结构—— 树:二叉树及存储结构