数据结构之二叉树

目录

一.树形结构

1.1概念

1.2树的术语

1.3树的表示形式

双亲表示法,孩子表示法,孩子双亲表示法,孩子兄弟法 

二.二叉树

2.1概念

2.2二叉树的性质:

2.3两种特殊的二叉树

1.满二叉树

2.完全二叉树

2.3二叉树的存储 

1.链式存储

 2.顺序存储

2.4二叉树的基本操作 

1.二叉树的构建

2.二叉树的遍历(递归&&非递归实现)

2.1前序遍历

2.2中序遍历

2.3.后序遍历

 2.4.层序遍历

3.求节点个数

 4.求叶子节点个数

5.求第k层的节点个数

 6.求值是否存在二叉树中

7.判断一棵树是不是完全二叉树


一.树形结构

1.1概念

树是一种非线性的数据结构,是由n(n>=0)个有限结点组成一个具有层次关系的集合。

1.2树的术语

  • 结点的度:一个结点拥有子树的个数称为该节点的度;如下图,A的度为6.
  • 树的度:一棵树中,所有结点度的最大值称为树的度;如下图,树的度为6.
  • 叶子结点或终端结点:度为0的结点称为叶子结点;如下图,B,C,H,I,P,D等都为叶子结点。
  • 双亲节点或父结点:若一个结点含有子节点,则这个结点称为其子节点的父节点;如下图,D是H的父结点。
  • 孩子节点或子结点:一个节点含有的子树的根结点称为该节点的子节点;如下图:H是D的子节点。
  • 根结点:一棵树中,没有双亲节点的节点;如下图,A是根节点。
  • 结点的层次:从根开始,根为第一层,根的子节点为第二层,以此类推。
  • 树的高度或深度:树中节点的最大层次;如下图,树的高度为4.
  • 森林:由m(m>=0)棵互不相交的树组成的集合称为森林。

1.3树的表示形式

双亲表示法,孩子表示法,孩子双亲表示法,孩子兄弟法 

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


二.二叉树

2.1概念

二叉树是节点的一个有限集合:

1.或者为空;

2.或者是由一个根节点加上两棵别称为左子树和右子树的二叉树组成。

注意:

1.二叉树不存在度大于2的结点

2.二叉树有左右之分,次序不能颠倒,因此二叉树是有序树

 二叉树的5种基本形态:

2.2二叉树的性质:

  1. 若规定根节点的层数为1,则一棵非空二叉树的第i层上最多有2^{i-1}(i>0)个节点
  2. 若规定只有根节点的二叉树的深度为1,则深度为K的二叉树的最大结点数是2^{k}-1(k>=0)
  3. 对任何一棵二叉树,如果其叶结点个数为n0,度为2的非叶结点个数为n2,则有n0=n2+1
  4. 具有n个结点的完全二叉树的深度k为log2(n+1)上取整

     5.对于具有n个结点的完全二叉树,如果按照从上至下从左至右的顺序对所有节点从0开始编号,则对于序号为i 的结点有:                                                                                                 若i>0,双亲序号:(i-1)/2i=0,i为根结点编号,无双亲结点

   若2i+1<n,左孩子序号为:2i+1,否则无左孩子

   若2i+2<n,右孩子序号为2i+2,否则无右孩子

2.3两种特殊的二叉树

1.满二叉树

一棵二叉树,如果每层的结点数都达到最大值,则这棵二叉树就是满二叉树,即若一棵二叉树的层数为k且节点总数为2^{k}-1,它就是满二叉树。

2.完全二叉树

完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为K的,有n 个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从0至n-1的结点一一对应时称之为完全二叉树。

满二叉树是一种特殊的完全二叉树

2.3二叉树的存储 

二叉树的存储结构分为:顺序存储和链式存储。

1.链式存储

链式存储类似于链表,通过一个个结点引用起来常见的表示方式有二叉和三叉表示方式。


 // 孩子表示法
class Node {
    int val;        // 数据域
    Node left;      // 左孩子的引用,常常代表左孩子为根的整棵左子树
    Node right;     // 右孩子的引用,常常代表右孩子为根的整棵右子树
}
 
// 孩子双亲表示法
class Node {
    int val;        // 数据域
    Node left;      // 左孩子的引用,常常代表左孩子为根的整棵左子树
    Node right;     // 右孩子的引用,常常代表右孩子为根的整棵右子树
    Node parent;    //当前节点的根节点
}

 2.顺序存储

二叉树的顺序存储其实就是用数组,按照层次顺序依次存储。

2.4二叉树的基本操作 

1.二叉树的构建

我们进行操作前,需要先构造一棵二叉树,按照下图,构建一棵满二叉树,如下:

class TreeBinary{
    class treeNode{
        char val;
        treeNode left;
        treeNode right;
        public treeNode(char val){
            this.val=val;
        }
    }
    
    public treeNode root;//根节点
    
    public treeNode creatBinaryTree(){
        treeNode A=new treeNode('A');
        treeNode B=new treeNode('B');
        treeNode C=new treeNode('C');
        treeNode D=new treeNode('D');
        treeNode E=new treeNode('E');
        treeNode F=new treeNode('F');
        treeNode H=new treeNode('H');
        A.left=B;
        A.right=C;
        B.left=D;
        B.right=E;
        C.left=F;
        C.right=H;
        return A;
    }
}

2.二叉树的遍历(递归&&非递归实现)

 遍历就是沿着某条搜索路径,依次对树中的每个结点进行有且仅有一次的访问。

二叉树的遍历有前中后遍历

主要应用:打印结点内容。

2.1前序遍历

前序遍历的顺序:根节点-->左子树-->右子树根左右

前序遍历打印流程图

代码实现(递归)

  public void preOder(treeNode root){
        if(root==null){//节点为空返回
            return;
        }
        //打印节点
        System.out.print(root.val+" ");
        //进入左子树
        preOder(root.left);
        //进入右子树
        preOder(root.right);
    }
/**
     * 使用栈实现二叉树的前序遍历。
     * @param root 二叉树的根节点
     */
        public static void preOder1(treeNode root){
            Stack<treeNode> stack=new Stack<>();
            treeNode cur=root;
            // 遍历二叉树直至所有节点都被访问
            while(cur!=null||!stack.isEmpty()){
                // 将当前节点及其左子节点全部入栈
                while(cur!=null){
                    stack.push(cur);
                    System.out.print(cur.val+" ");
                    cur=cur.left;
                }
                // 弹出栈顶节点,并访问其右子树
                treeNode top=stack.pop();
                cur=top.right;
            }
        }

前序遍历递归过程图:

前序遍历是根左右,所以当打印完根节点后,会进入左子树,以此类推,当走到null时,会返回到前一个根节点,再进入其右子树。 

2.2中序遍历

中序遍历的顺序:左子树-->根节点-->右子树左根右) 

(递归)

  public void inOder(treeNode root){
        if(root==null){
            return;
        }
        //进入左子树
        inOder(root.left);
        //打印节点
        System.out.print(root.val+" ");
        //进入右子树
        inOder(root.right);
    }

(非递归)

/**
     * 使用栈实现二叉树的中序遍历。
     * @param root 二叉树的根节点
     */
        public static void inOder1(treeNode root){
            Stack<treeNode> stack=new Stack<>();
            treeNode cur=root;
            // 遍历二叉树直至所有节点都被访问
            while(cur!=null||!stack.isEmpty()){
                // 将当前节点及其左子节点全部入栈
                while(cur!=null){
                    stack.push(cur);
                    cur=cur.left;
                }
                // 弹出栈顶节点,并访问其值和右子树
                treeNode top=stack.pop();
                System.out.print(top.val+" ");
                cur=top.right;
            }
        }

中序遍历打印流程图:

2.3.后序遍历

后序遍历顺序:左子树-->右子树-->根节点(左右根) 

(递归)

 public void postOder(treeNode root){
        if(root==null){
            return;
        }
        //进入左子树
        postOder(root.left);
        //进入右子树
        postOder(root.right);
        //打印节点
        System.out.print(root.val+" ");
        
    }

(非递归)

  /**
     * 使用栈实现二叉树的后序遍历。
     * @param root 二叉树的根节点
     */
        public static void postOder1(treeNode root) {
            Stack<treeNode> stack = new Stack<>();
            treeNode cur = root;
            treeNode prev = null;
            // 遍历二叉树直至所有节点都被访问
            while (cur != null || !stack.isEmpty()) {
                // 将当前节点及其左子节点全部入栈
                while (cur != null) {
                    stack.push(cur);
                    cur = cur.left;
                }
                // 获取栈顶节点
                treeNode top = stack.peek();
                // 如果右子节点为空或已经访问过,则弹出栈顶节点并访问
                if (top.right == null || prev == top.right) {
                    stack.pop();
                    System.out.print(top.val + " ");
                    prev=top;
                } else {
                    // 否则,转向右子节点
                    cur = top.right;
                }
            }
        }

后序遍历打印流程图: 

 2.4.层序遍历

层序遍历:从左往右、从上往下依次遍历

 public void levelOder(treeNode root){
        Queue<treeNode> queue=new LinkedList<>();
        queue.add(root);
        while(!queue.isEmpty()){
            treeNode node=queue.poll();
            System.out.print(node.val+" ");
            if(node.left!=null){
                queue.add(node.left);
            }
            if(node.right!=null){
                queue.add(node.right);
            }
        }

实现完前中后及层次遍历的代码,我们可以运行一遍是否如我们所想的一样

  public static void main(String[] args) {
        TreeBinary treeBinary=new TreeBinary();
        TreeBinary.treeNode root=treeBinary.creatBinaryTree();
        System.out.print("前序遍历:");
        treeBinary.preOder(root);
        System.out.println();
        System.out.print("中序遍历:");
        treeBinary.inOder(root);
        System.out.println();
        System.out.print("后序遍历:");
        treeBinary.postOder(root);
    }

 

3.求节点个数

对于求节点个数

1:我们可以借助上述中的前序遍历(前序遍历过程中必定会经过每一个节点)的基础上进行定义一个成员变量size进行计算,在遍历的过程中,只要根节点不为空,size就加1

2:求节点个数,也就是左子树的节点个数+右子树的节点个数+1=整棵树的节点个数

法一:

 /**
     * 法一:定义一个变量,记录节点个数
     */
    public  int nodesize;
    public int size1(treeNode root){
        if(root==null){
            return 0;
        }
        nodesize++;
        size1(root.left);
        size1(root.right);
        return nodesize;
    }

 法二:

 /**
     * 法二:递归求节点个数
     * @param root 根节点
     * @return
     */
    public int size(treeNode root){
        if(root==null){
            return 0;
        }
        //左子树的节点个数➕右子树的节点个数+1=整棵树的节点个数
        return  size(root.left)+size(root.right)+1;
    }

 4.求叶子节点个数

对于求叶子节点个数,我们知道叶子节点是没有左子树和右子树的,所以我们只需要在求节点个数的基础上加上一个条件:如果左子树为空且右子树也为空

两种方法:

 /**
     *
     * @param root
     * @return  叶子节点个数
     */
     public int leafCount(treeNode root){
        if(root==null){
            return 0;
        }
        if(root.left==null&&root.right==null){
            return 1;
        }
        return leafCount(root.left)+leafCount(root.right);
     }

    /**
     * 递归求叶子节点个数
     */
    public int leaffnode;
    public int getLeafnode(treeNode root) {
        if(root==null){
            return 0;
        }
        if(root.left==null&&root.right==null){
            leaffnode++;
        }
        getLeafnode(root.left);
        getLeafnode(root.right);
        return leaffnode;
    }

5.求第k层的节点个数

我们依旧是以上面的二叉树为例:

 假如我们现在要求第3层的节点个数,我们从图中可以知道,第3层的节点个数为4.

那么如果我们求第3层的节点个数,其实也就是求第2层节点的左子树和右子树之和。假设k为层数,我们要求第k层的节点数,也就是求(k-1)层的左子树和右子树之和。

代码如下:

 /**
     * 获取第k层的节点个数
     */
    public int getKLevelNodeCount(treeNode root,int k){
       if(root==null){
           return 0;
       }
       if(k==1){
           return 1;
       }
       return getKLevelNodeCount(root.left,k-1)
               +getKLevelNodeCount(root.right,k-1);
    }

 6.求值是否存在二叉树中

我们可以利用前面几种遍历,如果找到对应的节点则直接返回,否则进入左子树和右子树中进行查找。

public treeNode find(treeNode root, char val) {
        // 如果根节点为空,说明树为空,无法继续查找,直接返回null
        if (root == null) {
            return null;
        }
        // 如果根节点的值等于要查找的值,直接返回根节点
        if (root.val == val) {
            return root;
        }
    
        // 在左子树中递归查找指定值的节点
        treeNode node = find(root.left, val);
        // 如果在左子树中找到了指定值的节点,直接返回该节点
        if (node != null) {
            return node;
        }
        // 在右子树中递归查找指定值的节点
        node = find(root.right, val);
        // 如果在右子树中找到了指定值的节点,直接返回该节点
        if (node != null) {
            return node;
        }
        // 如果左右子树中都没有找到指定值的节点,返回null
        return null;
    }

7.判断一棵树是不是完全二叉树

判断一棵树是不是完全二叉树,我们可以利用队列,我们把根节点先放在队列中,在根节点出队的时候,把根节点的左右子树都放到队列中。在入队的过程中,我们可以判断是否遇到null的情况,如果遇到null,说明我们已经入队结束。

当我们入完队之后,我们要判断队列还存在的节点是否都为空,如果有一个不为空,说明这棵树不是完全二叉树。

/**
 * 检查二叉树是否为完全二叉树。
 * 完全二叉树定义:除最后一层外,每一层的节点数都达到最大值,并且最后一层的节点尽量都集中在左边。
 * 
 * @param root 二叉树的根节点
 * @return 如果二叉树是完全二叉树,则返回true;否则返回false。
 */
public boolean isCompleteTree(TreeNode root) {
    // 使用队列来进行层次遍历
    Queue<TreeNode> queue = new LinkedList<>();
    // 根节点为空,直接判断为完全二叉树
    if (root == null) {
        return true;
    }
    // 将根节点入队
    queue.offer(root);
    // 遍历队列中的所有节点,直到队列为空
    while (!queue.isEmpty()) {
        TreeNode cur = queue.poll();
        // 遇到空节点,说明当前层已经遍历完毕,跳出循环
        if (cur == null) {
            break;
        }
        // 左右子节点入队
        queue.offer(cur.left);
        queue.offer(cur.right);
    }
    // 继续遍历队列中剩余的节点,检查是否存在非空节点,判断是否为完全二叉树
    while (!queue.isEmpty()) {
        TreeNode node = queue.peek();
        // 如果存在非空节点,则不是完全二叉树
        if (node != null) {
            return false;
        } else {
            // 移除队列头部的空节点
            queue.poll();
        }
    }
    // 遍历结束,队列为空,说明二叉树是完全二叉树
    return true;
}

相关的二叉树题目:

二叉树知识点题库 - 力扣(LeetCode)

若有不足之处,欢迎指正~

  • 43
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 9
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zhyhgx

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值