跟树有关的问题,基本上会涉及树的遍历。
在这里,将对四种树的遍历,包括前序遍历 (preoder traverse),中序遍历(preoder traverse),
后序遍历 (preoder traverse),层次遍历(preoder traverse)做一些总结。
除了树的遍历外,还会介绍一些不同的树的类型,和一些解决树问题的方法。
下图为一个树的例子。
一、树的遍历
1.1 前序遍历
注意,所以的序都是相对于root节点来讲的。
前序遍历即以为root 最先遍历,接着遍历左子树,最后遍历右子树。
(其实遍历左子树的时候,又可以把它看成一棵新的树)
此树的前序遍历结果为:4,7,9,10,8。
根据,我们先前的描述,前序遍历可以很简单地用递归的方式来实现。
但同时需要掌握如何迭代地实现。
迭代是靠栈(stack)实现的,因为先不用遍历右子树,所以将右子树先存储起来。
1.2 中序遍历
先遍历左子树,接着遍历root,最后遍历右子树。
此树的中序遍历结果为:9,7,10,4,8。
需要掌握递归的中序遍历和迭代的中序遍历两种方式。
迭代的依旧靠栈实现,同样将遍历图中的右子树存储,跟前序遍历的差别是:root输出的先后。
1.3 后序遍历
先左子树,再右子树,最后root。
此树的后序遍历结果为:9,10, 7, 8, 4。
依旧有递归和迭代两种方式。
迭代的时候,用栈存储暂时不用输出的信息。
1.4 层次遍历
前面的3中遍历,可以看成深度优先遍历(depth-first search),往某一条路一直走,直至到了走不了位置。
树的层次遍历,则是一种广度优先遍历(breadth-first search)。
它的遍历结果为:
[ 4 ] [ 7, 8 ] [9 , 10 ]
用Queue来实现层次遍历。
掌握好了这几种遍历,明白各种遍历真正意味着什么,基本能处理80%的跟树有关的题目了。
很多时候,都是这几种遍历的结合体。
比如,给对于一棵没有重复值的树,中序遍历 + 后序遍历,或者 中序遍历 + 前序遍历 的序列都能够恢复出一棵树。
这是因为中序遍历本身就意味着左右的分割。
二、不同的树
2.1 二叉树 (binary tree )
这是最简单的树了,每个节点可以有左右节点,值之间没有限制。
2.2 二分搜索树(binary seach tree)
这是比二叉树更高级的树,它对节点的值做了限制。
每个节点的值必须比它的左节点值大,必须必它的右节点值小。
这个的好处是给定一个值,可以进行二分搜索(binary search)。
二分搜索树一个很重要的性质:中序遍历是递增序列。这个性质有时候能帮助我们解决某些问题。
2.3 平衡二叉树 (balanced binary tree)
这也是比二叉树更高级的树。高级在它要求一个节点的左子树和右子树的高度 <= 1。
当然它的左右子树也要满足这个要求。
树平衡的好处在于,能够保证查找的时间复杂度保证在 O (logN )内。
2.4 满二叉树 (full binary tree )
根据名字也可以猜到这树长什么样了,这是一个棵都填满的树。
除最后一层,每一个节点都有2个子节点。
最后一个,没有任何子节点。
给图中的8节点,加上2个子节点,就是蛮二叉树了。
满二叉树有一些性质:
假设层数为k
a. 总节点树是2 ^ k - 1。
b. 对树进行层次遍历, 下标为 i 的节点的左节点为 i * 2 + 1, 右节点为 i * 2 + 2。
2.5 完全二叉树 (complete binary tree)
完全二叉树对满二叉树的要求放宽了。
最后一层,不需要完全填慢,比如最上面的,其实就是一棵完全二叉树。
三、解决树问题的法宝。
1.核心思想是搜索的思想!
结合具体的应用,看用深度优先搜索还是广度优先搜索。
一般90%都是深度优先搜索。
2. 对于深度优先的搜索问题,递归可以说是实现这些的问题最简洁和最清晰的方法。要掌握利用递归,翻转二叉树(invert binary tree),判断是不是对称树(symmetirc tree),树的高度,两棵树是不是相同的等等问题。
递归是将root 的左右子树都看成一个新的树,所以原树的所有节点其实都是可以成为root 节点。
递归分两步。
1. 问题的分解。(一般先想这个)
2. 递归的结束