二叉树是我们常见的数据结构之一,在学习二叉树之前我们需要知道什么是树,什么是二叉树,本篇主要讲述了二叉树,以及二叉树的遍历。

你能get到的知识点?

1、树的介绍

2、二叉树的介绍

3、二叉树遍历的四种方法

4、牛客题目:反转二叉树

一、知识预备

1、树

(Tree)是n(n>=0)个结点的有限集。

数据结构中的树可以看作一个倒立的树,他的‘根’在上面,他的'叶子'在下面。

graph TD
4-->2
4-->7
2-->1
2-->3
7-->6
7-->9

2、树的相关术语介绍

  • 1、树的结点(node):包含一个数据元素及若干指向子树的分支;
  • 2、孩子结点(child node):结点的子树的根称为该结点的孩子,对于结点4来说,结点2和结点7就是结点4的孩子结点;
  • 3、双亲结点:B 结点是A 结点的孩子,则A结点是B 结点的双亲;
  • 4、兄弟结点:同一双亲的孩子结点; 堂兄结点:同一层上结点;
  • 5、祖先结点: 从根到该结点的所经分支上的所有结点
  • 6、子孙结点:以某结点为根的子树中任一结点都称为该结点的子孙
  • 7、结点层:根结点的层定义为1;根的孩子为第二层结点,依此类推;
  • 8、树的深度:树中最大的结点层,该树的深度为三,因为他只有三层。
  • 9、结点的度:结点子树的个数
  • 10、树的度: 树中最大的结点度。
  • 11、叶子结点:也叫终端结点,是度为 0 的结点,例如结点1、2、6、9,都是叶子结点;
  • 12、分枝结点:度不为0的结点;
  • 13、有序树:子树有序的树,如:家族树;
  • 14、无序树:不考虑子树的顺序;

1、二叉树

二叉树(Binary Tree)是每个结点最多有两个子树的树结构。所以二叉树也是一种特殊的树。

通常我们将二叉树的子树被称作“左子树”(left subtree)和“右子树”(right subtree)。

二叉树常被用于实现二叉查找树和二叉堆。



由此可以看出,一棵二叉树,他的每个节点最多只有两个结点,也就是结点的度小于等于二,即取0、1、2。

2、二叉树类型

  1. 满二叉树:除了叶结点外每一个结点都有左右子叶且叶子结点都处在最底层的二叉树。

  2. 完全二叉树:若设二叉树的高度为h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第h层有叶子结点,并且叶子结点都是从左到右依次排布,这就是完全二叉树。

    简单来说:如果二叉树中除去最后一层节点为满二叉树,且最后一层的结点依次从左到右分布,则此二叉树被称为完全二叉树。

  3. 平衡二叉树:平衡二叉树又被称为AVL树(区别于AVL算法),它是一棵二叉排序树,且具有以下性质:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。

二、二叉树实操(我没有说脏话)

1、定义二叉树的结点

定义二叉树每一个节点的结构,他拥有左右子叶,并且本身拥有一个值val,定义一个构造函数,多个结点组合在一起就是一个二叉树。

    /**
* Definition for binary tree
*/
public static class TreeNode {
//定义该结点值
int val;
//定义左结点
TreeNode left;
//定义右结点
TreeNode right; //定义一个构造函数
TreeNode(int x) { val = x; }
}

例图:以下将以该例图进行解说

graph TD
4-->2
4-->7
2-->1
2-->3
7-->6
7-->9

2、遍历二叉树(四种方法)

遍历二叉树主要有四种方法:①:前序遍历 ②:中序遍历 ③:后序遍历 ④:层序遍历

需要事先说明的就是前三种遍历,就是根节点的访问顺序不同,但是访问左右节点的顺序仍然是先访问左结点,再访问右结点。

①:前序遍历

1、访问根节点;

2、访问当前节点的左子树;

3、访问当前节点的右子树;

就是先从根节点出发,先访问根节点,然后访问根结点的左子树,若该左子树的根节点上存在左子树,则访问其左子树,否则,访问其右子树,依次类推。

以上图为例,

  1. 先找到根节点,读取4,
  2. 该结点还有左子树,访问其左子树的根节点,读取2,
  3. 结点2,还有左子树,读取1,
  4. 结点1没有左子树也没有右子树,返回上一层,访问结点2的右子树,读取3,
  5. 这时候应该访问3的左右子树,但是没有,返回上一层,此时结点2的左右子树都已经读取完,返回上一层,读取结点4的右子树,读取7,
  6. 访问结点7的左子树,读取6,
  7. 结点6没有左右子树,返回上一层,访问结点7的右子树,读取9,
  8. 结点9没有左右子树,这时候该二叉树已经遍历完成。

所以访问到的顺序为:4 2 1 3 7 6 9

②:中序遍历

1、访问当前节点的左子树;

2、访问根节点;

3、访问当前节点的右子树;

遍历思想与前序差不多,只不过将读取根节点放在读取左结点之后、右结点之前

③:后序遍历

1、访问当前节点的左子树;

2、访问当前节点的右子树;

3、访问根节点;

遍历思想与前序差不多,只不过将读取根节点放在读取左结点之后、右结点之后

④:层序遍历

按照二叉树的层级结构从左至右依次遍历结点

算法思路:定义一个队列,从树的根结点开始,依次将其左孩子和右孩子入队。而后每次队列中一个结点出队,都将其左孩子和右孩子入队,直到树中所有结点都出队,出队结点的先后顺序就是层次遍历的最终结果。

  1. 根节点4入队,
  2. 根节点4出队,访问结点4的左右结点(2,7),依次入队,
  3. 结点2出队,访问结点2的左右结点(1,3),依次入队,
  4. 结点1出队,无子结点,无需入队,
  5. 结点3出队,无子结点,无需入队,
  6. 结点6出队,无子结点,无需入队,
  7. 结点9出队,无子结点,无需入队,
  8. 队列为空,遍历完成。

最后访问顺序为:4 2 7 1 3 6 9

代码实现:

    /**
* 先序遍历(递归)
* @param node
*/
public void previous(TreeNode node) {
if (node == null) {
return;
}
System.out.print(node.val+"\t");
this.previous(node.left);
this.previous(node.right);
}
/**
* 中序遍历(递归)
* @param node
*/
public void middle(TreeNode node) {
if (node == null) {
return;
}
this.middle(node.left);
System.out.print(node.val+"\t");
this.middle(node.right);
}
/**
* 后序遍历(递归)
* @param node
*/
public void next(TreeNode node) {
if (node == null) {
return;
}
this.next(node.left);
this.next(node.right);
System.out.print(node.val+"\t");
} /**
* 遍历二叉树
* 层序遍历(非递归)
* @param node
*/
public void bfs(TreeNode node){
if (node == null) {
return;
}
Queue<TreeNode> queue = new LinkedList<>();
queue.add(node);
while (!queue.isEmpty()){
TreeNode current = queue.poll();
System.out.print(current.val + "\t");
//如果当前节点的左节点不为空入队
if(current.left != null)
{
queue.offer(current.left);
}
//如果当前节点的右节点不为空,把右节点入队
if(current.right != null)
{
queue.offer(current.right);
}
}
}
遍历结果:
1、前序遍历:4 2 1 3 7 6 9
2、中序遍历:1 2 3 4 6 7 9
3、后序遍历:1 3 2 6 9 7 4
4、层序遍历:4 2 7 1 3 6 9

在这里附上前三种方法的非递归方法,感兴趣的小伙伴可以研究研究。

附:非递归方法

主要实现是依靠来实现

    /**
* 先序遍历非递归
* @param node
*/
public void previous1(TreeNode node) {
if (node == null) {
return;
}
Stack<TreeNode> queue = new Stack<>();
queue.add(node);
while (!queue.isEmpty()) {
TreeNode current = queue.pop();
while(current!=null) {
System.out.print(current.val + "\t");
if (current.right!=null){
queue.push(current.right);
}
current = current.left;
}
}
} /**
* 中序遍历(非递归)
* @param node
*/
public void middle1(TreeNode node) {
Stack<TreeNode> stack = new Stack<>();
while (!stack.isEmpty() || node !=null) {
while (node != null){
stack.push(node);
node = node.left;
}
node = stack.pop();
System.out.print(node.val + "\t");
node = node.right;
}
} /**
* 后序遍历(非递归)
* @param node
*/
public void next1(TreeNode node) {
Stack<TreeNode> stack = new Stack<>();
Stack<Integer> stack1 = new Stack<>();
while (!stack.isEmpty() || node !=null) {
while (node != null){
stack.push(node);
stack1.push(0);
node = node.left;
}
while (!stack.isEmpty() && stack1.peek() == 1) {
stack1.pop();
System.out.print(stack.pop().val + "\t");
}
if (!stack.isEmpty()) {
stack1.pop();
stack1.push(1);
node = stack.peek();
node = node.right;
}
}
}

三、小试牛刀

leetcode题目:反转二叉树

原来的二叉树:

graph TD
4-->2
4-->7
2-->1
2-->3
7-->6
7-->9

经过算法,需要转换为:

graph TD
4-->7
4-->2
2-->3
2-->1
7-->9
7-->6

解法:

二叉树的遍历有四种方法,那么,该题解法也至少有四种,如果读懂了上面的遍历算法,那么这道题简直轻而易举。

主要思路:就是遍历某一结点时,也就是在原来输出该节点的操作换成将其结点的左右结点交换位置。

/**
* 反转二叉树
* 前序反转
* @param node
*/
public void invertTree_previous(TreeNode node){
if (node == null){
return;
}
TreeNode node1 = node.left;
node.left = node.right;
node.right = node1;
this.invertTree_previous(node.left);
this.invertTree_previous(node.right);
} /**
* 反转二叉树
* 中序反转
* @param node
*/
public void invertTree_middle(TreeNode node){
if (node == null){
return;
}
this.invertTree_middle(node.left);
TreeNode node1 = node.left;
node.left = node.right;
node.right = node1;
this.invertTree_middle(node.left);
} /**
* 反转二叉树
* 后序序反转
* @param node
*/
public void invertTree_next(TreeNode node){
if (node == null){
return;
}
this.invertTree_next(node.left);
this.invertTree_next(node.right);
TreeNode node1 = node.left;
node.left = node.right;
node.right = node1;
} /**
* 反转二叉树
* 层序反转
* @param node
*/
public void invertTree_bfs(TreeNode node){
if (node == null) {
return;
}
Queue<TreeNode> queue = new LinkedList<>();
queue.add(node);
while (!queue.isEmpty()){
TreeNode current = queue.poll();
TreeNode node1 = current.left;
current.left = current.right;
current.right = node1;
//如果当前节点的左节点不为空入队
if(current.left != null)
{
queue.offer(current.left);
}
//如果当前节点的右节点不为空,把右节点入队
if(current.right != null)
{
queue.offer(current.right);
}
}
}

答案:

1、转换前
前序遍历:4 2 1 3 7 6 9
中序遍历:1 2 3 4 6 7 9
后序遍历:1 3 2 6 9 7 4
层次遍历:4 2 7 1 3 6 9 2、转换后
前序遍历:4 7 9 6 2 3 1
中序遍历:9 7 6 4 3 2 1
后序遍历:9 6 7 3 1 2 4
层次遍历:4 7 2 9 6 3 1

源码获取:关注公众号:博奥思园,回复:数据结构二叉树

你的支持是我前进的最大动力

参考:

1、 Java数据结构和算法(十)——二叉树

2、二叉树的四种遍历算法

3、Java实现二叉树的前序、中序、后序、层序遍历(非递归方法)

【java 数据结构】还不会二叉树?一篇搞定二叉树的更多相关文章

  1. Java原来还可以这么学:如何搞定面试中必考的集合类

    原创声明 本文作者:黄小斜 转载请务必在文章开头注明出处和作者. 系列文章介绍 本文是<五分钟学Java>系列文章的一篇 本系列文章主要围绕Java程序员必须掌握的核心技能,结合我个人三年 ...

  2. 一篇搞定RSA加密与SHA签名|与Java完全同步

    基础知识 什么是RSA?答:RSA是一种非对称加密算法,常用来对传输数据进行加密,配合上数字摘要算法,也可以进行文字签名. RSA加密中padding?答:padding即填充方式,由于RSA加密算法 ...

  3. 2021升级版微服务教程6—Ribbon使用+原理+整合Nacos权重+实战优化 一篇搞定

    2021升级版SpringCloud教程从入门到实战精通「H版&alibaba&链路追踪&日志&事务&锁」 教程全目录「含视频」:https://gitee.c ...

  4. java数据结构和算法编程作业系列篇-数组

    /** * 编程作业 2.1 向highArray.java程序(清单2.3)的HighArray类添加一个名为getMax()的方法,它返回 数组中最大关键字的值,当数组为空时返回-1.向main( ...

  5. 一篇搞定Java集合类原理

    Java集合类实现原理 1.Iterable接口 定义了迭代集合的迭代方法 iterator() forEach() 对1.8的Lambda表达式提供了支持 2. Collection接口 定义了集合 ...

  6. 一篇搞定Java过滤器

    Filter:过滤器 引言 我们可以通过使用前面的技术,做出一些简单的登陆注册以及配合数据库实现对数据增删改查的Demo,程序是基本运行起来了,但是却存在着一个重大的安全问题,那就登陆权限验证,一般来 ...

  7. 一篇搞定微信分享和line分享

    前言 在h5的页面开发中,分享是不可或缺的一部分,对于一些传播性比较强的页面,活动页之类的,分享功能极为重要.例如,京东等电商年末时会有一系列的总结h5在微信中传播,就不得不提到微信的分享机制. 微信 ...

  8. 一篇搞定MongoDB

    MongoDB最基础的东西,我这边就不多说了,这提供罗兄三篇给大家热身 MongoDB初始 MongoDB逻辑与物理存储结构 MongoDB的基础操作 最后对上述内容和关系型数据做个对比 非关系型数据 ...

  9. 3年java开发面试BAT,你必须彻底搞定Maven!

    前言 现在的Java项目中,Maven随处可见. Maven的仓库管理.依赖管理.继承和聚合等特性为项目的构建提供了一整套完善的解决方案,如果你搞不懂Maven,那么一个多模块的项目足以让你头疼,依赖 ...

随机推荐

  1. NLP(二十七)开放领域的三元组抽取的一次尝试

      当我写下这篇文章的时候,我的内心是激动的,这是因为,自从去年6月份写了文章利用关系抽取构建知识图谱的一次尝试 后,我就一直在试图寻找一种在开放领域能够进行三元组抽取的办法,也有很多读者问过我这方面 ...

  2. 我成功攻击了Tomcat服务器之后

    Tomcat是一个开源的轻量级Web应用服务器,在我们平常工作过程中接触得非常多.代码也非常经典,很多人为了提升自己的技术也会去阅读学习Tomcat的源码.但正如著名诗人李白所说的:世界上本没有漏洞, ...

  3. WEB应用之httpd基础入门(一)

    前文我们聊了下http协议web服务的一些常识和httpd服务器软件三种响应模型的简单介绍,回顾请参考https://www.cnblogs.com/qiuhom-1874/p/12515075.ht ...

  4. vue one

    目录 复习 Vue框架 Vue的优点 Vue的使用 vue完成简单的事件 vue操作简单样式 小结 指令 文本指令 事件指令 属性指令 条件指令 复习 """ 1.BBS ...

  5. Python第四章-流程控制

    流程控制 在以前的代码中,所有的代码都是交由 Python 忠实地从头执行到结束.但是这些远远不够.很多时候需要根据不同的情况执行不同的代码. 如果你想改变这一工作流程,应该怎么做? 就像这样的情况: ...

  6. TensorFlow系列专题(十四): 手把手带你搭建卷积神经网络实现冰山图像分类

    目录: 冰山图片识别背景 数据介绍 数据预处理 模型搭建 结果分析 总结 一.冰山图片识别背景 这里我们要解决的任务是来自于Kaggle上的一道赛题(https://www.kaggle.com/c/ ...

  7. Jmeter4.0接口测试之案例实战(七)

    在前面的知识体系中介绍了Jmeter的基本应用,下来通过具体的案例来看Jmeter在接口测试中的具体案例实战部分. HTTP是基于应用层的协议,底层的网络传输层它不需要去关心,同时它是一个无状态的协议 ...

  8. Sublimeの虚拟环境(Venv)设置

    这里主要介绍,在使用 Python 虚拟环境(Venv)时,SublimeText 该怎么设置 为什么使用虚拟环境(Venv) 因为,我有洁癖! 我就是喜欢看到,pip list 命令下什么 Pack ...

  9. Python python lamda 表达式

    '''关键字lambda表示匿名函数,冒号前面的x表示函数参数. 匿名函数有个限制,就是只能有一个表达式,不用写return,返回值就是该表达式的结果. 用匿名函数有个好处,因为函数没有名字,不必担心 ...

  10. [bzoj2547]玩具兵<Spfa+二分+匈牙利算法>

    题目链接:http://www.lydsy.com/JudgeOnline/problem.php?id=2547 挺有意思的一道题,这道题可以划分成几个小题....... 题目大意: 三个兵种在一个 ...