当前位置: 首页 > article >正文

【Java---数据结构】二叉树(Tree)

目录

1. 树型结构

1.1 树的概念

1.2 树的基本术语

1.3 树的表示方法

(1)孩子表示法

(2)孩子兄弟表示法

2. 二叉树

2.1 二叉树的概念

2.2 二叉树的类型

(1)满二叉树

(2)完全二叉树

(3)平衡二叉树

(4)二叉搜索树

2.3 二叉树的性质

2.4 二叉树的存储

2.4.1 顺序存储

2.4.2 链式存储

2.5 二叉树的遍历

1. 前序遍历

(1)使用递归的方式

(2)非递归的方式

(3)返回顺序表

2. 中序遍历

(1)使用递归的方式

(2)非递归的方式

(3)返回顺序表

3. 后续遍历

(1)使用递归的方式

(2)非递归的方式

(3) 返回顺序表

4. 层序遍历


1. 树型结构

1.1 树的概念

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

树具有以下特点:

  1. 根结点:树中有一个特定的结点称为根结点,它是树的起点,没有前驱结点。

  2. 层次关系:除根结点外,每个结点有且仅有一个前驱结点,但可以有(0个或者多个)后继结点,从而形成一种层次结构。

  3. 递归:树是可以递归定义

注意:

  1. 子树不能相交
  2. 除了根节点,每个节点有且只有一个父节点 
  3. 一个N个节点的树有N-1个边

1.2 树的基本术语

(1)结点的度:一个节点含有子树的个数

(2)树的度:在所有结点的度中挑选出最大值

(3)叶子结点/终端结点:度为0的结点

(4)双亲结点/父亲结点:一个结点的直接上级结点

(5)孩子结点/子结点:一个结点的直接下级结点

(6)根节点:没有父节点的结点

(7)兄弟结点:具有相同的父亲结点

(8)堂兄弟结点:父亲结点在同一层的结点

(9)结点的层次:根节点是第一层,根的子节点为第二层,以此类推

(10)树的高度:树中的最大结点层次

(11)分支结点/非终端结点:度不为0的结点

(12)子孙:某节点子树中的所有节点。

(13)祖先:从根到某节点的路径上的所有节点。

(14)森林:(m>=0)多棵互不相交的树的集合

1.3 树的表示方法

(1)孩子表示法

孩子表示法:通过直接存储子节点的引用来表示树的结构。

由2部分组成:

  1. 数据域:存放结点的值
  2. 指针域
    1. left:指向当前结点的左子节点
    2. right:指向当前结点的右子结点

 ​​​​

代码实现:

public class TreeNode{
    public char val;
    public TreeNode left;
    public TreeNode right;

    public TreeNode(char val) {
        this.val = val;
    }
}

(2)孩子兄弟表示法

孩子兄弟表示法:通过将树中的每个节点的子节点和兄弟节点进行关联,从而实现对树的存储和操作

由2部分组成:

  1. 数据域:存储结点的数据。
  2. 指针域:
    1. 第一个孩子指针:指向该节点的第一个孩子节点。
    2. 下一个兄弟指针:指向该节点的下一个兄弟节点。

代码实现:

class TreeNode {
    int data; // 数据域
    TreeNode firstChild; // 指向第一个孩子节点
    TreeNode nextSibling; // 指向下一个兄弟节点
}

2. 二叉树

2.1 二叉树的概念

二叉树是一种特殊的树形数据结构,:每个节点最多有两个节点,左子节点右子节点

二叉树的特点:

  1. 每个节点最多有两个子节点:左子节点和右子节点
  2. 有序树:左子节点和右子节点的顺序是固定的,不能互换

二叉树的所有情况: 

注意 :任意的二叉树都是由这五种情况组合而成

2.2 二叉树的类型

(1)满二叉树

满二叉树:一棵深度为k且有2^(k−1)个节点的二叉树

满二叉树的每一层都是满的,即每一层的节点数都达到最大值。

特点:

  1. 每个节点的度为0或者2
  2. 所有的叶子节点都在一层

(2)完全二叉树

深度为k的二叉树,第1到k−1层的节点数都达到最大值,而第k层的节点从左到右连续排列,没有空缺。

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

(3)平衡二叉树

一棵二叉树,其中每个节点的左右子树的高度差不超过1。

(4)二叉搜索树

一棵二叉树,其中每个节点的值大于其左子树上所有节点的值,且小于其右子树上所有节点的值。

中序遍历的结果是一个递增的有序序列。

2.3 二叉树的性质

性质1:在二叉树的第i层上,最多有2^(i−1)个节点(i≥1)。

性质2:深度为k的二叉树最多有2k−1个节点。

性质3:对于任何非空二叉树,如果叶子节点数为n0​,度为2的节点数为n2​,则有n0​=n2​+1。

性质4:具有n个节点的完全二叉树的深度为log2(n+1)向上取整

性质4:具有n个节点的完全二叉树,n为偶数--度为1的节点==1,n为奇数--度为1的节点==0

2.4 二叉树的存储

2.4.1 顺序存储

顺序存储是通过数组来存储二叉树的节点,节点在数组中的位置与其在二叉树中的层次结构相对应。(下一章详细介绍)

特点:

  1. 适合完全二叉树。
  2. 空间利用率高,访问效率高。
  3. 插入和删除操作复杂。

 主要用于:堆(优先队列)和完全二叉树

2.4.2 链式存储

链式存储是通过指针来表示二叉树中节点之间的关系。每个节点包含数据部分和指向其左右子节点的指针。

特点:

  1. 可以方便地插入和删除节点,适合动态操作。
  2. 不需要预先分配存储空间,空间利用率高。
  3. 链式存储的实现相对简单,适合各种二叉树操作。

适用于各种二叉树场景

2.5 二叉树的遍历

链式存储的二叉树

遍历的流程走向

前序遍历:ABDEC FGH

中序遍历:DBEAFGCH

后序遍历:DEBGFHCA

1. 前序遍历

前序遍历 —— 访问根结点 ---> 根的左子树 --->根的右子树。
(1)使用递归的方式​​​​​​​

代码实现: 

    void preOrder(TreeNode root) {
        if (root == null) {
            return;
        }
        System.out.print(root.val + " ");
        preOrder(root.left);
        preOrder(root.right);
    }
(2)非递归的方式

主要步骤:前序遍历先访问根节点,根的左子树,根的右子树

  1. 通过使用栈进行存储,先让cur指向根节点,然后让cur去指向根节点的左子树(一直向左跑),只要不为cur不为空,就一直向左,并输出每次的cur,并让他入栈
  2. 如果左边走不通了(没有节点了),就出栈一个元素,得到最近走过的一个节点,并指向这个节点的右边,如果这个节点的右边为null,就继续出栈,得到上一个走过的节点,如果这节点右边不为空,则向这个节点的左边继续走,一直循环下去

代码实现:

    public void preOrderNor(TreeNode root) {
        if (root == null) {
            return;
        }
        Stack<TreeNode> stack = new Stack<>();
        TreeNode cur = root;
        TreeNode top = null;
       while(cur !=null||!stack.isEmpty()){
            while(cur != null) {
                stack.push(cur);
                System.out.print(cur.val + " ");
                cur = cur.left;
            }
            //如果左边为空
            top = stack.pop();
            cur = top.right;
       }
    }
(3)返回顺序表

代码实现:

    public List<TreeNode> preOrder2(TreeNode root) {
        List<TreeNode> list1 = new ArrayList<>();
        if (root == null) {
            return list1;
        }
        list1.add(root);
        List<TreeNode> leftList = preOrder2(root.left);
        list1.addAll(leftList);
        List<TreeNode> rightList = preOrder2(root.right);
        list1.addAll(rightList);
        return list1;
    }

2. 中序遍历

中序遍历——根的左子树--->根节点--->根的右子树。

(1)使用递归的方式

代码实现:

    public void inOrder(TreeNode root) {
        if (root == null) {
            return;
        }
        inOrder(root.left);
        System.out.print(root.val + " ");
        inOrder(root.right);
    }
(2)非递归的方式

主要步骤:

cur指向根节点,cur不为空就一直向左走并入栈,左边走不通了,出栈并打印元素,走出栈元素的右边

代码实现:

    public void inOrderNor(TreeNode root) {
        if (root == null) {
            return;
        }
        Stack<TreeNode> stack = new Stack<>();
        TreeNode cur = root;
        TreeNode top = null;
        while(cur!=null||!stack.isEmpty()){
            while (cur != null) {
                stack.push(cur);
                cur = cur.left;
            }
            top = stack.pop();
            //输出top的数值,左边走不通
            System.out.print(top.val + " ");
            cur = top.right;
        }
    }
(3)返回顺序表

代码实现:

    public List<TreeNode> inOrder2(TreeNode root) {
        List<TreeNode> list = new ArrayList<>();
        if (root == null) {
            return list;
        }
        List<TreeNode> leftlist = inOrder2(root.left);
        list.addAll(leftlist);
        list.add(root);
        List<TreeNode> rightlist = inOrder2(root.right);
        list.addAll(rightlist);
        return list;
    }

3. 后续遍历

后序遍历——根的左子树--->根的右子树--->根节点。

(1)使用递归的方式

代码实现:

    public void postOrder(TreeNode root) {
        if (root == null) {
            return;
        }
        postOrder(root.left);
        postOrder(root.right);
        System.out.print(root.val + " ");
    }
(2)非递归的方式

代码实现:

    public void postOrderNor(TreeNode root) {
        if (root == null) {
            return;
        }
        Stack<TreeNode> stack = new Stack<>();
        TreeNode cur = root;
        TreeNode top = null;
        TreeNode prev = null;
        while (cur != null || !stack.isEmpty()){
            while (cur != null) {
                stack.push(cur);
                cur = cur.left;
            }
            top = stack.peek();
            if (top.right == null || top.right == prev) {
                stack.pop();
                System.out.print(top.val + " ");
                prev = top;
            } else {
                cur = top.right;
            }
        }
    }

注意:主要步骤和先序遍历.中序遍历差不多,但是要在走右边结点的过程中,加上一个标记,走过了,就不能再走了,避免进入死循环 

(3) 返回顺序表

代码实现:

    public List<TreeNode> postOrder2(TreeNode root) {
        List<TreeNode> list = new ArrayList<>();
        if (root == null) {
            return list;
        }
        List<TreeNode> leftList = postOrder2(root.left);
        list.addAll(leftList);
        List<TreeNode> rightList = postOrder2(root.right);
        list.addAll(rightList);
        list.add(root);
        return list;
    }

4. 层序遍历

(1)层序遍历

层序遍历采用队列的思想

如图所示 :

结果:ABCDEFG 

    public void levelOrder(TreeNode root){
        if(root == null){
            return ;
        }
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        while(!queue.isEmpty()){
            TreeNode cur = queue.poll();
            System.out.print(cur.val+" ");
            if(cur.left !=null){
                queue.offer(cur.left);
            }
            if(cur.right!=null){
                queue.offer(cur.right);
            }
        }
    }

(2)分层遍历

结果:[ [A],[BC],[DEFG]  ]

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
//二叉树的分层遍历
public class Text_8 {
    public List<List<Character>> levelOrder(TreeNode root) {
        List<List<Character>> ret = new ArrayList<>();
        if (root == null) {
            return ret;
        }
        //队列,大的存放
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        while(!queue.isEmpty()){
            //每层存放的
            List<Character> tmp = new LinkedList<>();
            int size = queue.size();
            while (size != 0) {
                TreeNode cur = queue.poll();
                tmp.add(cur.val);
                size--;
                if (cur.left != null) {
                    queue.offer(cur.left);
                }
                if (cur.right != null) {
                    queue.offer(cur.right);
                }
            }
//        每层的元素都放在了tmp里,再将tmp给ret
            ret.add(tmp);
        }
        return ret;
    }
}

分层遍历的思想:在原来分层遍历的基础上, 控制每次进行出队的个数,从而控制入队的个数,得到每层的个数


http://www.kler.cn/a/579657.html

相关文章:

  • NetBeans 8.2 开发 CIFLog3.5 - 数据导入导出案例
  • 220页满分PPT | 华为ISC供应链解决方案
  • 【新闻资讯】IT 行业最新动向:AI 引领变革,多领域融合加速
  • 【从零开始学习计算机科学】计算机体系结构(一)计算机体系结构、指令、指令集(ISA)与量化评估
  • 不同开发语言对字符串的操作
  • 一文理清概念:数据中台(DMP)-数据仓库(DW)-数据湖(DL)-湖仓一体-数据治理(DG)
  • SpringMVC概述以及入门案例
  • Hadoop、Spark、Flink Shuffle对比
  • 音乐API
  • 渗透测试之利用sql拿shell(附完整流程+防御方案)【下】
  • 机器学习实战——音乐流派分类(主页有源码)
  • 行为模式---观察者模式
  • 生物信息学与计算生物学:各自概念、主要内容、区别与联系、发展展望?
  • Gazebo不报错但是没有机器人模型
  • MySQL配置文件my.cnf和mysql.cnf、mysqld.cnf的区别
  • AI智能体崛起,“智能经济”加速跑,GAI认证助力未来
  • CCF-CSP第36次认证第四题——跳房子【优化思路:预处理区间最大值】
  • 小智智能体语言大模型硬件软件开发
  • 深度解析前端页面性能优化
  • Python零基础学习第三天:函数与数据结构