【用Java学习数据结构系列】震惊,二叉树原来是要这么学习的(一)

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
简介: 【用Java学习数据结构系列】震惊,二叉树原来是要这么学习的(一)

前言

终于到了之前C语言没有讲过的数据结构了,那就是二叉树了,关于二叉树的学习难度确实比前面学习的数据结构都要难一点,所以我们这个关于二叉树的博客大概率是有好几篇的。如有哪里出现错误也欢迎指出唔。

二叉树的概念



Java 中的二叉树是一种基础的数据结构,它是由节点组成的树形结构,其中每个节点最多有两个子节点,通常称为左子节点和右子节点。二叉树的节点通常包含三个部分:节点的数据域、指向左子节点的指针和指向右子节点的指针。


我们通常把没有父节点(双亲节点)的节点叫做根节点,平常说的”这个根“,”它的根“中的根就是值根节点。

比如上面的”1“为整个树的根节点,也可以说”4“是 5和6的根因为其实4,5,6也可以说其组成了一棵树,组成的这棵树我们叫做子树。


二叉树的性质:

定义性:二叉树是节点的集合,其中每个节点最多有两个子节点,通常称为左子节点和右子节点。

层级性:二叉树的节点按照层次排列,根节点位于第一层,其子节点位于第二层,以此类推。

有序性:在二叉搜索树(Binary Search Tree, BST)中,每个节点的值都大于(或等于)其左子树中所有节点的值,并且小于(或等于)其右子树中所有节点的值。

递归性:二叉树可以递归地定义为:一个节点(包含数据域和两个指向子树的指针域)或者为空。

树的深度:二叉树的深度是根节点到最远叶子节点的最长路径上的边数。

节点的度:一个节点的度是指该节点拥有的子节点数量。在二叉树中,节点的度最大为2。

树的广度:二叉树的广度是指树的层数。

叶子节点:二叉树中没有子节点的节点称为叶子节点或外部节点。

内部节点:至少有一个子节点的节点称为内部节点。

树的高度:二叉树的高度是从根节点到任意叶子节点的最长路径上的边数。

子树:任何一个节点及其所有后代构成的二叉树称为该节点的子树。

兄弟节点:具有相同父节点的节点称为兄弟节点。

祖先节点:从根到该节点所经过的节点序列称为该节点的祖先。

后代节点:以某节点为根的子树中的任一节点都称为该节点的后代。

若规定根结点的层数为1,则一棵非空二叉树的第i层上最多有 (i>0)个结点

若规定只有根结点的二叉树的深度为1,则深度为K的二叉树的最大结点数是 (k>=0)

对任何一棵二叉树, 如果其叶结点个数为 n0, 度为2的非叶结点个数为 n2,则有n0=n2+1

这些性质是理解和操作二叉树的基础,它们在算法设计和数据结构分析中扮演着重要角色。


比较需要注意的是第17点性质。

三种特殊的二叉树
  1. 完全二叉树:如果一个二叉树的所有层都被完全填满,除了最后一层,并且最后一层的节点尽可能地集中在左侧,这样的二叉树称为完全二叉树。


也就是说在如果最后一层在左边没有填满,右边就突然有一个节点,那么他就不是完全二叉树。如下就不是一棵完全二叉树:

  1. 满二叉树:如果一个二叉树的所有节点都恰好有两个子节点,这样的二叉树称为满二叉树。


  1. 平衡二叉树:如果二叉树的任何两个叶子节点的深度之差不超过1,这样的二叉树称为平衡二叉树。AVL树和红黑树是两种常见的自平衡二叉搜索树。

这里叶子节点有”9“,”15“,”7“,他们之间的深度之差都不超过1,所以它是一棵平衡二叉树。


二叉树的数据储存---链式储存

孩子表示法

class Node<E>{
    E val;    // 数据域
    Node left;  //左孩子的引用,常常代表左孩子为根的整棵左子树
    Node right; //右孩子的引用,常常代表左孩子为根的整棵左子树
    
 
}


孩子双亲表示法

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


注:Java 标准库中包含了一些与二叉树相关的类,但它们并不是直接的二叉树实现,而是以不同的数据结构形式存在。

二叉树主要的三种遍历

前提准备:因为我们现在还没学习真正的创建一颗二叉树,所以我们先用一种笨的方法创建一棵二叉树,代码如下:

class BinaryTree{
    public static class BTNode{
        BTNode left;
        BTNode right;
        int value;
        BTNode(int value){
            this.value = value;
        }
    }
    private BTNode root;
    public void createBinaryTree(){
        BTNode node1 = new BTNode(1);
        BTNode node2 = new BTNode(2);
        BTNode node3 = new BTNode(3);
        BTNode node4 = new BTNode(4);
        BTNode node5 = new BTNode(5);
        BTNode node6 = new BTNode(6);
        root = node1;
        node1.left = node2;
        node2.left = node3;
        node1.right = node4;
        node4.left = node5;
        node4.right = node6;
    }
 }   

这样我们写了一个createBinaryTree()方法,用来创建我们的一颗二叉树,这棵二叉树是这样的:

那么接下来我们就来学习三种二叉树主流遍历方法

前序遍历

学习二叉树遍历通常都是学习其中一种就可以举一反三,很容易就会其它两种遍历了。我们现在学习的这种遍历方法有递归方法和非递归方法。前序遍历就是先遍历根然后打印,在遍历左节点,在遍历右节点。口诀:”左右根“

前序遍历结果:1 2 3 4 5 6

递归前序遍历代码如下:
void preOrder(BTNode root){
    if(root==null){
        return;
    }
 
    System.out.print(root.value+" ");
    preOrder(root.left);
    preOrder(root.right);
 
 
 
}


代码很简单,但是对于没学到的小伙伴可能就懵逼了。那我们就用图来详细解析一下这个递归的遍历方法

这一张图是总的二叉树前序遍历图,画出了递归和回退过程,对于有一点递归基础的应该是可以看懂的。


然后上面这两张图就是根节点的整个左子树的递归过程,也就是节点”1“,”2“,”3“节点的递归回退过程,后面就是到右子树的递归过程,右子树的递归过程我就不画了,思维和左子树是一样的。


非递归前序遍历方法
public void preOrderNor(TreeNode root) {
 
        if(root == null) return;
 
        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;
 
        }
 
    }

解析:

非递归的遍历,首先我们遍历是需要返回前一个节点的,所以我们必须用到一个栈来进行储存我们的节点,然后我们首先创建一个循环只要cur不等于null和栈不为空就进入循环,cur不为空是因为首次进入的时候栈是空的,为了保证刚开始能进入循环就加了这么个判断条件。然后在镶嵌一个循环,这个循环就是遍历左树的每遍历一个就进栈一个为了储存节点好让后面返回节点遍历右树,然后因为这是前序遍历,所以我们要先打印在 cur = cur.left; 然后除了第一个循环就可以开始遍历右树了,这时我们就需要先出栈来返回上一个节点。

中序遍历

中序遍历和前序遍历递归思维都是一样的,就不再画图给大家解析了。中序遍历就是先遍历完左节点在回退到根进行打印,在遍历右节点,回退过程中在打印口诀:”左右根“


中序遍历结果:3 2 1 5 4 6

递归前中遍历代码如下:
void inOrder(BTNode root){
    if(root==null){
        return;
    }
 
 
    inOrder(root.left);
 
    System.out.print(root.value+" ");
 
 
    inOrder(root.right);
 
 
 
}


非递归中序遍历方法
public void inOrderNor(TreeNode root) {
 
        if(root == null) return;
 
        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;
 
        }
 
    }



这个中序遍历的非递归方法还是和前序遍历思路一样,也就是打印的语句不一样罢了,就不再次解析了,理解了前序,中序的这个方法肯定是会理解的。重点放在后序遍历的非递归方法。


后序遍历

后续遍历就是遍历完当前节点的左右子树后,才会打印这个节点口诀:”左右根“

中序遍历和前序遍历递归思维都是一样的,就不再画图给大家解析了。

后序遍历结果:3 1 5 6 4 1


递归后序遍历代码如下:
void inOrder(BTNode root){
    if(root==null){
        return;
    }
 
 
    inOrder(root.left);
 
    System.out.print(root.value+" ");
 
 
    inOrder(root.right);
 
 
 
}
非递归后序遍历方法
public void postOrderNor(TreeNode root) {
 
        if(root == null) return;
 
        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 || top.right == prev) {
 
                System.out.print(top.val + " ");
 
                stack.pop();
 
                prev = top;
 
            } else {
 
                cur = top.right;
 
            }
 
        }
 
    }

为什么说后续遍历的非递归方法需要注意呢?


因为只要稍微不注意代码就会进入死循环。那么容易死循环的地方在哪里呢?


很多人可能会仿照前序和中序的代码在进入右树后面的语句加个判断左右是否为空,然后进行打印,但是却不会想到打印完后,回退到父节点时,此时的父节点已经在之前遍历过了。比如如果二叉树加了一个节点:

这个图如果失去了top.right == prev;这个语句就会死循环,它会在3和13不断死循环,所以我们就得在遍历右树时加上top.right == prev;这样就不会回退到3时,在进入13了。


这篇文章的二叉树就到这结束了,这一篇注要讲如何遍历二叉树。【用Java学习数据结构系列】震惊,二叉树原来是要这么学习的(一)

目录
相关文章
|
17天前
|
存储 算法 安全
2024重生之回溯数据结构与算法系列学习之串(12)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丟脸好嘛?】
数据结构与算法系列学习之串的定义和基本操作、串的储存结构、基本操作的实现、朴素模式匹配算法、KMP算法等代码举例及图解说明;【含常见的报错问题及其对应的解决方法】你个小黑子;这都学不会;能不能不要给我家鸽鸽丢脸啊~除了会黑我家鸽鸽还会干嘛?!!!
2024重生之回溯数据结构与算法系列学习之串(12)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丟脸好嘛?】
|
8天前
|
缓存 算法 Java
本文聚焦于Java内存管理与调优,介绍Java内存模型、内存泄漏检测与预防、高效字符串拼接、数据结构优化及垃圾回收机制
在现代软件开发中,性能优化至关重要。本文聚焦于Java内存管理与调优,介绍Java内存模型、内存泄漏检测与预防、高效字符串拼接、数据结构优化及垃圾回收机制。通过调整垃圾回收器参数、优化堆大小与布局、使用对象池和缓存技术,开发者可显著提升应用性能和稳定性。
27 6
|
13天前
|
C语言
【数据结构】二叉树(c语言)(附源码)
本文介绍了如何使用链式结构实现二叉树的基本功能,包括前序、中序、后序和层序遍历,统计节点个数和树的高度,查找节点,判断是否为完全二叉树,以及销毁二叉树。通过手动创建一棵二叉树,详细讲解了每个功能的实现方法和代码示例,帮助读者深入理解递归和数据结构的应用。
62 8
|
14天前
|
存储 Java 索引
Java中的数据结构:ArrayList和LinkedList的比较
【10月更文挑战第28天】在Java编程世界中,数据结构是构建复杂程序的基石。本文将深入探讨两种常用的数据结构:ArrayList和LinkedList,通过直观的比喻和实例分析,揭示它们各自的优势与局限,帮助你在面对不同的编程挑战时做出明智的选择。
|
10天前
|
Java 大数据 API
14天Java基础学习——第1天:Java入门和环境搭建
本文介绍了Java的基础知识,包括Java的简介、历史和应用领域。详细讲解了如何安装JDK并配置环境变量,以及如何使用IntelliJ IDEA创建和运行Java项目。通过示例代码“HelloWorld.java”,展示了从编写到运行的全过程。适合初学者快速入门Java编程。
|
17天前
|
算法 安全 搜索推荐
2024重生之回溯数据结构与算法系列学习(8)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
数据结构王道第2.3章之IKUN和I原达人之数据结构与算法系列学习x单双链表精题详解、数据结构、C++、排序算法、java、动态规划你个小黑子;这都学不会;能不能不要给我家鸽鸽丢脸啊~除了会黑我家鸽鸽还会干嘛?!!!
|
17天前
|
存储 算法 安全
2024重生之回溯数据结构与算法系列学习之顺序表【无论是王道考研人还真爱粉都能包会的;不然别给我家鸽鸽丢脸好嘛?】
顺序表的定义和基本操作之插入;删除;按值查找;按位查找等具体详解步骤以及举例说明
|
17天前
|
算法 安全 搜索推荐
2024重生之回溯数据结构与算法系列学习之单双链表精题详解(9)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
数据结构王道第2.3章之IKUN和I原达人之数据结构与算法系列学习x单双链表精题详解、数据结构、C++、排序算法、java、动态规划你个小黑子;这都学不会;能不能不要给我家鸽鸽丢脸啊~除了会黑我家鸽鸽还会干嘛?!!!
|
17天前
|
存储 Web App开发 算法
2024重生之回溯数据结构与算法系列学习之单双链表【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
数据结构之单双链表按位、值查找;[前后]插入;删除指定节点;求表长、静态链表等代码及具体思路详解步骤;举例说明、注意点及常见报错问题所对应的解决方法
|
17天前
|
算法 安全 NoSQL
2024重生之回溯数据结构与算法系列学习之栈和队列精题汇总(10)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
数据结构王道第3章之IKUN和I原达人之数据结构与算法系列学习栈与队列精题详解、数据结构、C++、排序算法、java、动态规划你个小黑子;这都学不会;能不能不要给我家鸽鸽丢脸啊~除了会黑我家鸽鸽还会干嘛?!!!