【面试题之算法部分】二叉树的遍历
来源:互联网 发布:java中的compare 编辑:程序博客网 时间:2024/06/07 02:06
本篇文章主要目的是详细讨论二叉树的前序、中序和后序遍历算法,包括递归版和迭代版。首先给出递归版的一般思想:
- 前序遍历:先访问根节点,前序遍历左子树,前序遍历右子树。
- 中序遍历:中序遍历左子树,访问根节点,中序遍历右子树。
- 后序遍历:后序遍历左子树,后序遍历右子树,访问根结点。
我们首先定义节点的结构
typedef struct node{ int val; node *lChild; node *rChild;}BiTree;
一. 前序遍历
递归版本:
void PreOrder(BiTree* T){ if(!T) return; visit(T); //访问根节点的值 PreOrder(T->lChild); //递归访问左子树 PreOrder(T->rChild); //递归访问右子树}visit(Bitree *T){ cout << T->val << " " << endl;}
递归版本的时间复杂度为O(n),但是由于空间复杂度的常系数较迭代版本更高,我们可以改用等效迭代版本。
迭代版本1:
#define HasRChild(x) ((x).rChild)#define HasLChild(x) ((x).lChild)void PreOrder(BiTree *T){ stack<BiTree *> s; if(T) s.push(T); while(!s.empty()) { T = s.top(); s.pop(); visit(T); //每次先访问根节点 if(HasRChild(*T)) s.push(T->rChild); //将右孩子入栈 if(HasLChild(*T)) s.push(T->lChild); //将左孩子入栈 }}
迭代版本2:
我们先沿最左侧通路自顶向下访问访问沿途节点,再自底而上一次遍历这些节点的右子树。其实遍历过程和迭代版本1大同小异。
void visitAlongLeftBranch(BiTree *x, stack<BiTree *> &s){ while(x) { visit(x); if(x->rChild) s.push(x->rChild); x = x->lChild; }}void PreOrder(BiTree *x){ stack<BiTree *> s; while(true) { visitAlongLeftBranch(x, s); if(s.empty()) break; x = s.top(); s.pop(); }}
二. 中序遍历
注意二叉查找树的中序遍历是数据的升序过程。
递归版本:
void InOrder(BiTree *T){ if(!T) return; Inorder(T->lChild); visit(T); InOrder(T->rChild);}
将中序遍历递归版本改成迭代版本的难度在于:尽管右子树的递归是属于严格尾递归的,但是右子树的递归并不是,我们可以参看前序遍历迭代版本2的思想。
迭代版本1:
void visitAlongLeftBranch(BiTree *x, stack<BiTree *> s){ while(x) { s.push(x); x = x->lChild; }}void InOrder(BiTree *x){ stack<BiTree *> s; while(true) { visitLongLeftBranch(x, s); //不断将左侧节点入栈 if(s.empty()) break; x = s.top(); s.pop(); visit(x); //自底向上访问左侧节点 x = x->rChild; //进入右子树 }}
进一步优化,可将上面的两个while循环改成一个循环
迭代版本2
void InOrder(Bitree *x){ stack<Bitree *> s; while(true) { if(x) { s.push(x); x = x->lChild; } else { if(!empty()) { x = s.top(); s.pop(); visit(x); x = x->rChild; } else break; } }}
以上版本都需要辅助栈,尽管时间复杂度没有什么实质影响,但所需的空间复杂度正比于二叉树的高度,最坏情况下将达到O(n)(退化成单链)。为此,我们继续优化,如果node节点内有parent指针,我们借助parent指针来实现。基于一个事实:中序遍历中前后元素关系满足调用succ()函数(寻找其直接后继的函数)前后两元素的关系。
简单来讲,假设我现在遍历到节点A,按中序遍历下一个节点是B。那同样,我对A调用succ()后返回的节点一定也是B。
void Inorder(BiTree *x){ bool backtrace = false; while(true) { if(!backtrace && x->lchild) x = x->lChild; else { visit(x); if(x->rChild) { x = x->rChild; backtrace = false; } else { if(!(x = succ(x))) break; backtrace = true; } } }}//直接后继函数BiTree* succ(BiTree* x){ if(x->rChild) //如果存在右子树,则直接后继是右子树中的最左子孙 { x = x->rChild->lChild; while(x) x = x->lChild; } else //如果不存在右子树,则直接后继是“包含x节点于左子树中的最低祖先” { while(x == x->parent->rChild) x = x->parent; x = x->parent; }}
三. 后序遍历
递归版本:
void PostOrder(Bitree *T){ if(!T) return; PostOrder(T->lChild); PostOrder(T->rChild); visit(T);}
迭代版本:
在后序遍历算法的递归版中,由于左、右子树递归遍历均严格的不属于尾递归,因此实现对应的迭代式算法难度更大,不过,仍可套用之前的思路。
我们思考的起点依然是,此时首先访问哪个节点?如下图所示,从左侧水平向右看去,未被遮挡的最高叶节点v——称作最高左侧可见节点(HLVFL)——即为后序遍历首先访问的节点。注意,该节点有可能是左孩子,也有可能是右孩子。故图中以垂直边示意它与其父节点的联边。
考察连接于v与树根之间的唯一通路(粗线所示),与先序和中序遍历类似,自底而上沿着该通路,整个后序遍历也可以分解为若干个片段。每个片段,分别起始于通路的一个节点,并包括三步: 访问当前节点;遍历其右兄弟(若存在)为根的子树;以及向上回溯至父节点(若存在)并转入下一片段。
基于以上理解,导出迭代式后序遍历算法:
void gotoHLVFL(stack<BiTree *> &s) //以s栈节点为根的子树中,寻找最高左侧可见节点(HLVFL){ while(BiTree *x = s.top()) //自顶向下,反复检查当前节点(即栈顶) { if(HasLChild(*x)) //尽可能向左 { if(HasRChild(*x)) s.push(x->rChild); //若有右孩子,优先入栈 s.push(x->lChild); //然后才转至左孩子 } else s.push(x->rChild); //实不得已才向右 } s.pop(); //返回前弹出在栈顶的空节点}PostOrder(Bitree *x){ stack<Bitree *> s; if(x) s.push(x); while(!s.empty()) { //如果栈顶元素非当前元素之父,则必为当前元素的右兄弟,所以需要递归以右兄弟为根的树,继续寻找最高左侧可见节点(HLVFL) if(s.top() != x->parent) gotoHLVFL(s); x = s.top(); s.pop(); visit(x); }}
参考文献:
《数据结构c++语言版》,邓俊辉
- 【面试题之算法部分】二叉树的遍历
- 用代码实现二叉树的遍历-Java经典面试题算法部分核心
- 九章算法面试题7 分层遍历二叉树
- 剑指Offer算法题之已知两种遍历方式重建二叉树--面试题6:重建二叉树
- 百度面试题之二叉树层次遍历(从上到下,从下到上)
- 剑指offer之面试题24:二叉搜索树的后序遍历序列
- 剑指Offer之面试题24:二叉搜索树的后序遍历序列
- 二叉树的简单实现及其部分面试题
- 二叉树的基本操作及部分面试题
- 二叉树的基本操作及部分面试题
- 面试题06 - 判断二叉树后序遍历的结果 [树]
- 面试题24:二叉树的后序遍历序列
- 面试题24:二叉搜索树的后续遍历序列
- 【面试题22】二叉搜索树的后序遍历
- 剑指offer面试题-二叉树的前序中序后序遍历
- 面试题24:二叉树的后序遍历序列
- 【面试题之算法部分】字符串的全排列
- 【数据结构】二叉树部分面试题解法
- selenium 找元素的方法
- 技术类
- 字典树
- 从3D打印成本来看3D打印的预测
- maven工程目录中各个文件的作用
- 【面试题之算法部分】二叉树的遍历
- A Flexible New Technique for Camera Calibration
- hdu 4576 概率dp
- LeetCode:Excel Sheet Column Number
- MySql Cluster搭建
- HDU2037 事件排序问题
- ListView中convertView和ViewHolder的工作原理
- STL学习----入门(1)[Vector]
- tomcat目录文件的作用和处理过程