数据结构--Chapter5(树与二叉树)

ESP32-S3双模CMSIS-DAP调试器:USB+WiFi无线调试方案 CMSIS-DAP是ARM生态中标准化的嵌入式调试协议接口,广泛用于Keil、IAR、STM32CubeIDE等开发环境。其核心原理是通过SWD/JTAG物理层实现目标芯片寄存器访问、内存读写与断点控制,依赖确定性时序和低延迟通信。传统DAPLink调试器受限于USB线缆束缚,而WiFi-USB-DAPLink项目将完整CMSIS-DAP v2协议栈移植至ESP32-S3平台,同时支持USB CDC直连与WiFi TCP远程通道,兼顾实时性与网络化部署能力。该方案无需主机驱动、兼容主流IDE,并支持多机并发 阅读详情

5 树与二叉树

5.1 树的基本概念

5.1.1 树的定义

    树是由n(n>=0)个结点所构成的有限集合,当n=0时,称为空树。当n>0时,n个结点满足以下条件:

1) 有且仅有一个称为根的结点。

2)其余结点可分为m(m>=0)个互不相交的有限集合,且每个集合又构成一棵树,这课树称为根结点的子树。

    树的层次结构体现了数据元素之间具有的层次关系,即对于一颗非空树,其中有且仅有一个没有前驱的结点,这个结点就是根结点,其余结点有且仅有一个前驱,单可以有多个后继。


5.1.2 树的常用术语

1)树的结点

    树的结点是由一个数据元素及关联子树的边所组成。例如,上图中的a中只有一个结点,b中有3个节点,c中有13个结点。在计算机程序中,结点中的数据元素一般代表着一些实体,节点中的边代表着实体与实体之间的逻辑关系。

2)结点的路径

     结点的路径是指从根根结点到该结点所经历的结点和分支的顺序排序。例如,上图中c中结点J的路径是A->D->J。

3)路径的长度

    路径的长度是指节点中路径所包含的分支数。例如,上图c中结点J的路径长度为2。

4)结点的度

    结点的度是指该结点所拥有子树的数目。例如,上图c中结点A的度为3,C的度为0。

5)树的度

    树的度是指书中所有结点的度的最大值。例如,上图a中树的度为0,b中树的度为1,c中树的度为3。

6)叶结点(终端结点)

    叶结点是指树中度为0的结点,叶结点也称为终端结点。例如,上图c中C、E、F、H、I、J、K、L、M都是叶结点。

7)分支结点(非终端 结点)

     分支结点是指树中度不为0的节点,分支结点也称为非终端结点。树中除叶结点之外的所有结点都是分支结点。

8)孩子结点(子结点)

    一个结点的孩子结点是指这个结点的子树的根结点。例如,上图c中结点B、C、D是结点A的孩子结点,或者说结点A的孩子结点是B、C、D。

9)双亲结点(父结点)

    一个结点的双亲结点是指:若树中某个结点有孩子结点,则这个结点就称为孩子结点的双亲结点。例如,上图c中结点A是指结点B、C、D的双亲结点。双亲结点和孩子结点也称为是具有互为前驱和后继关系的结点,其中,双亲结点是孩子结点的前驱,而孩子结点是双亲结点的后继。

10)子孙结点

    一个结点的子孙结点是指这个结点的所有子树中的任意结点。例如,上图c中结点B的子孙结点有E、F、G、K、L、M。

11)祖先结点

    一个结点的祖先结点是指该结点的路径中除此结点之外的所有结点。例如,上图c中结点L的祖先结点有A、B、G。

12)兄弟结点

    兄弟结点是指具有同一双亲的结点。例如,上图c中B、C、D是兄弟节点,它们的双亲结点都是结点A。

13)节点的层次

    规定树中根结点的层次为0,则其他结点的层次是其双亲结点的层次加1。例如,上图c中节点K的层次数为3,也可称节点K在树中处于第3层上。

14)树的深度

     树的深度是指树中所有结点的层次数的最大值加1。例如,上图a中中树的深度为1,b中树的深度为3,c中树的深度为4。

15)有序树

    有序树是指树中各结点的所有子树之间从左到右有严格的次序关系,不能互换。也就是说,如果子树的次序不同则对应着不同的有序树。

16)无序树

    与有序树相反,无序树是指树中各结点的所有子树之间没有严格的次序关系。

17)森林

    森林是指由m(m>=0)课互不相交的树构成的集合。

5.2 二叉树

5.2.1 二叉树概述

    二叉树是一种特殊的树,它的每个结点最多只有两颗子树,并且这两颗子树也是二叉树。由于二叉树中两颗子树有左、右之分,所以二叉树是有序树。下面给出二叉树的具体定义。

1. 二叉树的定义

    二叉树是由n(n>=0)个结点所构成的有限集合。当n=0时,这个集合为空,此时的二叉树为空树;当n>0时,这个集合是由一个根结点和两个互不相交的分别称为左子树和右子树的二叉树构成。

    二叉树与树的区别:

1)树中每个结点都可以有多颗子树,而二叉树中的每个结点最多有两颗子树

2)树中的子树是不分顺序的,而二叉树中的子树有严格的左、右之分。

3)二叉树与度小于2等于的树也不同。在二叉树中允许某些结点只有右子树而没有左子树,而在树中,一个结点若没有第一棵子树,则它就不可能有第二棵子树存在。

2. 满二叉树的定义

    满二叉树是二叉树的一种特殊形态。如果在一棵二叉树中,它的所有结点或是叶结点,或是左、右子树都非空,并且所有叶结点都在同一层上,则称这可二叉树为满二叉树。

3. 完全二叉树的定义

    完全二叉树也是二叉树的一种特殊形态。如果在一棵具有n个结点的二叉树中,它的逻辑结构与满二叉树前n个结点的逻辑结构相同,则成这样的二叉树而完全二叉树。

4. 单分支树的定义

    若二叉树的所有结点都没有右孩子或没有左孩子,则此二叉树为单分支树。单分支树又可分为左支树和右支树,其中所有结点都没有右孩子的二叉树称为左支树,所有结点都没有左孩子的树称为右支树。


5.2.2 二叉树的性质

性质1 二叉树中第i(i>=0)层上的结点树最多为2^i。

性质2 深度为h(h>=1)的二叉树中最多有2^h-1个结点。

性质3 对于任何一棵二叉树,若其叶结点的个数为n0,度为2的节点个数为n2,则有n0=n2+1。

性质4 具有n个结点的完全二叉树,其深度为int(log2n)+1。

性质5 有n个结点的完全二叉树,若从根结点开始自上到下并且按照层次有左向右对结点从0开始变编号,则对于任意一个编号i(0<=i<n)的结点有:

1)若i=0,则编号为i的节点时二叉树的根结点,它没有双亲;若i>1,则编号为i的结点其双亲的编号为

2)若2i+1>=n,则编号为i的结点无左孩子,否则编号为2i+1的结点就是其左孩子

3)若2i+1>=n,则编号为i的结点无右孩子,否则编号为2i+2的结点就是其右孩子

5.2.3 二叉树的存储结构

1.  二叉树的顺序存储结构

    二叉树的顺序存储结构是指按照某种顺序依次将二叉树中的各个结点的值存放在一组地址连续的存储单元中。由于二叉树是非线性结构的,所以需要将 二叉树的各个结点先按照一定的顺序排列成一个线性序列,再通过这些节点在线性序列中的相对位置,确定二叉树中的各个结点之间的逻辑关系。对于一棵完全二叉树,可以从根结点开始自上而下并按照层次有左向右对结点一次进行编号,然后按照编号顺序依次将其存放在一维数组中,其结点之间的逻辑关系可有性质5中父结点与孩子结点编号之间的关系式计算得到。对于一棵非完全二叉树,可有现在此树中添加一些并不存在的虚结点并使其成为一棵完全二叉树,然后用与完全二叉树相同的方法对结点进行编号,再将编号为i的结点的值存放到数组下表为i的数组单元总,虚结点不存放任何值。

    对于满二叉树和完全二叉树,顺序存储是一种最简单、最节省空间的存储方式,而且能是操作简单,所以顺序存储方式非常适合用于满二叉树和完全二叉树。但对于非完全二叉树,由于有“虚结点”的存在从而造成了存储空间的浪费,特别是对右支树来说,若其深度为h,则此右支树只有h个结点,却要为此分配2^h-1个存储单元,这种情况下的存储空间浪费最大。

2. 二叉树的链式存储结构

    二叉树的链式存储结构是指将二叉树的各个结点随机地存放在位置任意的内存空间中,各个结点之间逻辑关系通过指针来反映。由于二叉树中的任意一个结点至多只有一个双亲结点和两个孩子结点,所以在用链式存储方式来实现而差事的存储时,可以有两种形式,一种是二叉链表存储结构,另一种是三叉链表存储结构。在二叉链表机构中,二叉树中的每一个结点设置有3个域:数据域、左孩子域和右孩子域,其中,数据域用来存放结点的值;左、右孩子域分别用来存放该结点的左、右孩子结点的存储地址,三叉链表结构是指在二叉链表结构中增加一个父结点域,该域用来存放此结点的父结点的存储地址。


    三叉链表存储结构既方便查找孩子结点,又便于查找双亲结点,但它相对于二叉链表存储结构来说,增加了存储空间的开销。因此,二叉链式存储结构是二叉树中最常用的存储结构。

    二叉链表存储结构的结点类描述如下:

package tree;

public class BiTreeNode {
	public Object data;// 结点的数据域
	public BiTreeNode lchild, rchild;// 左、右孩子域

	public BiTreeNode() {
		this(null);
	}

	public BiTreeNode(Object data) {
		this(data, null, null);
	}

	public BiTreeNode(Object data, BiTreeNode lchild, BiTreeNode rchild) {
		this.data = data;
		this.lchild = lchild;
		this.rchild = rchild;
	}

}


3. 二叉链式存储结构下二叉树类的描述

5.3 二叉树的遍历

   二叉树的遍历是指沿着某条搜索路径对二叉树中的结点进行访问,使得每个结点均被访问一次,而且仅被访问一次。其中“访问”的含义较为广泛,它可以是输出二叉树中的结点信息,也可以是对结点进行任何其他处理。

5.3.1 二叉树的遍历方法及其实现

1. 二叉树的遍历方法

    由二叉树的结构特点,可以将二叉树划分为3个部分:根结点、左子树和右子树;其次,二叉树的所有结点都是有层次之分。因此,对于一棵二叉树来说,它有3条搜索路径,分别是:先上后下、先左(子树)后右(子树)和先右(子树)后左(子树)。如果规定用D、L和R分别表示访问根结点、处理左子树和处理右子树,则可得到二叉树的7种遍历方法:层次遍历、DLR、LDR、LRD、DRL、RDL和RLD。现在给出前面4种遍历方法的定义。

1)层次遍历操作

     若二叉树为空,则为空操作;否则,先访问第0层的根结点,然后从左到右依次访问第1层的每一个结点,以此类推,当第i层的所有结点访问完成后,再从左到右依次访问第i+1层的每一结点,直到最后一层的所有结点都访问完为止。

    根据根的访问的先后次序不同,这里将DLR称为先根或先序遍历;LDR称为中根或中序遍历;LRD称为后根或后序遍历。由于二叉树是递归定义的,所以DLR、LDR、LRD3种遍历方法给出的定义也都是递归定义的。

2)先根遍历(DLR)操作

    若二叉树为空,则为空操作;否则

➀访问根结点

➁先根遍历左子树

➂先根遍历右子树

3)中根遍历(LDR)操作

    若二叉树为空,则为空操作;否则

➀中根遍历左子树

➁访问根结点

➂中根遍历右子树

4)后根遍历(LRD)操作

    若二叉树为空,则为空操作;否则

➀后根遍历左子树

➁后根遍历右子树

➂访问根结点

    采用不同方法对二叉树进行遍历可以得到二叉树结点的不同线性序列。

2. 二叉树遍历操作实现的递归算法

	/**
	 * 先根遍历操作实现的递归算法
	 */
	public void preRootTraverse(BiTreeNode t) {
		if (t != null) {
			System.out.print(t.data);// 访问根结点
			preRootTraverse(t.lchild);// 先根遍历左子树
			preRootTraverse(t.rchild);// 先根遍历右子树
		}
	}

	/**
	 * 中根遍历操作实现的递归算法
	 */
	public void inRootTraverse(BiTreeNode t) {
		if (t != null) {
			inRootTraverse(t.lchild);// 中根遍历左子树
			System.out.print(t.data);// 访问根结点
			inRootTraverse(t.rchild);// 中根遍历右子树
		}
	}

	/**
	 * 后根遍历操作实现的递归算法
	 */
	public void postRootTraverse(BiTreeNode t) {
		if (t != null) {
			postRootTraverse(t.lchild);// 后根遍历左子树
			postRootTraverse(t.rchild);// 后根遍历右子树
			System.out.print(t.data);// 访问根结点
		}
	}

    先根遍历是指每次进入一层递归调用时先访问根结点,然后依次对它的左、右子树执行递归调用;中根遍历是指在执行完左子树的递归调用后再访问根结点,然后对右子树执行递归调用;后根遍历则是指执行完左、右子树的递归调用后,最后访问根结点。

3. 二叉树遍历操作实现的非递归算法

    将递归算法转换成非递归算法有两种方式:一种是直接转换法,不需要回溯;另一种是间接转换法,需要回溯。前置使用一些变量保存中间结果,递归过程用循环结构来替代;后者需要利用栈保存中间结果,故引入一个栈操作,并依照递归算法执行过程中编译栈的工作原理,得到相应的非递归算法。二叉树便利操作的非递归算法的实现采用的就是间接间接转换法。

1)先根遍历操作的实现

    先根遍历的递归过程是指先访问根结点,再沿着该结点的左子树向下一次访问其左子树的根结点,直到最后访问的结点都是无左子树为止,再继续依次向上先根遍历根结点的右子树。

    先根遍历的非递归过程则需要借助一个栈来记载当前被访问结点右孩子结点,以便遍历完一个结点的左子树后能顺利地进入这个结点的右子树继续进行遍历。

    实现先根遍历操作的非递归算法的主要思想:从二叉树的根结点出发,沿着该结点的左子树向下搜索,在搜索过程中每遇到一个结点就先访问该结点,并将该结点的非空右孩子结点压栈。当左子树结点访问完成后,从栈顶弹出结点的右孩子结点,然后用上述相同的方法去遍历该结点的右子树,以此类推,直到二叉树中所有结点都被访问为止。其主要操作过程可描述如下;

1)创建一个栈对象,根结点入栈。

2)当栈为非空时,将栈顶结点弹出栈内并访问该结点。

3)对当前访问结点的非空左孩子结点相继依次访问,并将当前访问结点的非空右孩子结点压入栈内。

4)重复执行步骤2)和3),直到栈为空为止。

	public void preRootTraverse() {
		BiTreeNode t = root;
		if (t != null) {
			LinkStack s = new LinkStack();// 构造栈
			s.push(t);// 根结点入栈
			while (!s.isEmpty()) {
				t = (BiTreeNode) s.pop();// 移除栈顶结点,并返回其值
				System.out.print(t.data);// 访问根结点
				while (t != null) {
					if (t.lchild != null) {// 访问左孩子
						System.out.print(t.lchild.data);// 访问结点
					}
					if (t.rchild != null) {// 右孩子非空入栈
						s.push(t.rchild);
					}
					t = t.lchild;
				}
			}
		}
	}


2)中根遍历操作的实现

    中根遍历的非递归过程也要借助一个栈来记载遍历过程中所经历的而未被访问所有结点,以遍历完一个结点的左子树后能顺利地返回到它的父结点。

    实现后根遍历操作的非递归算法的主要思想:从二叉树的根结点出发,沿着该结点的左子树向下搜索,在搜索过程中每遇到一个结点就依次压栈,直到二叉树中最左下的结点压栈为止,然后从栈中弹出栈顶结点并对其进行访问,访问完后再进入该结点的右子树并用上述同样的方法去遍历该结点的右子树,以此类推,直到二叉树中所有结点都被访问为止。其主要操作过程可描述如下;

1)创建一个栈对象,根结点入栈。

2)当栈为非空时,将栈顶结点的非空左孩子相继入栈。

3)栈顶结点出栈并访问非空栈顶结点,并使该栈顶结点的非空右孩子结点入栈。

4)重复执行步骤2)和3),直到栈为空为止。

	public void inRootTraverse() {
		BiTreeNode t = root;
		if (t != null) {
			LinkStack s = new LinkStack();// 构造站节点栈结点
			s.push(t);// 根结点入栈
			while (!s.isEmpty()) {
				while (s.peek() != null) {// 将栈顶结点的左孩子结点相继入栈
					s.push(((BiTreeNode) s.peek()).lchild);
				}
				s.pop();// 空结点退栈
				if (!s.isEmpty()) {
					t = (BiTreeNode) s.pop();// 移除栈顶结点,并返回其值
					System.out.print(t.data);// 访问结点
					s.push(t.rchild);// 结点的右孩子入栈
				}
			}
		}
	}


3)后根遍历操作的实现

    由于后根遍历是先处理左子树,后处理右子树,最后才访问根结点,所以在遍历搜索过程中也是从二叉树的根结点出发,沿着该结点的左子树向下搜索,在搜索过程中每遇到一个结点判断该结点是否第一次经过,若是,则不立即访问,而是将该结点入栈保存,遍历该结点的左子树;当左子树遍历完毕后再返回到该结点,这时还不能访问该结点,而是应继续进入该结点的右子树进行遍历;当左、右子树均遍历完毕后,才能从栈顶弹出该结点并访问它。由于在决定栈顶结点是否能访问时,需要知道该结点的右子树是否被遍历完毕,因此为解决这个问题,在算法中引入一个布尔型的访问编辑变量flag和一个结点指针p。其中,flag用来标记当前栈顶结点是否被访问,当值为true时,表示栈顶结点已被访问;当值为false时,表示当前栈顶结点未被访问,指针p指向当前遍历过程中最后一个被访问的结点。若当前栈顶结点的右孩子结点是空,或者就是p指向的结点,则表明当前结点的右子树已遍历完毕,此时就可以访问当前栈顶结点。其操作的实现过程描述如下:

1)创建一个栈对象,根结点入栈,p赋初始值null。

2)若栈非空,则栈顶结点的非空左孩子相继入栈

3)若栈非空,查看栈顶结点,若栈顶结点的右孩子为空,或者与p相等,则将栈顶结点弹出并访问它,同时使p指向该结点,并置flag值为true;否则,将栈顶结点的右孩子压入栈,并置flag值为false。

4)若flag值为true,则重复执行步骤3);否则,重复执行步骤2)和3),直到栈为空为止。

	public void postRootTraverse() {
		BiTreeNode t = root;
		if (t != null) {
			LinkStack s = new LinkStack();// 构造链栈
			s.push(t);// 根结点入栈
			Boolean flag;// 访问标记
			BiTreeNode p = null;// p指向刚被访问的结点
			while (!s.isEmpty()) {
				while (s.peek() != null) {// 将栈顶结点的左孩子相继入栈
					s.push(((BiTreeNode) s.peek()).lchild);
				}
				s.pop();// 空结点出栈
				while (!s.isEmpty()) {
					t = (BiTreeNode) s.peek();// 查看栈顶元素
					if (t.rchild == null || t.rchild == p) {
						System.out.print(t.data);// 访问结点
						s.pop();// 移除栈顶元素
						p = t;// p指向刚被访问的结点
						flag = true;// 设置访问标记
					} else {
						s.push(t.rchild);// 右孩子结点入栈
						flag = false;// 设置未被访问标记
					}
					if (!flag) {
						break;
					}
				}
			}
		}
	}


4)层次遍历操作实现

    层次遍历操作的实现过程中需要使用一个队列作为辅助的存储结构,这里使用了链队列类LinkQueue来创建一个队列对象l。在遍历开始时,首相将根结点入队,然后每次从队列中取出队首元素进行处理,每处理一个结点,都是先访问该结点,再按照从左到右的顺序把它的孩子结点一次入队。这样,上一层的结点总排在下一层结点的前面,从而实现了二叉树的层次遍历。其操作的实现过程描述如下:

1)创建一个队列对象

2)若队列非空,则将队首节点出队并访问该结点,再将该结点的非空左、右孩子结点依次入队。

3)重复执行步骤2),知道队列为空为止。

	public void levelTraverse() {
		BiTreeNode t = root;
		if (t != null) {
			LinkQueue l = new LinkQueue();// 构造队列
			l.offer(t);// 根结点入队
			while (!l.isEmpty()) {
				t = (BiTreeNode) l.poll();
				System.out.print(t.data);// 访问结点
				if (t.lchild != null) {// 左孩子非空,入队列
					l.offer(t.lchild);
				}
				if (t.rchild != null) {// 右孩子非空,入队列
					l.offer(t.rchild);
				}
			}
		}

	}


下面来测试下二叉树BiTree类


package debug;

import tree.BiTree;
import tree.BiTreeNode;

public class DebugBiTree {
	public BiTree createBiTree() {
		BiTreeNode d = new BiTreeNode('D');
		BiTreeNode g = new BiTreeNode('G');
		BiTreeNode h = new BiTreeNode('H');
		BiTreeNode e = new BiTreeNode('E', g, null);
		BiTreeNode b = new BiTreeNode('B', d, e);
		BiTreeNode f = new BiTreeNode('F', null, h);
		BiTreeNode c = new BiTreeNode('C', f, null);
		BiTreeNode a = new BiTreeNode('A', b, c);
		return new BiTree(a);// 构造根结点为a的二叉树
	}

	public static void main(String[] args) {
		DebugBiTree debugBiTree = new DebugBiTree();
		BiTree biTree = debugBiTree.createBiTree();
		BiTreeNode root = biTree.root;// 取得树的根结点
		// 测试先根遍历
		System.out.print("(递归)先根遍历序列为:");
		biTree.preRootTraverse(root);
		System.out.println();// 输出换行
		System.out.print("(非递归)先根遍历序列为:");
		biTree.preRootTraverse();
		System.out.println();
		// 测试中根遍历
		System.out.print("(递归)中根遍历序列为:");
		biTree.inRootTraverse(root);
		System.out.println();
		System.out.print("(非递归)中根遍历序列为:");
		biTree.inRootTraverse();
		System.out.println();
		//测试后根遍历
		System.out.print("(递归)后根遍历序列为:");
		biTree.postRootTraverse(root);
		System.out.println();
		System.out.print("(非递归)后根遍历序列为:");
		biTree.postRootTraverse();
		System.out.println();
		//测试层次遍历
		System.out.print("层次遍历序列为:");
		biTree.levelTraverse();
		System.out.println();
	}
}


5.3.2 二叉树遍历算法的应用练习

练习1 二叉树的查找:编写算法完成在二叉树中查找值为x的结点的操作。

    思路:1)若二叉树为空,则不存在这个接单,返回空值;否则,将根结点的值与x进行比较,若相等,则返回该结点。

2)若根结点的值与x不相等,则在左子树中进行查找,若找到,则返回找到的结点。

3)若在左子树中没有找到值为x的结点,则继续在右子树中进行查找,并返回查找结果。

	public BiTreeNode searchNode(BiTreeNode t, Object x) {
		BiTreeNode node;
		if (t != null) {
			if (t.data.equals(x)) {// 对根结点进行判断
				return t;
			} else if ((node = searchNode(t.rchild, x)) != null) {
				return node;
			} else if ((node = searchNode(t.rchild, x)) != null) {
				return node;
			}
		}
		return null;
	}


练习2 计算二叉树中结点的个数

使用先根遍历的递归的方法

	public int countNode(BiTreeNode t) {
		int count = 0;
		if (t != null) {
			count++;// 根结点增加1
			count += countNode(t.lchild);// 加上左子树节点数
			count += countNode(t.rchild);// 加上右子树节点数
		}
		return count;
	}


使用递归模型方法

        | 0                                         (t为空)

f(t) = | 1                                        (t为叶结点)

        | max(f(t.lchild),f(t.rchild))+1 (其他情况)

	public int countNode2(BiTreeNode t) {
		if (t == null) {
			return 0;
		} else {
			return 1 + countNode2(t.lchild) + countNode2(t.rchild);
		}
	}


使用层次遍历的非递归方法

	public int countNode3(BiTreeNode t) {
		int count = 0;
		if (t != null) {
			LinkQueue l = new LinkQueue();
			l.offer(t);
			while (!l.isEmpty()) {
				t = (BiTreeNode) l.poll();
				count++;
				if (t.lchild != null) {
					l.offer(t.lchild);
				}
				if (t.rchild != null) {
					l.offer(t.rchild);
				}
			}
		}
		return count;
	}

练习3 求二叉树的深度

    求二叉树的深度,方法是先求出左子树的深度,再求出右子树的深度,二叉树的深度就是左子树的深度和右子树的深度中的最大值加1。

后根遍历方法

	public int getDepth(BiTreeNode t) {
		if (t != null) {
			int lDepth = getDepth(t.lchild);// 左子树的深度
			int rDepth = getDepth(t.rchild);// 右子树的深度
			return 1 + (lDepth > rDepth ? lDepth : rDepth);// 返回左子树的深度和右子树深度中最大值加1
		}
		return 0;
	}

使用递归模型方法

         | 0                                           (t为空)
f(t) = | 1                                           (t为叶结点)
         | max(f(t.lchild),f(t.rchild))+1  (其他情况)

	public int getDepth2(BiTreeNode t) {
		if (t == null) {
			return 0;
		} else if (t.lchild == null && t.rchild == null) {
			return 1;
		} else {
			int depth = Math.max(getDepth2(t.lchild), getDepth2(t.rchild));
			return 1 + depth;
		}
	}


练习4 判断两棵二叉树是否相等。

    若两棵树相等,则只有两种情况:一种情况是这两棵二叉树都为空;另一种情况则是当两棵二叉树都为非空树,两棵树的根结点、左子树和右子树都分别对应相等。

先根遍历方法:

	public boolean isEqual(BiTreeNode t1, BiTreeNode t2) {
		if (t1 == null && t2 == null) {// 同时为空
			return true;
		}
		if (t1 != null && t2 != null) {// 同时非空进行比较
			if (t1.data.equals(t2.data)) {// 根结点的值是否相等
				if (isEqual(t1.lchild, t2.lchild)) {// 左子树是否相等
					if (isEqual(t1.rchild, t2.rchild)) {// 右子树是否相等
						return true;
					}
				}
			}
		}
		return false;
	}


递归模型方法:

              | true                                                                                                     (t1和t2都为空)
f(t1,t2) = | t1.data==t2.data && f(t1.lchild,t2.lchild) && f(t1.rchild,t2.rchild)        (t1和t2都非空)
              | false                                                                                                   (其他情况)

	public boolean isEqual2(BiTreeNode t1, BiTreeNode t2) {
		if (t1 == null && t2 == null) {
			return true;
		} else if (t1 != null && t2 != null) {
			return (t1.data.equals(t2.data) && (isEqual2(t1.lchild, t2.lchild)) && (isEqual2(
					t1.lchild, t2.lchild)));
		} else {
			return false;
		}
	}


5.3.3 建立二叉树

    先根遍历序列或后根遍历序列能反映双亲与孩子结点之间的层次关系,而中根遍历能反映兄弟结点之间的关系。所以,已知一种二叉树的遍历序列是不能唯一确定一棵二叉树的。只有已知先根和中根遍历序列,或中根和后根遍历序列,才能唯一确定一棵二叉树。而已知先根和后根遍历序列也是无法唯一确定一棵二叉树。

1. 由先根和中根遍历序列建立一棵二叉树

    由于二叉树是由具有层次关系的结点所构成的非线性结构,而且二叉树中的每个结点的两棵子树具有左、右次序之分,所以要建立一棵二叉树,就必须明确:结点与双亲结点和孩子结点之间的层次关系;兄弟结点的左右次序关系。创建步骤:

1)区先根遍历序列的第一个结点作为根结点;

2)在中根遍历序列中寻找根结点,确定根结点在中根遍历序列中的位置,假设为i(0<=i<=count-1),其中,count为二叉树遍历序列中结点的个数。

3)在中根遍历序列中确定:根结点之前的i个结点序列构成左子树的中根遍历序列,根结点之后的count-i-1,剩下的count-i-1个结点序列构成右子树的先根遍历序列。

4)在先根遍历序列中确定:根结点之后的i个结点序列构成左子树的先根遍历序列,剩下的count-i-1个结点构成右子树的先根遍历序列;

5)由步骤3)和4)又确定了左、右子树的先根和中根遍历序列,接下来可以用上述同样的方法来确定左、右子树的根结点,以此递归就可建立唯一的一棵二叉树。

    实现上述建立二叉树的算法,需要引入5个参数:preOrder、inOrder、preIndex、inIndex和count。其中,参数preOrder是整棵树的先根遍历序列;inOrder是整棵树的中根遍历序列;preIndex是先根遍历序列在preOrder中的开始位置;inIndex是中根遍历序列在InOrder中的开始位置;count表示树中结点的个数。

	public BiTree(String preOrder, String inOrder, int preIndex, int inIndex,
			int count) {
		if (count > 0) {
			char r = preOrder.charAt(preIndex);// 取先根遍历序列中的第一个结点作为根结点
			int i = 0;
			for (; i < count; i++) { // 寻找根结点在中根遍历序列中的位置
				if (r == inOrder.charAt(inIndex + i)) {
					break;
				}
			}
			root = new BiTreeNode(r);// 建立树的根结点
			root.lchild = new BiTree(preOrder, inOrder, preIndex + 1, inIndex,
					i).root;
			root.rchild = new BiTree(preOrder, inOrder, preIndex + i + 1,
					inIndex + i + 1, count - i - 1).root;
		}
	}


2.  由标明空子树的先根遍历序列建立一棵二叉树

   已知二叉树的先根遍历序列是不能够唯一确定一棵二叉树,如果能够在先根遍历序列中加入每一个结点的空子树信息,则可明确二叉树中结点与双亲、孩子与兄弟间的关系,因此就可唯一确定一棵二叉树。

    按标明空子树“#”的先根遍历序列来建立一棵二叉树的主要操作步骤:

    从标明空子树信息的先根遍历序列中依次读入字符,若读入的字符是“#”,则建立空子树;否则

1)建立根结点

2)继续建立树的左子树

3)继续建立树的右子树

3. 由完全二叉树的顺序存储结构建立其二叉链式存储结构

    对于一棵顺序存储的完全二叉树,由二叉树的性质5可知,第0个字符是根结点,对于第i个字符,它的双亲是第[(i-1)/2]个字符,若它有左孩子,则它的左孩子是第2i+1个字符;若它有右孩子,则它的右孩子是第2i+2个字符。所以利用性质5,根据完全二叉树的顺序存储结构,可以建立完全二叉树的二叉链式存储结构。

新手福利!大龙虾OpenClaw可视化管理工具来了!告别命令行,这个可视化工具让OpenClaw管理变得超简单 还在用命令行管理OpenClaw?🚀 这个跨平台可视化工具支持14+AI模型,一键切换,还能对接微信飞书钉钉等8大消息渠道,让AI助手管理变得简单直观 ✅ 暗色主题护眼舒适,Rust后端高性能低占用,新手也能轻松上手 🎯 阅读详情

相关推荐

Matlab实战:3种雷达间歇采样干扰的仿真与识别(附完整代码)

本文通过Matlab实战,详细仿真了雷达有源间歇采样干扰的三种核心样式:直接转发、重复转发和循环转发干扰。文章提供了完整的代码实现,并深入探讨了基于时频分析和循环平稳分析的特征提取与识别方法,为雷达抗干扰技术的学习与实践提供了清晰的工程实现路径。

weixin_29053695的博客 364

树的基本概念

树是节点和连接节点的边的集合。节点:树的每一个数据元素称为一个节点,节点是树的基本构成部分。边:边有方向,连接两个节点,表示它们之间的联系。指向该节点的边称为入边,从该节点指向其他节点的边称为出边。根节点:树中唯一没有入边节点。路径:由边连接起来的节点的有序排列。子节点:一个节点是它入边所连接的节点的子节点。父节点:一个节点是它出边所连接的所有节点的父节点。兄弟节点:同一个节点的所有子节点互为兄弟...

数据结构 1422

Windows C语言编程:Beep API实现蜂鸣器原理与实战

如果你在 Windows 上写过 C 语言程序,想给程序加个简单的提示音,第一个想到的可能是 printf("\a") 。这个转义字符确实能在某些终端里发出“嘀”的一声,但它太原始、太不可控了,而且很多时候根本没声音。那么,有没有一种更“Windows”、更可控、更专业的方法来让你的程序“说话”呢? 答案是肯定的,而且它比你想象的要强大和复杂。Windows 提供了一个名为 Beep 的 API 函数,专门用于生成蜂鸣声。你可能觉得这功能太简单,甚至有点过时——不就是让电脑“哔”一声吗?但恰恰是这个看似简单

weixin_34109408的博客 394

树的基本概念(含图论中的严格定义)

树的相关概念介绍,树形结构,树的定义,树的结点,树与森林的区别

weixin_54186646的博客 3710

避坑指南:AUTOSAR FlashDriver操作DFlash模拟EEPROM时,你最容易忽略的5个细节

本文深入探讨了AUTOSAR FlashDriver在DFlash模拟EEPROM操作中最容易被忽视的五大细节问题,包括地址合法性检查、操作时序控制、并发访问冲突、电源稳定性管理和寿命管理。通过实际项目案例和工程化解决方案,帮助开发者避免常见陷阱,提升汽车电子控制单元(ECU)的可靠性和稳定性。

weixin_42741930的博客 246

window下移动设备/硬盘报错位置不可用

移动硬盘指示灯亮,但电脑上不显示 报错信息 : 无法访问E:\。参数错误。 报错原因: 移动硬盘打不开提示参数错误,是因为这个H盘的文件系统内部结构损坏导致的。要怎么恢复里面的数据呢? https://jingyan.baidu.com/article/cb5d6105a80242005c2fe0c3.html 解决办法 : 不用格式化修复方法 方法一: 1、单击–开始–运行–输入cmd; 2、...

qq_40907977的博客 4928

最小生成树之Prim算法

Prim算法介绍: 假设N = (V, {E})是连通网,TE是N上最小生成树中边的集合。算法U={u0}(u0ЄV),TE={}开始,重复执行下述操作:在所有uЄU,vЄV-U的边(u,v)ЄE中找一条代价最小的边(u0,v0)并入集合TE,同时v0并入U,直至U=V为止。此时TE中必有n-1条边,则T=(V,{TE})为N的最小生成树。 Prim算法构造最小生成树的过程如下: 代码如下: i...

zzu-hust-madd 390

Python遥感图像处理指南(2)-卫星影像分析MNDWI提取

在第一篇文章中,我们使用了我们导入的软件包中已定义的函数,如 rasterio 中的 open 和 read 以及 matplotlib 中的 imshow。现在,我们可以编写自己的函数了。函数是一段代码,它接收一些参数作为输入并返回输出(经过一些计算),可以在代码中多次重复使用,也可以被其他代码重用。在 Python 中定义一个新函数非常简单。return v在此语法示例中,arg1和arg2强制参数,应该传递给函数,否则会引发错误,而arg3是一个可选参数,如果是,则假定默认值 1从函数调用中省略。

gis收藏家的博客 1575

第五章:树和二叉树

beibeiyuan123的专栏 653

32GB Jetson Orin SOM 不能刷机问题排查

拿到了一个新的Orin SOM,当用orin Devkit CVB或定制的CVB不能用Linux_For_Tegra刷机,flash日志(JetPack5.0DP和5.0.1DP都失败了) 2. 确认mb2配置中将CVB的eeprom读取大小设置是否为0 ? 已经确认是正常的 在定制的orin cvb flash上使用Orin devkit som,启动是成功的。但新得到的Orin som并不工作可以排除理由:使用Orin Devkit CVB和Orin SOM(不是Devkit SOM)仍然不能

jzwjzw19900922的博客 1499

基于jeecgboot3.9.0的项目管理——项目工作台(二)

基于jeecgboot3.9.0的项目管理——项目工作台(二)

宁波阿成的博客 205

数据结构--树

树1.树的定义2.结点分类3.结点之间的关系3.树的其它相关概念线性表和树的对比 1.树的定义 树(Tree)是n(n≥0)个结点的有限集。n=0时称为空树。在任意一棵非空树中: ( 1)有且仅有一个特定的称为根( Root )的结点; (2)当n>1时,其余结点可分为m(m>0 )个互不相交的有限集T1、T2、… Tm,其中每一一个集合本身又是一棵树,并且称为根的子树( SubTree ),如下图所示。 对于树的定义还需要强调两点: n>0时根结点是唯一的,不可能存在多个根结点,别

刘一儿(嵌入式) 1795

基于STM32的智能宠物喂食系统设计与实现(代码+PCB )

STM32;DHT11;本系统的软件程序基于Keil MDK-ARM V5集成开发环境进行编写和编译。Keil MDK是ARM公司官方推荐的嵌入式开发工具,支持ARM Cortex-M系列微控制器的程序开发,内置ARM C/C++编译器、调试器和仿真器,具有代码编辑、语法高亮、工程管理和在线调试等功能,是STM32开发领域应用最为广泛的IDE之一。

3254968421的博客 72

【数据结构错题集】Chapter5 树与二叉树

1.度&深度

Crumble 277

IDEA的使用指南

IDEA基于java的使用手册,可以最快速的上手,在使用中不断的丰富自己的使用技巧.

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

【王道数据结构】【chapter5树与二叉树】【P159t16】

试设计判断两棵二叉树是否相似的算法。所谓二叉树T1和T2相似,指的是T1和T2都是空的二叉树或都只有一个根节点;或者T1的左子树和T2的左子树是相似的,且T1的右子树和T2的右子树是相似的。

芊樱烛渊的博客 426

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

【王道数据结构】【chapter5树与二叉树】【P158t3】

编写后序遍历二叉树的非递归算法。

芊樱烛渊的博客 1005

数学建模模型总结

这是本人在两年内准备参加数学建模竞赛搜集到的模型资料,已经经过了本人的筛选和整理,希望能够对准备参加数学建模竞赛的学弟学妹们提供一些帮助!

【王道数据结构】【chapter5树与二叉树】【P158t4】

试给出二叉树的自下而上、从右往左的层次遍历算法。

芊樱烛渊的博客 398

【王道数据结构】【chapter5树与二叉树】【P158t5】

假设二叉树采用二叉链表存储结构,设计一个非递归算法求二叉树的高度。

芊樱烛渊的博客 410

【王道数据结构】【chapter5树与二叉树】【P158t6】

二叉树按二叉链表形式存储,试编写一个判别二叉树是否是完全二叉树的算法。

芊樱烛渊的博客 395

【王道数据结构】【chapter5树与二叉树】【P159t15】

设计一个算法将二叉树的叶结点从左到右的顺序连成一个单链表,表头指针为head。二叉树按二叉链表方式存储,链接时用叶结点的右指针来存放单链表指针。

芊樱烛渊的博客 522

【王道数据结构】【chapter5树与二叉树】【P185t5】

以孩子兄弟链表为存储结构,请设计递归算法求树的深度。

芊樱烛渊的博客 434

【王道数据结构】【chapter5树与二叉树】【P159t13】

假设二叉树采用二叉链表存储结构,设计一个算法,求非空二叉树b的宽度(即具有节点数最多的那一层的结点个数)

芊樱烛渊的博客 428

【王道数据结构】【chapter5树与二叉树】【P158t8】

设树B是一颗采用链式结构存储的二叉树,编写一个把树B种所有节点的左、右子树进行交换的函数。

芊樱烛渊的博客 385

【王道数据结构】【chapter5树与二叉树】【P159t12】

设一棵二叉树的结点结构为(LLINK,INFO,RLINK),ROOT为指向该二叉树根结点的指针,p和q分别为指向该二叉树中任意两个节点的指针,试编写算法ANCESTOR(ROOT,p,q,r),找到p和q的最近公共祖先结点r。

芊樱烛渊的博客 445

【王道数据结构】【chapter5树与二叉树】【P158t10】

已知二叉树以二叉链表存储,编写算法完成:对于树种每个元素值为x的结点,删除以它为根的子树,并释放相应的空间。

芊樱烛渊的博客 484

【王道数据结构】【chapter5树与二叉树】【P158t9】

假设二叉树采用二叉链存储结构存储,设计一个算法,求先序遍历序列中第k个结点的值。

芊樱烛渊的博客 366
上一篇: 数据结构--Chapter4(串与数组)
下一篇: 数据结构--Chapter6(图)
Wang_AI
博客等级 码龄12年 988粉丝 162原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值