二叉树(binary tree)

二叉树(Binary Tree)是一种常见的树状数据结构,它由一组节点组成,每个节点最多有两个子节点,分别称为左子节点和右子节点。二叉树具有以下特点:

  1. 每个节点最多有两个子节点,分别称为左子节点和右子节点。
  2. 左子树和右子树也是二叉树,它们的结构与父节点类似。
  3. 二叉树的顺序不固定,可以是任意形状。

两种特殊形式

二叉树还有两种特殊形式,一个叫作满二叉树 ,另一个叫作完全二叉树

满二叉树

如果该二叉树的所有叶子节点都在最后一层,并且结点总数= 2^n -1,n 为层数,则我们称为满二又树。简单点说,满二叉树的每一个分支都是满的。

完全二叉树

对一个有n个节点的二叉树,按层级顺序编号,则所有节点的编号为从1到n。如果这个树所有节点和同样深度的满二叉树的编号为从1到n的节点位置相同,则这个二叉树为完全二叉树。完全二叉树的条件没有满二叉树那么苛刻: 满二叉树要求所有分支都是满的;而完全二叉树只需保证最后一个节点之前的节点都齐全即可。

遍历二叉树

在计算机程序中,遍历本身是一个线性操作。所以遍历同样具有线性结构的数组或链表,是一件轻而易举的事情。反观二叉树,是典型的非线性数据结构,遍历时需要把非线性关联的点转化成一个线性的序列,以不同的方式来遍历,遍历出的序列顺序也不同。

遍历方式

深度优先和广度优先这两个概念不止局限于二叉树,它们更是一种抽象的算法思想,决定了访问某些复杂数据结构的顺序。在访问树、图,或其他一些复杂数据结构时,这两个概念常常被使用到。

1.深度优先遍历(Depth-First Search,DFS)

所谓深度优先,顾名思义,就是偏向于纵深,“一头扎到底”的访问方式。可能这种说法有些抽象,下面就通过二叉树的前序遍历、中序遍历、后序遍历 ,来看一看深度优先

2.广度优先遍历 (Breadth-First Search,BFS)

如果说深度优先遍历是在一个方向上“一头扎到底”,那么广度优先遍历则恰恰相反:先在各个方向上各走出1步,再在各个方向上走出第2步、第3步......一直到各个方向全部走完。听起来有些抽象,下面让我们通过二叉树的层序遍历

也是一种遍历或搜索树或图的算法。在广度优先遍历中,从根节点开始,按照层级顺序逐层访问节点,先访问当前层的所有节点,然后再访问下一层的节点。广度优先遍历可以使用队列来实现。

前序遍历

根节点 -> 左子树 -> 右子树。在前序遍历中,首先访问根节点,然后按照左子树和右子树的顺序递归地进行前序遍历。

    // 前序遍历二叉树(根-左-右)
public static void preOrderTraversal(TreeNode node) {
if (node == null) {
return;
} System.out.print(node.val + " "); preOrderTraversal(node.left);
preOrderTraversal(node.right);
}

中序遍历

左子树 -> 根节点 -> 右子树。在中序遍历中,首先按照左子树的顺序递归地进行中序遍历,然后访问根节点,最后按照右子树的顺序递归地进行中序遍历。

    // 中序遍历二叉树(左-根-右)
public static void inOrderTraversal(TreeNode node) {
if (node == null) {
return;
} inOrderTraversal(node.left); System.out.print(node.val + " "); inOrderTraversal(node.right);
}

后序遍历

左子树 -> 右子树 -> 根节点。在后序遍历中,首先按照左子树和右子树的顺序递归地进行后序遍历,然后访问根节点。

    // 后序遍历二叉树(左-右-根)
public static void postOrderTraversal(TreeNode node) {
if (node == null) {
return;
} postOrderTraversal(node.left);
postOrderTraversal(node.right); System.out.print(node.val + " ");
}
 public static void main(String[] args) {
// 创建一个二叉树
TreeNode root = new TreeNode(1);
root.left = new TreeNode(2);
root.right = new TreeNode(3);
root.left.left = new TreeNode(4);
root.left.right = new TreeNode(5); // 前序遍历二叉树
System.out.println("前序遍历结果:");
preOrderTraversal(root); // 中序遍历二叉树
System.out.println("\n中序遍历结果:");
inOrderTraversal(root); // 后序遍历二叉树
System.out.println("\n后序遍历结果:");
postOrderTraversal(root);
}

层次遍历

是一种广度优先遍历的应用,它按照层级顺序逐层访问二叉树的节点。在层次遍历中,从根节点开始,先访问根节点,然后按照从左到右的顺序逐层访问每个节点的子节点,一层一层横向遍历各个节点,可以借助于队列实现。

public static void levelOrderTraversal(TreeNode root) {
if (root == null) {
return;
} Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root); // 将根节点入队 while (!queue.isEmpty()) {
int levelSize = queue.size(); // 当前层级的节点数量 for (int i = 0; i < levelSize; i++) {
TreeNode node = queue.poll(); // 出队当前节点
System.out.print(node.val + " "); // 访问当前节点 if (node.left != null) {
queue.offer(node.left); // 左子节点入队
} if (node.right != null) {
queue.offer(node.right); // 右子节点入队
}
} System.out.println(); // 换行表示进入下一层级
}
}

存储结构

顺序结构存储

使用数组来表示二叉树的结构。按照层级顺序依次将二叉树节点存储到数组中,空缺位置用特定值(如null)表示。这种存储结构适用于完全二叉树,因为不是完全二叉树会有空间的浪费,可以通过数组下标计算节点之间的关系。

特点

  • 顺序二叉树通常只考虑完全二叉树

  • 第n个元素的左子节点为 2* n +1

  • 第n个元素的右子节点为 2* n + 2

  • 第n个元素的父节点为 (n-1)/2

n: 表示二又树中的第几个元素(按0开始编号如图所示)

代码示例

public class ArrayBinaryTree {
private int[] treeArray;
private int size; public ArrayBinaryTree(int capacity) {
treeArray = new int[capacity];
size = 0;
} public void insert(int data) {
if (size >= treeArray.length) {
System.out.println("The tree is full");
return;
} treeArray[size] = data;
size++;
} public void inorderTraversal(int index) {
if (index >= size) {
return;
} inorderTraversal(index * 2 + 1); // 访问左子树
System.out.print(treeArray[index] + " "); // 访问当前节点
inorderTraversal(index * 2 + 2); // 访问右子树
} public static void main(String[] args) {
ArrayBinaryTree tree = new ArrayBinaryTree(10);
tree.insert(1);
tree.insert(2);
tree.insert(3);
tree.insert(4);
tree.insert(5); System.out.print("Inorder Traversal: ");
tree.inorderTraversal(0); // 输出: 4 2 5 1 3
}
}

链式存储

用节点对象和指针来表示二叉树的结构。每个节点包含一个数据元素和左右子节点的指针。用链表来表示一棵二叉树,即用链来指示元素的逻辑关系。 通常的方法是链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别用来给出该结点左孩子和右孩子所在的链结点的存储地址 。这种存储结构可以灵活地表示任意形状的二叉树。

class TreeNode {
int val;
TreeNode left;
TreeNode right; public TreeNode(int val) {
this.val = val;
this.left = null;
this.right = null;
}
}

线索化二叉树

线索二叉树是对普通二叉树的一种扩展,它通过在空指针位置存储指向当前节点的前驱节点和后继节点的指针,使得二叉树的遍历更加方便。可以有效地减少遍历的次数,同时也可以避免空指针的浪费。

  • n个结点的二又链表中含有n+1 [公式 2n-(n-1)=n+1] 个空指针域。利用二又链表中的空指针域,存放指向该结点在某种遍历次序下的前驱和后继结点的指针(这种附加的指针称为"线索")

  • 这种加上了线索的二叉链表称为线索链表,相应的二叉树称为线索二叉树(Threaded BinaryTree)。根据线索性质的不同,线索二叉树可分为前序线索叉树、中序线索二叉树和后序线索二叉树三种

  • 一个结点的前一个结点,称为前驱结点,一个结点的后一个结点,称为后继结点

代码实现

class TreeNode {
int val;
TreeNode left;
TreeNode right; private int leftType; // 0:左子树 1:前驱节点 private int rightType; // 0:右子树 1:后继节点 public TreeNode(int val) {
this.val = val;
this.left = null;
this.right = null;
}
// 省略get set方法
} class ThreadedBinaryTree { private TreeNode root; //为了实现线索化,需要创建要给指向当前结点的前驱结点的指针
//在递归进行线索化时,pre 总是保留前一个结点
private TreeNode pre = null; public void setRoot(TreeNode root) {
this.root = root;
} public void threadedNodes(){
this.threadedNodes(root);
} // 线索化节点 中序线索化
public void threadedNodes(TreeNode node){ if (node == null){
return;
} // 先线索化左子树
threadedNodes(node.getLeft()); // 线索化当前节点
if (node.getLeft() == null){
//让当前结点的左指针指向前驱结点
node.setLeft(pre);
node.setLeftType(1);
} //处理后继结点
if (pre != null && pre.getRight() == null) {
//让前驱结点的右指针指向当前结点
pre.setRight(node);
//修改前驱结点的右指针类型
pre.setRightType(1);
}
// 让当前结点是下一个结点的前驱结点
pre = node; pre = node; // 线索化右子树
threadedNodes(node.getRight());
}
}

遍历线索化二叉树

因为线索化后,各个结点指向有变化,因此原来的遍历方式不能使用,这时需要使用新的方式遍历线索化二叉树,各个节点可以通过线型方式遍历,因此无需使用递归方式,这样也提高了遍历的效率。 遍历的次序应当和中序遍历保持一致。

代码实现

    // 遍历线索化二叉树
public void threadedNodeList(){
//定义一个变量,存储当前遍历的结点,从root开始
TreeNode node = root; while (node != null){
while (node.getLeftType() == 0){
node = node.getLeft();
} System.out.println(node); while (node.getRightType() == 1){
node = node.getRight();
System.out.println(node);
}
node = node.getRight();
} }

以上面图中的例子,进行代码测试

public class ThreadedBinaryTreeDemo {

    public static void main(String[] args) {
TreeNode root = new TreeNode(1);
TreeNode node2 = new TreeNode(3);
TreeNode node3 = new TreeNode(6);
TreeNode node4 = new TreeNode(8);
TreeNode node5 = new TreeNode(10);
TreeNode node6 = new TreeNode(14); root.setLeft(node2);
root.setRight(node3);
node2.setLeft(node4);
node2.setRight(node5);
node3.setLeft(node6); //测试中序线索化
ThreadedBinaryTree threadedBinaryTree = new ThreadedBinaryTree();
threadedBinaryTree.setRoot(root);
threadedBinaryTree.threadedNodes();
//测试: 以10号节点测试
TreeNode leftNode = node5.getLeft();
TreeNode rightNode = node5.getRight();
System.out.println("10号结点的前驱结点是 =" + leftNode); //3
System.out.println("10号结点的后继结点是=" + rightNode); //1 // 测试遍历
System.out.println("使用线索化的方式遍历 线索化二叉树");
threadedBinaryTree.threadedNodeList(); // 8, 3, 10, 1, 14, 6 } }

二叉树(binary tree)的更多相关文章

  1. [Swift]LeetCode968.监控二叉树 | Binary Tree Cameras

    Given a binary tree, we install cameras on the nodes of the tree. Each camera at a node can monitor  ...

  2. 算法与数据结构基础 - 二叉树(Binary Tree)

    二叉树基础 满足这样性质的树称为二叉树:空树或节点最多有两个子树,称为左子树.右子树, 左右子树节点同样最多有两个子树. 二叉树是递归定义的,因而常用递归/DFS的思想处理二叉树相关问题,例如Leet ...

  3. 二叉树(Binary Tree)相关算法的实现

    写在前面: 二叉树是比较简单的一种数据结构,理解并熟练掌握其相关算法对于复杂数据结构的学习大有裨益 一.二叉树的创建 [不喜欢理论的点我跳过>>] 所谓的创建二叉树,其实就是让计算机去存储 ...

  4. 数据结构-二叉树(Binary Tree)

    #include <stdio.h> #include <string.h> #include <stdlib.h> #define LIST_INIT_SIZE ...

  5. [数据结构]——二叉树(Binary Tree)、二叉搜索树(Binary Search Tree)及其衍生算法

    二叉树(Binary Tree)是最简单的树形数据结构,然而却十分精妙.其衍生出各种算法,以致于占据了数据结构的半壁江山.STL中大名顶顶的关联容器--集合(set).映射(map)便是使用二叉树实现 ...

  6. [LeetCode] Find Leaves of Binary Tree 找二叉树的叶节点

    Given a binary tree, find all leaves and then remove those leaves. Then repeat the previous steps un ...

  7. [LeetCode] Verify Preorder Serialization of a Binary Tree 验证二叉树的先序序列化

    One way to serialize a binary tree is to use pre-oder traversal. When we encounter a non-null node, ...

  8. [LeetCode] Binary Tree Vertical Order Traversal 二叉树的竖直遍历

    Given a binary tree, return the vertical order traversal of its nodes' values. (ie, from top to bott ...

  9. [LeetCode] Binary Tree Longest Consecutive Sequence 二叉树最长连续序列

    Given a binary tree, find the length of the longest consecutive sequence path. The path refers to an ...

  10. [LeetCode] Serialize and Deserialize Binary Tree 二叉树的序列化和去序列化

    Serialization is the process of converting a data structure or object into a sequence of bits so tha ...

随机推荐

  1. DHCP配置;DHCP Relay配置

    目录 DHCP 配置 实验拓扑 实验需求 实验步骤 1. 基于全局地址池的DHCP服务器给客户端分配IP地址 DHCP server 上配置如下 2. 在PC1上设置为DHCP自动获取方式,ipcon ...

  2. Java基础之基础语法与面向对象

    前言 小知识 Java由Sun公司于1995年推出,2009年Sun公司被Oracle公司收购,取得Java的版权 Java之父:James Gosling(詹姆斯·高斯林) 专业术语 JDK:jav ...

  3. 【HMS Core】Android Studio安装Toolkit登录报错{"httpCode":500,"errorCode":"00012005"...

    [问题描述] 在Android Studio安装Toolkit插件,安装后登录,报错 ​ ​ [问题分析] 此种情况一般是由于开发者账户未实名造成的 [解决方案] 1.检查开发者账户是否实名,登录联盟 ...

  4. JVM源码分析:深入剖析java.c文件中JavaMain方法中InitializeJVM的实现

    经过前文<从JDK源码级深入剖析main方法的运行机制>的分析,我们知道了实现JavaMain方法的四个主要步骤: 初始化Java虚拟机 加载主运行类 通过加载的主运行类,获取main方法 ...

  5. ASP.NET Core 6框架揭秘实例演示[39]:使用最简洁的代码实现登录、认证和注销

    认证是一个确定请求访问者真实身份的过程,与认证相关的还有其他两个基本操作--登录和注销.ASP.NET Core利用AuthenticationMiddleware中间件完成针对请求的认证,并提供了用 ...

  6. 浅谈TCP和UDP

    简介 在计算机网络中,TCP(传输控制协议)和UDP(用户数据报协议)是两个常用的传输层协议.它们分别提供了可靠的数据传输和快速的数据传送,成为互联网世界中的双子星.本文将探讨TCP和UDP的特点.优 ...

  7. [ARM 汇编]高级部分—性能优化与调试—3.4.1 性能分析与优化策略

    性能优化是嵌入式系统开发中的一个重要环节,尤其是在资源受限的环境下.性能优化的目标是提高代码执行速度.降低功耗和减少内存占用.在本章节中,我们将讨论性能分析与优化策略,并通过实例来学习如何应用这些策略 ...

  8. Task Execution and Scheduling In SpringBoot

    开天辟地 Task Execution and Scheduling In the absence of an Executor bean in the context, Spring Boot au ...

  9. AOA定位技术原理

    AOA定位技术是一种基于信号到达角度的定位方法,利用单一天线发射寻向讯号,而接收端的装置内建天线阵列, 当信号通过时, 会因阵列中接收到的不同距离, 产生相位差异, 进而计算出相对的信号方向:其原理如 ...

  10. 在Linux环境下通过命令行执行JMeter脚本后查看响应结果的配置

    在Linux环境中进行性能测试时,我们可能会遇到一定程度的报错.如果无法打开JMeter的GUI界面,但又需要查看响应结果,可以按照以下步骤进行配置: 1. 打开JMeter的安装目录,在`bin/` ...