数据结构与算法(二叉树)~ 介绍二叉树以及力扣上几道二叉树题目的方法和套路

简介: 数据结构与算法(二叉树)~ 介绍二叉树以及力扣上几道二叉树题目的方法和套路

数据结构与算法(二叉树)~ 介绍二叉树以及力扣上几道二叉树题目的方法和套路


☺ 需要明白的点是:在方法 执行的 遍历过程中

❀ 根(或父节点):是一个结点,

✿ 左子树(是一个区间,左区间,只是当它只有一个结点时才变成一个左结点)

✿ 右子树(也是一个区间,右区间,只是当它只有一个结点时才变成一个右结点)

 

1,二叉树的数据结构:

(1)基本实现(组成):由一个一个根(或父结点)和左结点、右结点构成。

自己动手实现:定义了一个含有 数据域 左结点指针右结点指针 + (父结点【自己设计就加上,这样方便后续的一些操作】) 的 结点类Node。

(2)二叉树【需要先定义为一颗什么类型的二叉树(这样才能按照一定规则进行结点位置的添加),这里咱设计一棵 二叉搜索树主要的功能(增删改查):定义一些接口方法


32.png


■ 主要的功能(增删改查 + 各种方式的遍历 【前序遍历、中序遍历、后序遍历、层序遍历】+ 前驱结点、后驱结点(针对中序遍历的)+ 树的深度 + 是否为完全二叉树 ):

● 增加:制定一定比较规则(具体实现:【 二叉搜索树中传入一个比较器类对象(属性对象,可以不写,java内置有一个compator 比较器接口 ~ 组合关系)】 /

【让二叉搜索树 继承 比较类(java内置了comparable 接口) ~ 继承关系/   将对象进行强制类型转化,然后就可以使用相应的接口方法啦,例如以下:~强制类型转化】)


33.png

33.png

 

● 删除:先查找到给结点,然后删除结点:删除过程~~~~~

● 查找:查找结点。

序遍历: 根(父)      左子树        右子树     |     根(父)      右子树         左子树     【前序只需满足 根最先访问】

序遍历: 左子树         根(父)      右子树      |      右子树         根(父)       左子树   【中序只需满足 根中间访问】

序遍历:  左子树          右子树         根(父) |      右子树        左子树            根(父) 【后序只需满足 根最后访问】

序遍历: 一层一层的结点从左到右进行访问。

● 前驱结点:中序遍历的前一个结点。

● 后驱结点:后序遍历的后一个结点。

 

(3)过程中进行重构二叉搜索树,将 增删改查 或者一些通用的接口或者属性封装到外部抽象类或者接口(方便设计给其他类用这样子):


34.png


35.png


2,二叉树的力扣算法题:

36.png


总结一些小套路吧 (没有通用的套路,就讲一下方法哈):

✿ 自己需要先知道的是二叉树的特点就是左右子树(左右结点)构成(一般没有特别强调的二叉树,结点类就是左右子树(结点)(没给父指针结点哈,咱自己设计加的))

套路: 层序遍历:一层一层从左到右,一个一个结点的遍历。使用的数据结构是 队列。

 

(1)101_对称二叉树 的方法 和 套路:

方法一:(该过程使用层序遍历~迭代的,使用的数据结构是队列(为何数据结构使用队列:一层一层从左边到右边【父节点A 先于 父结点B, 则父节点A 的孩子 会先于 父结点B 的孩子】)

~两个“镜子”同时进行添加~ 不过作者用一面镜子成双对称式添加方式,变成了两个“镜子” )

思路是:

● 两块镜子的思维:第一个镜子添加进入了左,则第二个镜子添加进入了右 然后第一个镜子添加进入了右,第二个镜子添加进入了左

● 非常非常的巧妙的一个点是作者通过了一个队列的数据,(同时成双添加)结果实现了一个镜子变成两个镜子的效果
● 这里使用的数据结构是:队列(特点先进先出),为了进行(“两面镜子”)每一层的遍历的同步对比

 

(2)102_二叉树的层序遍历 的方法与套路 :

方法一:

思路是: 该过程使用层序遍历~迭代的,使用的数据结构是队列(为何数据结构使用队列:一层一层从左边到右边【父节点A 先于 父结点B, 则父节点A 的孩子 会先于 父结点B 的孩子】)

 

(3)104_二叉树的最大深度 的方法与套路 :

方法一:该过程使用层序遍历~迭代的,使用的数据结构是队列(为何数据结构使用队列:一层一层从左边到右边【父节点A 先于 父结点B, 则父节点A 的孩子 会先于 父结点B 的孩子】),然后只需要知道当前层遍历完毕(遍历到最后一个结点,这里咱定义一个辅助变量,通过辅助变量数量为 0 时知道进入下一层~ 通过分析,得知:辅助变量= queue.size() )

 

(4)105_从前序与中序遍历序列构造二叉树 的方法与套路 :

方法一:

思路是:

    * 前序特点:第一个便是根结点(知道根比较快),中序:左 根 右

    * 前序提供的根结点, 中序不断地划分成左子树、右子树

套路:定义一个 map,键值对,中序的【值, 位置】,方便通过(前序提供的根)值确定到位置对应中序中的位置,从而让中序 划分出左子树、右子树。

 

(5)106_从中序与后序遍历序列构造二叉树 的方法与套路 :

方法一:

思路是:

    * 后序特点:最后一个便是根结点(知道根比较快),中序:左 根 右
    * 后序提供的根结点, 中序不断地划分成左子树、右子树

套路:定义一个 map,键值对,中序的【值, 位置】,方便通过(后序提供的根)值确定到位置对应中序中的位置,从而让中序 划分出左子树、右子树。

 

(6)107_二叉树的层序遍历II 的方法与套路 :

方法一:从叶子到根的遍历(层序遍历)~ 思路:倒序:只需要不断插入第一个位置

 

(7)114_二叉树展开为链表 的方法与套路 :

方法一:

思路是:题目给提示了呀:“展开后的单链表应该与二叉树 先序遍历 顺序相同。”

咱就利用先序遍历的递归或者迭代遍历到每个结点,将其添加到list 集合中,(为什么要添加到List 而不直接操作:)

理由:咱需要重塑一颗树的形状呀(不是你想变就能变)

//然后咱再遍历每个结点,将其左指针指向null,右指针指向下个结点(构建出链式形状)

 

(8)144_二叉树的前序遍历 的方法与套路 :

☺ 树的形状:【左区域(左子树)        根(父结点)              右区域(右子树)】

☺☺☺ 对于树的遍历,到下一层,在形式上是先到了“根”(父结点)上。

前序遍历【根数据先出,然后才是左或者右子树的数据】:

左区域(左子树):数据需要先出根数据,然后才是左、右小小子树数据       根(父结点)  右区域(右子树):数据需要先出根数据,然后才是左、右小小子树数据

 

方法一:递归实现

方法二:迭代实现:使用的数据结构是栈(为何数据结构使用栈:左区域:一层一层从左不断地往左,(不断地拿到根的结点,将数据添加上)直到没有左了【左为null】,然后从最后这个左【null】这一层层开始,因为没有左了,pop 掉的是当前的根(已经遍历过的结点),然后就开始右结点区域。

TreeNode node = root;
       while (!stack.isEmpty() || node != null) {
           while (node != null) {
               res.add(node.val);
               stack.push(node);
               node = node.left;
           }
           node = stack.pop();
           node = node.right;
       }

 

(9)145_二叉树的后序遍历 的方法与套路 :

方法一:递归实现

方法二:迭代实现使用的数据结构是栈(为何数据结构使用栈:一层一层从左不断地往左,(不断地拿到根的结点)直到没有左了【左为null】,然后从最后这个左【null】这一层层开始,pop出当前根结点,寻找右结点,然后就开始右子树区域:可能右区域(右子树不存在哦):

① 没有右子树,则已经遍历完数据(左右数据null、null):这是轮到根数据,添加根数据,然后记录当前结点(可能会是下一次的右区域),然后置空根结点;

② 有右子树:把根结点 push回去,还没轮到添加根的数据(左右区域数据的添加优于根数据),切换到右区域 ;

 TreeNode prev = null;
       while(!stack.isEmpty() || root != null) {
           while(root != null) {
               stack.push(root);
               root = root.left;
           }
           root = stack.pop();
           if (root.right == null || root.right == prev) {
               list.add(root.val);
               prev = root;
               root = null;    //不加:超出内存
           } else {
               stack.push(root);
               root = root.right;
           }
       }       

 

(10)226_翻转二叉树 的方法与套路 :

方法一:递归:(前、后序递归),遍历拿到当前结点时就将其左右子树(结点)进行交换。(中序遍历):因为先处理好左子树,然后进行交换,处理过的左子树到了右边去了,原先没处理的右子树现在在左边

方法二:迭代:层序遍历:遍历到当前结点时将左右结点进行交换。

 

(11)559_N叉树的最大深度 的方法与套路 :

方法一:递归:细节:遍历每个孩子前  height 必须等于 1;

方法二:迭代 : 使用到了层序遍历的框架【队列】(通过辅助变量知道当前层到了最后一个结点)/   或者通过 Pair 对象(与map 差不多,只是它可以直接存或取俩个数据(一对))

 

(12)589_N叉树的前序遍历 的方法与套路 :

方法一:递归实现:先拿到当前结点,然后遍历孩子树

方法二:迭代实现:利用到了栈的特点跟集合倒序(拿到根结点后,先添加根的数据,然后逆置孩子树结点:Collections.reverse(node.children); 再把孩子树的添加进去栈)

 

(13)590_N叉树的后序遍历 的方法与套路 :

方法一:递归实现:先遍历孩子树然后再拿到当前结点。

方法二:迭代实现:利用到了栈的特点,拿到当前父结点(pop 出来拿到它)~将其 数据倒序添加进当前“所谓的第一个位置” ,然后再实现遍历添加孩子树倒序添加进当前“所谓的第一个位置”】,这样便实现: 孩子树的数据在根结点之前。

 

(14)662_二叉树最大宽度 的方法与套路 :

方法一:直接套用层序遍历的框架(有bug:来自于,无法应对null的中间结点),使用map无法做到,

需要像官网那样封装一个类(标记结点位置的)~ 然后刚刚进入下一层时(通过一个辅助变量进行判断是否进行了下一层~ 当深度辅助变量 != 当前结点的深度时,进入下一层 )标记最左的位置,然后遍历每个位置与之进行取最大宽度,最后返回最大的宽度。

 

(15)889_根据前序和后序遍历构造二叉树 的方法与套路 :

方法一:

思路是:

    * 前序特点:第一一个便是根结点(知道根比较快),后序: 左 右 根
    * 前序提供的根结点, 后序不断地划分成左子树、右子树

套路:定义一个 map,键值对,后序的【值, 位置】,方便通过(后序提供的根)值确定到位置对应后序中的位置,从而让后序 划分出左子树、右子树。

 

(16)94_二叉树的中序遍历 的方法与套路 :

方法一:递归实现

方法二:迭代实现使用的数据结构是栈(为何数据结构使用栈:一层一层从左不断地往左,(不断地拿到根的结点)直到没有左了【左为null】,然后从最后这个左【null】这一层层开始,pop出当前根结点,并将根数据进行添加,然后就开始右子树区域。

        while(node != null || !stack.isEmpty() ) {
           while(node != null) {
               stack.push(node);
               node = node.left;
           }
           node = stack.pop();
           list2.add(node.val);    //已经拿到当前结点了    
           node = node.right;
       }

 

总结:【前、中、后序的迭代遍历~使用到了栈数据结构】:

1,为啥:一开始就不断地往左,往左:while(node != null){ ....    node = node.left; }?

❀ 因为需要到下一层【而下一层就是 左 右】,咱先到达最近的左呗。

【在不断地通过node = node.left, 到达下一层的遍历过程,其实在形式上是到达下一层的根,先到达了下一层的根结点。】

 

 

套路哈哈哈:

1,想要使用递归【当直接使用不了题目给的参数是,可以自己定义一个同名(参数自己定,然后不断的递归)】

题意给的方法再调用该方法即可。例如力扣:106_从中序与后序遍历序列构造二叉树:
*  public TreeNode buildTree(int[] inorder, int[] postorder) 这个方法是题目本身,这个接口无法直接递归调用实现生成目的。
*  public TreeNode buildTree(int is, int ie, int ps, int pe) 这个方法是自定义的
*  使用递归突破点:什么时候跳出return:往往看传入的参数的关系(当参数变化到不合理是即退出)

 

2,map 哈希表非常有作用哈哈,有一一对应的关系【相当于字典】

目录
相关文章
|
12月前
|
机器学习/深度学习 算法 数据挖掘
K-means聚类算法是机器学习中常用的一种聚类方法,通过将数据集划分为K个簇来简化数据结构
K-means聚类算法是机器学习中常用的一种聚类方法,通过将数据集划分为K个簇来简化数据结构。本文介绍了K-means算法的基本原理,包括初始化、数据点分配与簇中心更新等步骤,以及如何在Python中实现该算法,最后讨论了其优缺点及应用场景。
1145 6
|
8月前
|
存储 算法 Java
算法系列之数据结构-二叉树
树是一种重要的非线性数据结构,广泛应用于各种算法和应用中。本文介绍了树的基本概念、常见类型(如二叉树、满二叉树、完全二叉树、平衡二叉树、B树等)及其在Java中的实现。通过递归方法实现了二叉树的前序、中序、后序和层次遍历,并展示了具体的代码示例和运行结果。掌握树结构有助于提高编程能力,优化算法设计。
257 10
 算法系列之数据结构-二叉树
分享一些提高二叉树遍历算法效率的代码示例
这只是简单的示例代码,实际应用中可能还需要根据具体需求进行更多的优化和处理。你可以根据自己的需求对代码进行修改和扩展。
319 64
|
10月前
|
存储 算法 测试技术
【C++数据结构——树】二叉树的遍历算法(头歌教学实验平台习题) 【合集】
本任务旨在实现二叉树的遍历,包括先序、中序、后序和层次遍历。首先介绍了二叉树的基本概念与结构定义,并通过C++代码示例展示了如何定义二叉树节点及构建二叉树。接着详细讲解了四种遍历方法的递归实现逻辑,以及层次遍历中队列的应用。最后提供了测试用例和预期输出,确保代码正确性。通过这些内容,帮助读者理解并掌握二叉树遍历的核心思想与实现技巧。
414 3
|
11月前
|
存储 算法 Python
文件管理系统中基于 Python 语言的二叉树查找算法探秘
在数字化时代,文件管理系统至关重要。本文探讨了二叉树查找算法在文件管理中的应用,并通过Python代码展示了其实现过程。二叉树是一种非线性数据结构,每个节点最多有两个子节点。通过文件名的字典序构建和查找二叉树,能高效地管理和检索文件。相较于顺序查找,二叉树查找每次比较可排除一半子树,极大提升了查找效率,尤其适用于海量文件管理。Python代码示例包括定义节点类、插入和查找函数,展示了如何快速定位目标文件。二叉树查找算法为文件管理系统的优化提供了有效途径。
190 5
|
存储 缓存 算法
如何提高二叉树遍历算法的效率?
选择合适的遍历算法,如按层次遍历树时使用广度优先搜索(BFS),中序遍历二叉搜索树以获得有序序列。优化数据结构,如使用线索二叉树减少空指针判断,自定义节点类增加辅助信息。利用递归与非递归的特点,避免栈溢出问题。多线程并行遍历提高速度,注意线程安全。缓存中间结果,避免重复计算。预先计算并存储信息,提高遍历效率。综合运用这些方法,提高二叉树遍历算法的效率。
311 5
|
机器学习/深度学习 JSON 算法
二叉树遍历算法的应用场景有哪些?
【10月更文挑战第29天】二叉树遍历算法作为一种基础而重要的算法,在许多领域都有着不可或缺的应用,它为解决各种复杂的问题提供了有效的手段和思路。随着计算机科学的不断发展,二叉树遍历算法也在不断地被优化和扩展,以适应新的应用场景和需求。
627 0
|
存储 算法
非递归实现后序遍历时,如何避免栈溢出?
后序遍历的递归实现和非递归实现各有优缺点,在实际应用中需要根据具体的问题需求、二叉树的特点以及性能和空间的限制等因素来选择合适的实现方式。
272 59
|
5月前
|
编译器 C语言 C++
栈区的非法访问导致的死循环(x64)
这段内容主要分析了一段C语言代码在VS2022中形成死循环的原因,涉及栈区内存布局和数组越界问题。代码中`arr[15]`越界访问,修改了变量`i`的值,导致`for`循环条件始终为真,形成死循环。原因是VS2022栈区从低地址到高地址分配内存,`arr`数组与`i`相邻,`arr[15]`恰好覆盖`i`的地址。而在VS2019中,栈区先分配高地址再分配低地址,因此相同代码表现不同。这说明编译器对栈区内存分配顺序的实现差异会导致程序行为不一致,需避免数组越界以确保代码健壮性。
107 0
栈区的非法访问导致的死循环(x64)
232.用栈实现队列,225. 用队列实现栈
在232题中,通过两个栈(`stIn`和`stOut`)模拟队列的先入先出(FIFO)行为。`push`操作将元素压入`stIn`,`pop`和`peek`操作则通过将`stIn`的元素转移到`stOut`来实现队列的顺序访问。 225题则是利用单个队列(`que`)模拟栈的后入先出(LIFO)特性。通过多次调整队列头部元素的位置,确保弹出顺序符合栈的要求。`top`操作直接返回队列尾部元素,`empty`判断队列是否为空。 两题均仅使用基础数据结构操作,展示了栈与队列之间的转换逻辑。

热门文章

最新文章