数据结构--二叉树详解

一,概念

1,结点的度:一个结点含有子树的个数称为该结点的度

2, 树的度:一棵树中,所有结点度的最大值称为树的度;

3,叶子结点或终端结点:度为0的结点称为叶结点; 

4,双亲结点或父结点:若一个结点含有子结点,则这个结点称为其子结点的父结点,

5,孩子结点或子结点:一个结点含有的子树的根结点称为该结点的子结点;

6,根结点:一棵树中,没有双亲结点的结点;

7,树的高度或深度:树中结点的最大层次;

二,二叉树的分类

1,满二叉树

每层的结点数都达到最大值,则这棵二叉树就是满二叉树。满二叉树是一种特殊的完全二叉树

2,完全二叉树

从上到下,从左到右一次排列

三,二叉树的基本性

1,若规定根节点的层数为1,则一颗非空二叉树的第i层上最多有2^i-1个节点

2, 如规定根结点的二叉树的深度为为1,则深度为k的二叉树的最大节点数是2^-1

3,对任意一颗二叉树,如果其叶节点的个数为n0,度为2的非叶节点个数为n2,则有n0=n2+1

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

5,对于具有n个节点的完全二叉树,如果按照从上至下从左至右所有节点从0开始编号,如果父节点的下标为i,则孩子节点为2i+1,2i+2。但是如果2i+1>=n,左无左孩子,若2i+2>=n,否则没有右孩子

6,知道孩子的下标,父节点的下标为(i-1)/2,如果i=0,则无则无双亲节点 

7, 二叉树存储分为顺序存储,和链式储存,链式储存是通过一个一个节点的引用起来的。

以链式储存为例:

例如:

public class BinaryTree {public TreeNode root;static class TreeNode{public char val;public TreeNode left;public TreeNode right;public TreeNode(char val) {this.val = val;}}
}

四,二叉树的遍历

分类:

二叉树的遍历分为大体四种:前序遍历,中序遍历,后序遍历,层序遍历。前三种遍历的方式又可以写为递归的形式和非递归的形式

前序遍历:

二叉树中的每一棵树都要符合先遍历根,然后遍历左树,最后是右树(根左右)

递归:

如果根为空,直接return。先打印根,然后打印左树,最后是右树

public void preOrder(TreeNode root){if (root==null){return;}System.out.print(root.val+" ");preOrder(root.left);preOrder(root.right);
}
非递归:
法一:

借助栈。先将根放到栈中,然后弹出,记录下来(赋值给cur),并打印。然后先将cur的右根放到栈中,再放cur的左根。在再弹出栈顶元素也就是左根,重复上述步骤(记录下来,并打印,将其右左树再放到栈中)

注意:

1,一定是先放右树,再放左树

2,将左右树放到栈中时要分别判断左右树是否为空,如果为空则不进栈

public void preOrder2(TreeNode root){Stack<TreeNode> stack=new Stack<>();stack.push(root);TreeNode cur=stack.pop();stack.push(cur.right);stack.push(cur.left);System.out.print(cur.val+" ");while (!stack.isEmpty()){cur=stack.pop();if (cur.right!=null){stack.push(cur.right);}if (cur.left!=null) {stack.push(cur.left);}System.out.print(cur.val+" ");}
}
法二:

借助栈。将root赋值给cur,进入两次循环,内循环是找到cur最左边的树并把过程中经过的每一个节点放到栈中,直到找到null。因为这里是前序遍历,所以每找到一个节点就要打印出来。当找到null时,走出循环。这时弹出栈顶元素,cur等于栈顶元素的右树(通过前面步骤,已知栈顶元素的左树为空)。然后cur开始外循环。由于内循环的条件是cur!=null,所以当右树为空时,不进入内循环,直接再次弹出栈顶元素。如果不为空是,则进入内循环,寻找它的左树……

public void preOrder3(TreeNode root){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 old=stack.pop();cur=old.right;}System.out.println();
}

中序遍历:

二叉树中的每一棵树都要符合先遍历左树,然后遍历根,最后是右树(左根右)

递归:
public void midOrder(TreeNode root){if (root==null){return;}midOrder(root.left);System.out.print(root.val+" ");midOrder(root.right);
非递归:

与前序遍历非递归的法二原理相似,只是根打印的位置不相同,所以在走内循环时不打印节点,走完后,在打印,这样保证先打印的是左树。

public void midOrder2(TreeNode root){Stack<TreeNode>stack=new Stack<>();TreeNode cur=root;while (cur!=null||!stack.isEmpty()){while (cur!=null){stack.push(cur);cur=cur.left;}TreeNode old=stack.pop();System.out.print(old.val+" ");cur=old.right;}System.out.println();
}

后序遍历:

二叉树中的每一棵树都要符合先遍历左树,然后遍历右树,最后是根(左右根)

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

与中序遍历非递归思路相似,只是根打印的位置不相同,所以在走内循环,及走完内循环后均不打印,完成内循环后,直接判断栈顶元素右树是否为空,如果为空,就可以弹出栈顶元素,并且打印。但是如果不为空,就要先走右树(因为后序遍历的顺序是左右根,已知没有左树,所以要先打印右树)。

注意:要把每次遍历完的节点储存一下。因为每次内循环走完左树为空时,到判断栈顶元素A右树时(在右树不为空的情况下),这时会开始遍历右树,当遍历完右树后,又会回到这个起点(判断该栈顶元素A是否有右树),这时就会进入死循环,所以这里的判断条件进行丰富,即栈顶元素既有右树且之前没有遍历过【注意栈顶元素A,只是举了一个例子,方便理解!】

public void postOrder2(TreeNode root){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 old=stack.peek();if (old.right==null||prev==old.right){stack.pop();System.out.print(old.val+" ");prev=old;}else {cur=old.right;}}System.out.println();
}

层序遍历:

一层一层的进行遍历

这里我们用到了队列,先把根放进去,弹出时,记录下来(赋值到ret中)并打印,然后根据ret,将ret的左树和右树也放到队列里面,重复上述步骤(弹出,记录下来,并打印,将其左右树再放到队列中),循环上述步骤,直到队列为空,则遍历完成。需要注意的是:将左右树放到队列中时要分别判断左右树是否为空,如果为空则不进队列,只有不为空时,才能放入。

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

这种方法是将每一层的节点放到一个链表中,然后将每一层的的链表放到一个“大的链表”中。先将根放到队列中,计算这一层的大小size,则决定着这一层的的链表的大小。然后循环size次,从而将这一层的每个元素均放到该层链表中。然后将这一层的每个元素的左右树再放到队列中,重复上述步骤,直到链表为空。

public List<List<Character>> levelOrder2(TreeNode root){List<List<Character>> ret=new LinkedList<>();if (root==null){return ret;}Queue<TreeNode> queue=new LinkedList<>();queue.offer(root);while (!queue.isEmpty()){int size= queue.size();List<Character> list=new LinkedList<>();while (size>0){TreeNode node=queue.peek();if (node.left!=null){queue.offer(node.left);}if (node.right!=null) {queue.offer(node.right);}list.add(queue.poll().val);size--;}ret.add(list);}return ret;}

五,求二叉树的简单性质

1,一棵树的节点个数

法一:

我们遍历二叉树时,遍历了每个节点,所以只需要将遍历中打印的步骤改为count++,就可以得到节点的个数

public static  int sizeNode;
public void size2(TreeNode root) {if (root==null){return ;}sizeNode++;size2(root.left);size2(root.right);
}

法二:

一棵树的节点个数=这棵树的左子树的节点个数+右子树的节点个数+1.(这个1是根节点)

public int size(TreeNode root){if (root==null){return 0;}return 1+size(root.left)+size(root.right);
}

2,求叶子节点的个数

法一:

叶子结点的性质是左子树和右子树均为null,所以当遇到这样的节点时,返回1。整颗树的叶子结点个数=左子树叶子节点的个数+右子树叶子结点个数

public int getLeafNodeCount(TreeNode root){if (root==null){return 0;}if (root.left==null&&root.right==null){return 1;}return getLeafNodeCount(root.left)+getLeafNodeCount(root.right);
}

法二:

也可以遍历二叉树,找到左子树和右子树均为null的节点,count++.

public static  int sizeLeafNode;
public void getLeafNodeCount2(TreeNode root){if (root==null){return ;}if (root.left==null&&root.right==null){sizeLeafNode++;}getLeafNodeCount2(root.left);getLeafNodeCount2(root.right);}

3,获取k层节点的个数

我们每递归一层时,让参数k-1,这样当k==1时,就是k层的节点,我们只需要返回1,整颗树的k层结点个数=左子树的k层节点的个数+右子树的k层结点个数

public int getKLevelNodeCount(TreeNode root,int k){if (root==null ){return 0;}if (k==1){return 1;}return getKLevelNodeCount(root.left,k-1)+getKLevelNodeCount(root.right,k-1);
}

4,树的高度

树的高度=左子树高度和右子树高度的最大值+1

public int getHeight(TreeNode root){if (root==null){return 0;}int leftHeight=getHeight(root.left);int rightHeight=getHeight(root.right);return Math.max(leftHeight,rightHeight)+1;
}

5,找到某个节点

如果找到该节点,返回该节点的根。左子树递归完后,如果找到了,直接返回,如果没有找到,再右子树递归,这样可以提高效率

public TreeNode find(TreeNode root,char val){if (root==null){return null;}if (root.val==val){return root;}TreeNode ret=find(root.left,val);if (ret!=null){return ret;}ret=find(root.right,val);if (ret!=null){return ret;}else {return null;}
}

六,简单应用

1,检查两棵树是否相同

如果两棵树均为空,则相同。因为我们需要用递归来实现,所以写的时候我们用if语句(两棵树一定不相同的条件)来快速排除

排除条件

(1)如果一棵树为空,一棵树不为空,直接返回false

(2)如果对应节点的值不一样,直接返回false

当两棵树对应的左树与右树均相同,则两棵树相同

public boolean isSameTree(TreeNode p,TreeNode q){if (p==null&&q==null){return true;}if (p==null&&q!=null||p!=null&&q==null){return false;}if (p.val!= q.val){return false;}return isSameTree(p.left,q.left)&&isSameTree(p.right,q.right);}

2,第二棵树是否是第一棵树的子树

我们先找到第一棵树是否有节点与第二棵树的根结点一致,如果有相同的节点,调用方法一,看两棵树是否相同。我们分别从左树和右树中寻找,只要有一边找到了,就说明第二棵树是第一棵树的子树

public boolean isSubTree(TreeNode root,TreeNode subRoot){if (root==null&&subRoot!=null){return false;}if (root.val== subRoot.val){return isSameTree(root,subRoot);}return isSubTree(root.left,subRoot)||isSubTree(root.right,subRoot);
}

3,翻转二叉树

当根为空,或者根的左右树均为空,则直接返回根。如果不是,交换左右树

private void swap(TreeNode root){TreeNode tmp=root.left;root.left=root.right;root.right=tmp;
}
public TreeNode reverseTree(TreeNode root){if (root==null||root.left==null&&root.right==null){return root;}swap(root);reverseTree(root.left);reverseTree(root.right);return root;
}

4,判断一棵二叉树是否是平衡二叉树

即:所有节点的高度差小于等于一

法一:

算左右树的高度,如果差的绝对值小于1,且左树和右树每一棵子树的左右树的高度差的绝对值小于1,则是平衡二叉树

public boolean isBalanced(TreeNode root){if (root==null){return true;}int leftHeight=getHeight(root.left);int rightHeight=getHeight(root.right);return Math.abs(leftHeight-rightHeight)<=1&&isBalanced(root.left)&&isBalanced(root.right);
}

法二(优化):

重写计算书的高度的方法,如果树的左右树的高度差小于1,则返回该树的高度,如果高度差大于1,返回-1,最后看树的高度是否大于0,如果大于0,则说明每一棵树的左右子树的高度差均小于1,如果小于0,则说明有树的左右子树的高度差均大于1,则不是平衡二叉树

public int getHeight2(TreeNode root){if (root==null){return 0;}int left=getHeight2(root.left);if (left<0){return -1;}int right=getHeight2(root.right);if (right<0){return -1;}if (Math.abs(left-right)<=1){return Math.max(left,right)+1;}else {return -1;}
}
public boolean isBalanced2(TreeNode root){if (root==null){return true;}return getHeight2(root)>0;}

5,对称二叉树

如果根为空或者根的左树右树均为空,则是对称二叉树。

(1)如果跟的左树,右树一个为空一个不为空,则不是对称二叉树

(2)如果根的左树和右树的值不一样,则不是对称二叉树

然后判断左右,这两棵树是否镜面对称,我们再写一个子方法。

(1)如果这两棵树的左右树均为空,则是对称二叉树

(2)如果一棵树的左树,与一棵树的右树,一颗为空,一颗不为空,则不是对称二叉树

(3)如果一棵树的右树,与一棵树的左树,一颗为空,一颗不为空,则不是对称二叉树

(4)如果一棵树的左树,与另一棵树的右树的值不相同,或者一棵树的右树,与一棵树的左树的值不相同,则不是对称二叉树

private boolean isSymmetricChild(TreeNode p,TreeNode q){if (p.left==null&&p.right==null&&q.left==null&&q.right==null){return true;}if (p.left!=null&&q.right==null||p.left==null&&q.right!=null||p.right!=null&&q.left==null||p.right==null&&q.left!=null){return false;}if (p.left.val!=q.right.val||p.right.val!=q.left.val){return false;}return isSymmetricChild(p.left,q.right)&&isSymmetricChild(p.right,q.left);
}public boolean isSymmetric(TreeNode root){if (root==null||root.left==null&&root.right==null){return true;}if (root.left==null&&root.right!=null||root.left!=null&&root.right==null){return false;}if (root.left.val!=root.right.val){return false;}return isSymmetricChild(root.left,root.right);
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/49270.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

go语言Gin框架的学习路线(十一)

目录 GORM的CRUD教程 更新操作 更新所有字段 更新指定字段 使用 Select 和 Omit 更新 无 Hooks 更新 批量更新 删除操作 删除记录 批量删除 软删除 物理删除 示例代码 GORM的CRUD教程 CRUD 是 "Create, Read, Update, Delete"&#xff08;创建、查询、…

新手小白的pytorch学习第八弹------分类问题模型和简单预测

目录 1 启动损失函数和优化器2 训练模型创建训练和测试循环 3 预测和评估模型 这篇是接着新手小白的pytorch学习第七弹------分类问题模型这一篇的&#xff0c;代码也是哟~ 1 启动损失函数和优化器 对于我们的二分类问题&#xff0c;我们经常使用 binary cross entropy 作为损…

机器视觉系列之【硬件知识】-工业相机(四)

目录 几个高频面试题目 工业彩色相机如何调节白平衡解决偏色问题 算法原理 多光谱成像技术和相机选型 多光谱相机技术 选择多光谱成像相机技术时的主要考虑因素 智慧工厂机器视觉感知与控制 1 智慧工厂与机器视觉检测控制技术 2 智慧工厂机器视觉感知与控制 基于机器视…

详解yolov5和yolov8以及目标检测相关面试

一、与yoloV4相比&#xff0c;yoloV5的改进 输入端&#xff1a;在模型训练阶段&#xff0c;使用了Mosaic数据增强、自适应锚框计算、自适应图片缩放基准网络&#xff1a;使用了FOCUS结构和CSP结构Neck网络&#xff1a;在Backbone和最后的Head输出层之间插入FPN_PAN结构Head输出…

[NOIP2009 提高组] 最优贸易(含代码题解)

[NOIP2009 提高组] 最优贸易 题目描述 C C C 国有 n n n 个大城市和 m m m 条道路&#xff0c;每条道路连接这 n n n 个城市中的某两个城市。任意两个城市之间最多只有一条道路直接相连。这 m m m 条道路中有一部分为单向通行的道路&#xff0c;一部分为双向通行的道路&am…

NLP-使用Word2vec实现文本分类

Word2Vec模型通过学习大量文本数据&#xff0c;将每个单词表示为一个连续的向量&#xff0c;这些向量可以捕捉单词之间的语义和句法关系。本文做文本分类是结合Word2Vec文本内容text&#xff0c;预测其文本标签label。以下使用mock商品数据的代码实现过程过下&#xff1a; 1、…

JMeter的使用方法

软件安装&#xff1a; 参考链接&#xff1a;JMeter 下载安装及环境配置&#xff08;包含jdk1.8安装及配置&#xff09;_jmeter5.2.1需要什么版本的jdk-CSDN博客 前置知识储备&#xff1a; JMeter的第一个案例 增加线程数 线程&#xff08;thread&#xff09;是操作系统能够进…

ROS2入门到精通—— 2-8 ROS2实战:机器人安全通过狭窄区域的方案

0 前言 室内机器人需要具备适应性和灵活性&#xff0c;以便在狭窄的空间中进行安全、高效的导航。本文提供一些让机器人在狭窄区域安全通过的思路&#xff0c;希望帮助读者根据实际开发适当调整和扩展 1 Voronoi图 Voronoi图&#xff1a;根据给定的一组“种子点”&#xff0…

【数据挖掘】词云分析

目录 1. 词云分析 2. Python 中的 WordCloud 库 1. 词云分析 词云&#xff08;Word Cloud&#xff09;是数据可视化的一种形式&#xff0c;主要用于展示文本数据中单词的频率和重要性。它具有以下几种主要用途和意义&#xff1a; 1. 文本分析 • 识别关键主题&#xff1a;通…

AI学习记录 - 图像识别的基础入门

代码实现&#xff0c;图像识别入门其实非常简单&#xff0c;这里使用的是js&#xff0c;其实就是把二维数组进行公式化处理&#xff0c;处理方式如上图&#xff0c;不同的公式代表的不同的意义&#xff0c;这些意义网上其实非常多&#xff0c;这里就不细讲了。 const getSpecif…

JavaScript构造函数小挑战

// 编码挑战 #1 /* 使用构造函数实现一辆汽车。一辆汽车有一个品牌和一个速度属性。speed 属性是汽车当前的速度&#xff0c;单位为 km/h&#xff1b; a. 执行一个 “accelerate ”方法&#xff0c;将汽车的速度提高 10&#xff0c;并将新速度记录到控制台&#xff1b; 3. a.…

VSCode python autopep8 格式化 长度设置

ctrl, 打开设置 > 搜索autopep8 > 找到Autopep8:Args > 添加项--max-line-length150

等保测评练习卷17

等级保护初级测评师试题17 姓名: 成绩: 判断题(101=10分)1. 关于安全区域边界的安全审计,三级系统的要求包括应对审计进程进行保护,防止未经授权的中断。( F ) 是安全计算环境的安全审计 2.…

秋招突击——7/22——复习{堆——前K个高频元素}——新作{回溯——单次搜索、分割回文串。链表——环形链表II,合并两个有序链表}

文章目录 引言复习堆堆——前K个高频元素个人实现复习实现二参考实现 新作单词搜索个人实现参考实现 分割回文串个人实现参考实现 环形链表II个人实现参考实现 两个有序链表个人实现 总结 引言 又是充满挑战性的一天&#xff0c;继续完成我们的任务吧&#xff01;继续往下刷&a…

WebRTC QoS方法十三.2(Jitter延时的计算)

一、背景介绍 一些报文在网络传输中&#xff0c;会存在丢包重传和延时的情况。渲染时需要进行适当缓存&#xff0c;等待丢失被重传的报文或者正在路上传输的报文。 jitter延时计算是确认需要缓存的时间 另外&#xff0c;在检测到帧有重传情况时&#xff0c;也可适当在渲染时…

【目标检测实验系列】EMA高效注意力机制,融合多尺度特征,助力YOLOv5检测模型涨点(文内附源码)

1. 文章主要内容 本篇博客主要涉及多尺度高效注意力机制&#xff0c;融合到YOLOv5s模型中&#xff0c;增加模型提取多尺度特征的能力&#xff0c;助力模型涨点。&#xff08;通读本篇博客需要7分钟左右的时间&#xff09;。 2. 简要概括 论文地址&#xff1a;EMA论文地址 如下…

Blender材质-PBR与纹理材质

1.PBR PBR:Physically Based Rendering 基于物理的渲染 BRDF:Bidirection Reflectance Distribution Function 双向散射分散函数 材质着色操作如下图&#xff1a; 2.纹理材质 左上角&#xff1a;编辑器类型中选择&#xff0c;着色器编辑器 新建着色器 -> 新建纹理 -> 新…

音视频入门基础:H.264专题(17)——FFmpeg源码获取H.264裸流文件信息(视频压缩编码格式、色彩格式、视频分辨率、帧率)的总流程

音视频入门基础&#xff1a;H.264专题系列文章&#xff1a; 音视频入门基础&#xff1a;H.264专题&#xff08;1&#xff09;——H.264官方文档下载 音视频入门基础&#xff1a;H.264专题&#xff08;2&#xff09;——使用FFmpeg命令生成H.264裸流文件 音视频入门基础&…

【开源库编译 | zlib】 zlib库最新版本(zlib-1.3.1)在Ubuntu(Linux)系统下的 编译 、交叉编译(移植)

&#x1f601;博客主页&#x1f601;&#xff1a;&#x1f680;https://blog.csdn.net/wkd_007&#x1f680; &#x1f911;博客内容&#x1f911;&#xff1a;&#x1f36d;嵌入式开发、Linux、C语言、C、数据结构、音视频&#x1f36d; &#x1f923;本文内容&#x1f923;&a…

《书生大模型实战营第3期》入门岛 学习笔记与作业:Git 基础知识

文章大纲 Git 是什么&#xff1f;-- 分布式版本控制系统版本控制系统简介Git 基本概念1. 安装 Git1.1 Windows 系统1.2 Linux 系统 2. Git 托管平台3. 常用 Git 操作4. tips4.1 全局设置 vs. 本地设置4.2 如何配置4.3 验证设置4.4 Git 四步曲 5. 常用插件6. 常规开发流程 作业其…