Java数据结构学习之二叉树

1 背景知识:树(Tree)

在之前的笔记中,我们介绍的链表、栈、队列、数组和字符串都是以线性结构来组织数据的。本篇笔记要介绍的采用的是树状结构,这是一种非线性的数据组织形式。

树结构由节点构成,且不存在。我们曾在线性表型的数据结构中介绍过循环链表和循环队列,这两种数据结构使得存储容器中的元素形成一个闭环,具体可参看“数据结构学习笔记”系列的相关博文,链接贴在下面:

链表:https://www.jb51.net/article/215278.htm

队列:https://www.jb51.net/article/211502.htm

树状结构与线性结构最重要的区别在于:树只能有分叉,不能有闭环。如下图所示:

树结构不允许有环其实是树的层级性决定的。树结构中最顶端的结点是根节点, 根节点即整棵树的顶级父节点。除了根节点只有子节点,最底层的节点只有父节点,其余各层的节点都是上层节点的子节点、下层节点的父节点。也就是说,树中的数据只与其上下层的数据有关,同层数据间不能有直接联系,这也就是树结构不能有环的原因。

树层级的多少往往被描述为树的高度(height),由于我们是从上往下观察树结构的,因此也被描述为树的深度(depth)。上面图示中两颗树的深度都是3.

2 何为二叉树(Binray Tree)

2.1 二叉树的概念与结构

二叉树顾名思义,即每个父节点最多只有两个分叉的树,这是数据结构领域使用频率极高的一种树结构,这与我们常常用二元对立的观点认识世界的思维习惯有关。

二叉树的结构不仅具有层级性,还具有递归性,一个父节点连接左子节点右子节点,而左右子节点又可以作为父节点再各自连接两个子节点,以此类推。因此二叉树是一种层次嵌套的数据结构,除了根节点外,树中任意一个父节点都能作为一棵子树,位于上层父节点左侧的子树被称为左子树,位于右侧的子树被称为右子树

二叉树体现了人们用二元思维认识自然的方式。笔者的本行是语言学,语言学界主流的对句法结构的分析方法就是类似于二叉树的二分法。拿汉语的句法结构来说,有主谓、述宾、定中、状中、述补等基本的结构类型。句法结构具有层次嵌套递归的特点,同时也有对语序的要求,即句法二叉树中的左右节点的位置并不是任意的。这种分析方法语言学上被称为层次分析法,如果我们用该方法分析句子“文程同学热爱编程”,传统图示和句法树图示分别如下:

2.2 满二叉树与完全二叉树

二叉树中有两个特殊的结构类型:满二叉树完全二叉树。满二叉树的结构特点是除了最后一层外,所有层级的节点都有两个子节点;完全二叉树的结构特点是除了最后两层外,所有层级的节点都有两个子节点,倒数第二层的子节点(即最后一层节点)全部靠左排列。如下图所示:

由此可见,满二叉树一定是完全二叉树,完全二叉树可满可不满。这两种二叉树体现了我们采用树状结构存储数据时,对于空间利用率的追求。比如我们设计一个深度为n的二叉树,那么整个二叉树能容纳的最大节点数为2^n-1,满二叉树就是达到了最大节点数,用足了二叉树的容量。完全二叉树除了n层没有子节点,除n-1层外各层父节点都充分利用了自己拥有子节点的名额,也算是尽可能做到了对空间的充分利用。

为了更好地理解完全二叉树的空间利用率,我们看一个非完全二叉树的例子,如下图所示:

上图是一个深度为4的非完全二叉树,前3层的父节点都预留了左右两个子节点的位置,然而第二层的第2个结点只使用了右子节点的空间,浪费了左子节点的空间。如果二叉树的深度很深,其中很多层级的父节点都存在浪费子节点“名额”的现象,那么会造成相当大的空间资源的浪费,二叉树也失去了“二叉”的意义。但是完全二叉树最多浪费倒数第二层父节点的子节点名额, 整体上能够保证较高的空间利用率。

2.3 二叉树的三种遍历方式

二叉树的形状整体上构成一个三角形,最小的二叉树由一个位于中间的父节点和位于左右两侧的子节点构成,这导致遍历访问一棵二叉树的所有节点有三种顺序:前序遍历(Preorder Traversal , VLR)、中序遍历(Inorder Traversal , LDR)和后序遍历(Inorder Traversal , LRD)。

无论哪种遍历方式,二叉树都是从上到下、从左到右遍历的,即从父节点层到子节点层、从左子树到右子树。2.1解释了二叉树的递归性,遍历二叉树时采用的也是递归(recursion)的方式。对于整棵树或某一子树,都是从根开始,先遍历其左子树,再遍历其右子树;分别遍历左右子树时,同样是从根开始,从左向右遍历;以此类推,直到遍历到最后一个右子节点。

如果我们以打印节点数据的方式来表示对节点的访问,那么前序、中序和后序的区别就在于打印节点的时机不同。前序遍历的操作顺序是打印节点在遍历左子树和遍历右子树之前中序遍历的操作顺序是打印节点在遍历左子树和遍历右子树之间后序遍历的操作顺序是打印节点在遍历左子树和遍历右子树之后。子树遍历的过程是递归实现的。

如果我们想遍历2.1演示的“文程同学热爱编程”的句法二叉树,那么用三种遍历方法得到的遍历结果分别如下:

3 二叉树及其遍历的简单实现(Java)

我们用Java语言实现“文程同学热爱编程”这个句子对应的句法二叉树,设计思路是:将同层级的父节点(二叉树及其各子树的根节点)存入数组中,数组中存入的是结点,包括数据左右指针,左右指针分别指向位于下一层节点的左右子节点,如果没有子节点则指针为空指针。

用数组的好处是,可以通过节点所在的索引建立上下层级父节点和子节点的指针联系。假设父节点在它所在的层级数组中的索引为i,那么左子节点在它所在层级数组中的索引为(i+1)*2-2,右子节点的索引为(i+1)*2-1,即左子节点的索引+1。

遍历默认从整棵二叉树的根节点开始,通过方法的重写实现默认参数的效果。

准备工作1:MyBinaryTree.java,创建一个二叉树的类

package com.notes.data_structure6;

import com.notes.data_structure6.NumberOfNodesException;

public class MyBinaryTree {
	// 树的根结点
	private Node[] root;
	// 树的深度(当前层级数)
	private int depth;
	// 将每一层所有的 结点 都存储在数组中,结点数是 2的 层数 次幂
	private Node[] currentLevel;

	public MyBinaryTree(String data) {
		Node[] rootArray = new Node[] {new Node(data)};
		this.root = rootArray;
		this.currentLevel = rootArray;
	}

	// 定义一个结点类
	private class Node{
		private String data; // 数据
		private Node leftNext; // 左指针
		private Node rightNext; // 右指针

		// 构造方法:Node实例化时传入数据
		public Node(String data) {
			this.data = data;
		}
	}

	// 向树中增加一层结点
	public void add(String[] datas) throws NumberOfNodesException {
		// 层级数增加1
		depth++;
		// 新增 层级 的最大结点数
		int nodeNum = numberOfNextNodes();
		// 如果传入的 数据数 与 当前层级 最大结点数 不符,抛出异常
		if(datas.length != nodeNum) {
			throw new NumberOfNodesException("第"+depth+"层最大父节点数为"+nodeNum);
		}
		// 将传入的 数据数组 转换为 结点数组
		Node[] newLevel = new Node[nodeNum];
		// 当前 层级的 结点数量(新增层级的父)
		int nodeNum2 = (int) Math.pow(2, depth-1);
		// 让每一个结点都与上层 父结点 建立连接
		for(int i=0;i<nodeNum2;i++) {
			// 让父结点 的左指针 指向 左子结点
			int leftIndex = (i+1)*2-2; // 计算左子结点对应的新层级数组的索引
			currentLevel[i].leftNext = new Node(datas[leftIndex]); // 建立指针指向
			newLevel[leftIndex] = currentLevel[i].leftNext; // 将结点加入新层级结点数组
			// 让父结点 的右指针 指向 右子结点
			int rightIndex = leftIndex+1; // 计算右子结点对应的新层级数组的索引
			currentLevel[i].rightNext = new Node(datas[rightIndex]); // 建立指针指向
			newLevel[rightIndex] = currentLevel[i].rightNext; // 将结点加入新层级结点数组
		}
		// 让新增层级的数组成为当前层级的数组
		currentLevel =  newLevel;
	}

	// 前序遍历所有结点
	public void preTraversal(Node node) {
		if(node==null) {
			return;
		}
		System.out.print(node.data+" ");
		preTraversal(node.leftNext);
		preTraversal(node.rightNext);
	}

	// 重写 前序遍历 方法,让遍历从 根结点 开始
	public void preTraversal() {
		Node node = root[0];
		if(node==null) {
			return;
		}
		// 递归时调用带参数的方法
		System.out.print(node.data+" ");
		preTraversal(node.leftNext);
		preTraversal(node.rightNext);
	}

	// 中序遍历所有结点
	public void midTraversal(Node node) {
		if(node==null) {
			return;
		}
		midTraversal(node.leftNext);
		System.out.print(node.data+" ");
		midTraversal(node.rightNext);
	}

	// 重写中序遍历 方法,让遍历从 根结点 开始
	public void midTraversal() {
		Node node = root[0];
		if(node==null) {
			return;
		}
		// 递归时调用带参数的方法
		midTraversal(node.leftNext);
		System.out.print(node.data+" ");
		midTraversal(node.rightNext);
	}

	// 后序遍历所有结点
	public void posTraversal(Node node) {
		if(node==null) {
			return;
		}
		posTraversal(node.leftNext);
		posTraversal(node.rightNext);
		System.out.print(node.data+" ");
	}

	// 重写后序遍历 方法,让遍历从 根结点 开始
	public void posTraversal() {
		Node node = root[0];
		if(node==null) {
			return;
		}
		// 递归时调用带参数的方法
		posTraversal(node.leftNext);
		posTraversal(node.rightNext);
		System.out.print(node.data+" ");
	}

	// 查看 新增层级 的最大结点数
	public int numberOfNextNodes() {
		return (int) Math.pow(2,depth);
	}

	// 查看 树 的深度(层级数)
	public int getDepth() {
		return depth;
	}

}

准备工作2:NumberOfNodesException.java,为add()方法创建一个自定义异常,如果传入的数据数与当前层级最大结点数不符,则抛出该异常(如果二叉树不满,在数组的相应位置传入null)。

package com.notes.data_structure6;

public class NumberOfNodesException extends Exception{
	public NumberOfNodesException(String message) {
		super(message);
	}
}

句法二叉树的实现及其遍历:TreeDemo.java

package com.notes.data_structure6;

public class TreeDemo {
	public static void main(String[] args) throws NumberOfNodesException {
		// 实例化二叉树类,并且传入根节点的数据
		MyBinaryTree tree = new MyBinaryTree("句子");
		// 加入第一层节点的数据
		tree.add(new String[] {"主语","谓语"});
		// 加入第二层节点的数据
		tree.add(new String[] {"定语","中心语","述语","宾语"});

		// 前序遍历
		tree.preTraversal(); // 句子 主语 定语 中心语 谓语 述语 宾语
		System.out.println();
		// 中序遍历
		tree.midTraversal(); // 定语 主语 中心语 句子 述语 谓语 宾语
		System.out.println();
		// 后序遍历
		tree.posTraversal(); // 定语 中心语 主语 述语 宾语 谓语 句子
	}
}

到此这篇关于Java数据结构学习之二叉树的文章就介绍到这了,更多相关Java二叉树内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Java求解二叉树的最近公共祖先实例代码

    一.题目 给定一个二叉树, 找到该树中两个指定节点的最近公共祖先. 百度百科中最近公共祖先的定义为:"对于有根树 T 的两个结点 p.q,最近公共祖先表示为一个结点 x,满足 x 是 p.q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)." 例如,给定如下二叉树: root = [3,5,1,6,2,0,8,null,null,7,4] 二.分析 本题需要找公共祖先,如果可以从下往上查找,就可以很方便的找到公共祖先 所以需要先访问叶子节点,然后在往上访问,对应着二叉树的

  • java编程题之从上往下打印出二叉树

    本文实例为大家分享了java从上往下打印出二叉树的具体代码,供大家参考,具体内容如下 github:剑指offer编程全部试题 import java.util.ArrayList; import java.util.Stack; /** * * 剑指offer编程题(JAVA实现)--第22题:从上往下打印出二叉树 * * 题目描述 * 从上往下打印出二叉树的每个节点,同层节点从左至右打印. * */ public class Test22 { ArrayList<Integer> arra

  • Java实现二叉树的深度优先遍历和广度优先遍历算法示例

    本文实例讲述了Java实现二叉树的深度优先遍历和广度优先遍历算法.分享给大家供大家参考,具体如下: 1. 分析 二叉树的深度优先遍历的非递归的通用做法是采用栈,广度优先遍历的非递归的通用做法是采用队列. 深度优先遍历:对每一个可能的分支路径深入到不能再深入为止,而且每个结点只能访问一次.要特别注意的是,二叉树的深度优先遍历比较特殊,可以细分为先序遍历.中序遍历.后序遍历.具体说明如下: 先序遍历:对任一子树,先访问根,然后遍历其左子树,最后遍历其右子树. 中序遍历:对任一子树,先遍历其左子树,然

  • JAVA二叉树的几种遍历(递归,非递归)实现

    首先二叉树是树形结构的一种特殊类型,它符合树形结构的所有特点.本篇博客会针对二叉树来介绍一些树的基本概念,二叉树的基本操作(存储,返回树的深度,节点个数,每一层的节点个数),二叉树的四种遍历(层次,先序,中序,后序) 一.基本概念 二叉树有5种基本形态: 注:二叉树有序树,就是说一个节点的左右节点是有大小之分的,我们通常设定为左孩子一定大于右孩子,下面的实现都是基于这个规则的.二叉树分为三种:满二叉树,完全二叉树,不完全二叉树 二叉树的四种遍历:层次,先序,中序,后序首先是非递归实现上图的满二叉

  • Java二叉树的四种遍历(递归和非递归)

    二叉树的遍历可以分为前序.中序.后序.层次遍历. 前中后是指何时访问中间节点,即前序遍历,遍历节点的顺序为:中->左->右: 中序遍历,遍历节点的顺序为:左->中->右: 后序遍历,遍历节点的顺序为:左->右->中. 前序遍历 递归实现 public void preorder_Traversal(TreeNode root) { if(root==null)return; //访问节点的逻辑代码块 System.out.print(root.val+" &q

  • Java数据结构学习之二叉树

    1 背景知识:树(Tree) 在之前的笔记中,我们介绍的链表.栈.队列.数组和字符串都是以线性结构来组织数据的.本篇笔记要介绍的树采用的是树状结构,这是一种非线性的数据组织形式. 树结构由节点和边构成,且不存在环.我们曾在线性表型的数据结构中介绍过循环链表和循环队列,这两种数据结构使得存储容器中的元素形成一个闭环,具体可参看"数据结构学习笔记"系列的相关博文,链接贴在下面: 链表:https://www.jb51.net/article/215278.htm 队列:https://ww

  • Java数据结构学习之树

    一.树 1.1 概念 与线性表表示的一一对应的线性关系不同,树表示的是数据元素之间更为复杂的非线性关系. 直观来看,树是以分支关系定义的层次结构. 树在客观世界中广泛存在,如人类社会的族谱和各种社会组织机构都可以用树的形象来表示. 简单来说,树表示的是1对多的关系. 定义(逻辑结构): 树(Tree)是n( n>=0 )个结点的有限集合,没有结点的树称为空树,在任意一颗非空树中: 有且仅有一个特定的称为根(root)的结点 . 当n>1的时,其余结点可分为 m( m>0 ) 个互不相交的

  • java数据结构之搜索二叉树

    本文实例为大家分享了java数据结构之搜索二叉树的具体代码,供大家参考,具体内容如下 搜索二叉树的定义是:在一个二叉树上,左节点一定比父节点小,右节点一定比父节点大,其他定义跟二叉树相同. 代码实现: public class node {     int data;     public node left, right=null;       public node(int data) {         this.data = data;       }       public node

  • Java数据结构学习之栈和队列

    一.栈 1.1 概述 Java为什么要有集合类: 临时存储数据. 链表的本质: 对象间通过持有和引用关系互相关联起来. 线性表: 普通线性表, 操作受限线性表(某些操作受到限制 --> 某一个线性表它的增删改操作受到限制) --> 栈 & 队列 1.1.1 线性表的概念 (1)线性表:n个数据元素的有序序列. ①首先,线性表中元素的个数是有限的. ②其次,线性表中元素是有序的. (2)那这个"序"指的是什么呢? ①除表头和表尾元素外,其它元素都有唯一前驱和唯一后继,

  • Java数据结构最清晰图解二叉树前 中 后序遍历

    目录 一,前言 二,树 ①概念 ②树的基础概念 三,二叉树 ①概念 ②两种特殊的二叉树 ③二叉树的性质 四,二叉树遍历 ①二叉树的遍历 ②前序遍历 ③中序遍历 ④后序遍历 五,完整代码 一,前言 二叉树是数据结构中重要的一部分,它的前中后序遍历始终贯穿我们学习二叉树的过程,所以掌握二叉树三种遍历是十分重要的.本篇主要是图解+代码Debug分析,概念的部分讲非常少,重中之重是图解和代码Debug分析,我可以保证你看完此篇博客对于二叉树的前中后序遍历有一个新的认识!!废话不多说,让我们学起来吧!!

  • Java数据结构二叉树难点解析

    前言 本章,我们主要需要了解以下内容 什么是线索二叉树 怎么去把二叉树线索化 怎么通过线索二叉树查找某个数的后继结点 二叉树的查看--二叉树怎们遍历 什么是线索二叉树 首先我们来了解一下什么是线索二叉树? 定义:一个二叉树通过如下的方法"穿起来":所有原本为空的右(孩子)指针改为指向该节点在中序序列中的后继,所有原本为空的左(孩子)指针改为指向该节点的中序序列的前驱. 再看一下为什么要有线索二叉树? 顾名思义,线索二叉树,肯定是根据线索查找,查找速度肯定更快. 线索二叉树能线性地遍历二

  • Java数据结构与算法学习之双向链表

    目录 双向链表的储存结构示意图 双向链表的初始化结构 1.双向链表的结点 2.双向链表的头结点 3.总代码 双向链表中的指定文件插入元素  1.插入的为第一个位置 2.其他位置插入 总代码 双向链表的删除 1.删除第一个元素 2.删除其他位置元素 总代码 双向链表的储存结构示意图 双向链表的初始化结构 1.双向链表的结点 代码实现 //双向链表的结点,包含一个数据域,两个指针域 typedef struct DoublyNode { ElementType date; //数据域 struct

  • Java数据结构与算法学习之循环链表

    目录 存储结构示意图 初始化循环链表  循环链表的插入 首位置 代码实现 其他位置 代码实现(总) 循环链表的删除 1.操作的为第一个元素 2.操作元素不为第一个元素 代码实现(总) 循环链表的常见操作  存储结构示意图 优点 : 能够通过任意结点遍历整个链表结构 初始化循环链表  1.循环链表的结点 typedef struct CircularNode { ElementType date; //数据域 struct CircularNode* next; //指向下一个结点的指针域 }Ci

  • Java 数据结构进阶二叉树题集下

    目录 1.对称二叉树 2.创建并遍历二叉树 3.二叉树中两节点最近公共祖先 4.二叉搜索树与双向链表 5.根据前序和中序遍历结果创建二叉树 6.二叉树创建字符串 7.非递归实现二叉树前序遍历 8.非递归实现二叉树后序遍历 1.对称二叉树 [OJ链接] 分为以下几种情况: 二叉树为空,是对称二叉树 二叉树不为空,其左子树或者右子树为空,不是对称二叉树 二叉树不为空,左右子树都为空,是对称二叉树 二叉树不为空,左右子树不为空,左右子节点值不同,不是对称二叉树 二叉树不为空,左右子树不为空,左右子节点

  • Java 数据结构进阶二叉树题集上

    目录 1.二叉树的遍历 (1)前.中.后序遍历 (2)层序遍历 2.获取树中子结点的个数 3.获取二叉树的高度 4.判断是不是完全二叉树 5.判断两个树是否相同 6.另一棵树的子树 7.判断平衡二叉树 二叉树操作的代码大多数使用递归来实现,代码会比较简洁,如果使用非递归,代码会比较的繁荣,而且不易理解.(上)中的题偏向于基础,后面(下)中的题机会比较难. 1.二叉树的遍历 (1)前.中.后序遍历 这里写到的遍历是递归遍历,代码比较简单,后续会写非递归的代码.以前序遍历为例: 如果根节点root为

随机推荐