作者:llhthinker 个人博客:http://www.cnblogs.com/llhthinker/ 往期回顾: Stanford机器学习笔记-2.Logistic Regression Stanford机器学习笔记-3.Bayesian statistics and Regularization Stanford机器学习笔记-4. 神经网络Neural Networks (part one) Stanford机器学习笔记-5.神经网络Neural Networks (part two) Stanford机器学习笔记-7. Machine Learning System Design Stanford机器学习笔记-8. 支持向量机(SVMs)概述 对于一种数据结构而言,遍历是常见操作。二叉树是一种基本的数据结构,是一种每个节点的儿子数目都不多于2的树。二叉树的节点声明如下:
二叉树的遍历主要有先序遍历,中序遍历,后序遍历,层序遍历四种方式,下面一一介绍。 1. 先序遍历 在先序遍历中,对节点的访问工作是在它的左右儿子被访问之前进行的。换言之,先序遍历访问节点的顺序是根节点-左儿子-右儿子。由于树可以通过递归来定义,所以树的常见操作用递归实现常常是方便清晰的。递归实现的代码如下:
由递归代码可以看出,该递归为尾递归(尾递归即递归形式在函数末尾或者说在函数即将返回前)。尾递归的递归调用需要用栈存储调用的信息,当数据规模较大时容易越出栈空间。虽然现在大部分的编译器能够自动去除尾递归,但是即使如此,我们不妨自己去除。非递归先序遍历算法基本思路:使用堆栈 a. 遇到一个节点,访问它,然后把它压栈,并去遍历它的左子树; b. 当左子树遍历结束后,从栈顶弹出该节点并将其指向右儿子,继续a步骤; c. 当所有节点访问完即最后访问的树节点为空且栈空时,停止。 实现代码如下:
2. 中序遍历 中序遍历的遍历路径与先序遍历完全一样。其实现的思路也与先序遍历非常相似。其主要的不同点是访问节点顺序不同:中序遍历是访问完所有左儿子后再访问根节点,最后访问右儿子,即为左儿子-根节点-右儿子。 递归实现的代码如下:
非递归辅助栈实现代码如下:
非递归不用辅助栈实现中序遍历: 试设计一个非递归算法,按中根顺序遍历非线索二叉树,但不得用任何辅助栈。在执行算法期间,允许改变左孩子指针和右孩子指针的值。 算法:右线索化+回溯 若当前树的根节点p有左孩子且未被线索化:将其左孩子的最右结点(可为左孩子本身)指向p,即右线索化,然后p = p->lChild; 若p有左孩子但已被线索化,说明该p是回溯上来的,即左孩子已经被访问了,则释放线索化的指针; 若p无左孩子,打印p,向上回溯(即p = p->rChild)。 代码如下:
运行结果: 参考博客:http://m.blog.csdn.net/blog/Raito__/40618257 3. 后序遍历 后序遍历与中序遍历,先序遍历的路径也完全一样。主要的不同点是后序遍历访问节点的顺序是先访问左儿子和右儿子,最后访问节点,即左儿子-右儿子-根节点。 递归实现思路与中序遍历和先序遍历相似,代码如下:
后序遍历的非递归实现 思路一: 对于一个节点而言,要实现访问顺序为左儿子-右儿子-根节点,可以利用后进先出的栈,在节点不为空的前提下,依次将根节点,右儿子,左儿子压栈。故我们需要按照根节点-右儿子-左儿子的顺序遍历树,而我们已经知道先序遍历的顺序是根节点-左儿子-右儿子,故只需将先序遍历的左右调换并把访问方式打印改为压入另一个栈即可。最后一起打印栈中的元素。代码如下:
思路一的优点是由于利用了先序遍历的思想,代码较简洁,思路较清晰。缺点是需要用一个栈来存储树的所有节点,空间占用较大。 思路二:
代码如下:
4. 层序遍历 二叉树遍历的核心问题是二维结构的线性化。我们通过节点访问其左右儿子时,存在的问题是访问左儿子后,右儿子怎么访问。因此我们需要一个存储结构保存暂时不访问的节点。前面三种遍历方式的非递归实现,我们是通过堆栈来保存。事实上也可以通过队列来保存。 队列实现的基本思路:遍历从根节点开始,首先将根节点入队,然后执行循环:节点出队,访问(访问)根节点,将左儿子入队,将右儿子入队,直到队列为空停止。 这种遍历方式的结果是将二叉树从上到下,从左至右一层一层的遍历,即层序遍历,代码实现如下:
|
|