二叉树的遍历
二叉树的遍历是指按照一定的次序访问二叉树中的所有结点,并且每个结点仅被访问一次的过程,二叉树有四种常用的遍历方法
遍历的递归算法
- 先序遍历
先序遍历的过程
- 先输出根节点
- 再访问左子树
- 最后访问右子树
对于上述数的遍历顺序为:abdfcg
其递归模型为:
f ( n ) = { 输 出 f ( n − 1 ) 当 没 有 达 到 根 节 点 时 , 其 执 行 方 法 是 f ( n ) = { 输 出 根 节 点 访 问 左 子 树 访 问 右 子 树 f(n) = \begin{cases} 输出\\f(n-1)\end{cases} \\当没有达到根节点时,其执行方法是 f(n) = \begin{cases}输出根节点\\访问左子树\\访问右子树\end{cases} f(n)={输出f(n−1)当没有达到根节点时,其执行方法是f(n)=⎩⎪⎨⎪⎧输出根节点访问左子树访问右子树
void preOrder(BTNode* p){
if(p==NULL)return;
std::cout<<p->data<<'\t';
preOrder(p->left);
preOrder(p->right);
}
- 中序遍历
中序遍历的过程
- 先访问左子树
- 再输出根节点
- 最后访问右子树
对于上述数的遍历顺序为:dbfagc
其递归模型为:
f ( n ) = { 输 出 f ( n − 1 ) 当 没 有 达 到 根 节 点 时 , 其 执 行 方 法 是 f ( n ) = { 访 问 左 子 树 输 出 根 节 点 访 问 右 子 树 f(n) = \begin{cases} 输出\\f(n-1)\end{cases} \\当没有达到根节点时,其执行方法是f(n) = \begin{cases}访问左子树\\输出根节点\\访问右子树\end{cases} f(n)={输出f(n−1)当没有达到根节点时,其执行方法是f(n)=⎩⎪⎨⎪⎧访问左子树输出根节点访问右子树
void midOrder(BTNode* p){
if(p==NULL) return;
midOrder(p->left);
std::cout<<p->data<<'\t';
midOrder(p->right);
}
- 后序遍历
后序遍历的过程
- 先访问左子树
- 在访问右子树
- 最后输出根节点
对于上述数的遍历顺序为:afbgca
其递归模型为:
f ( n ) = { 输 出 f ( n − 1 ) 当 没 有 达 到 根 节 点 时 , 其 执 行 方 法 是 f ( n ) = { 访 问 左 子 树 访 问 右 子 树 输 出 根 节 点 f(n) = \begin{cases} 输出\\f(n-1)\end{cases} \\当没有达到根节点时,其执行方法是f(n) = \begin{cases}访问左子树\\访问右子树\\输出根节点\end{cases} f(n)={输出f(n−1)当没有达到根节点时,其执行方法是f(n)=⎩⎪⎨⎪⎧访问左子树访问右子树输出根节点
void lastOrder(BTNode* p){
if(p==NULL) return;
lastOrder(p->left);
lastOrder(p->right);
std::cout<<p->data<<'\t';
}
通过上述算法,可以实现很多的操作,从而达到解决复杂问题的效果!
ps
设计一个高效的算法,计算一颗采用二叉链存储结构存储的二叉树中的结点个数
其递归模型为:
f ( n ) = { 0 − − 二 叉 树 为 N U L L f ( n − > l e f t ) + f ( n − > r i g h t ) + 1 − − 二 叉 树 不 为 空 f(n) = \begin{cases} 0--二叉树为NULL\\f(n->left)+f(n->right)+1--二叉树不为空\end{cases} f(n)={0−−二叉树为NULLf(n−>left)+f(n−>right)+1−−二叉树不为空
int countNode(BTNode *p){
if(p==NULL)return 0;
return countNode(p->left)+countNode(p->right)+1;
}
设计一个高效的算法,输出一个给定二叉树的所有叶子节点
其递归模型为
f ( n ) = { 不 做 任 何 事 情 − − n 为 N U L L 输 出 叶 子 结 点 值 − − n 为 叶 子 节 点 f ( n − > l e f t ) & f ( n − > r i g h t ) − − n 不 为 叶 子 结 点 f(n) = \begin{cases} 不做任何事情--n为NULL\\输出叶子结点值--n为叶子节点\\f(n->left)\&f(n->right)--n不为叶子结点 \end{cases} f(n)=⎩⎪⎨⎪⎧不做任何事情−−n为NULL输出叶子结点值−−n为叶子节点f(n−>left)&f(n−>right)−−n不为叶子结点
void dispLeaf(BTNode *P){
if(p==NULL) return;
if(p->left==NULL&&p->right==NULL)
std::cout<<p->data<<'\t';
dispLeaf(p->left);
dispLeaf(p->right);
//可以看到,这个算法跟先序遍历有着离奇的相似性!
}
这几个遍历并不仅仅代表着遍历结点,理解好他,能在很多地方发挥作用!
ps
假设二叉树采用二叉链式存储结构,设计一个高效的算法求二叉树b中第k层的结点个数
void Lnodenum(BTNode *p,int h,int k,int &n){
if(b==NULL)
return;
if(h==k) k++;
else{
Lnodenum(p->left,h+1,k,n);
Lnodenum(p->right,h+1,k,n);
}
}
假设二叉树采用二叉链式存储结构,设计一个高效的算法判断两颗二叉树是否相似,二叉树相似的条件是,两个树为空或者两颗树的每一个对应结点相等
其递归模型为
f ( n ) = { t r u e − − b 1 和 b 2 都 为 N U L L f a l s e − − b 1 和 b 2 不 相 等 f ( n − > l e f t ) & f ( n − > r i g h t ) f(n) = \begin{cases}true--b1和b2都为NULL\\false--b1和b2不相等\\f(n->left)\&f(n->right)\end{cases} f(n)=⎩⎪⎨⎪⎧true−−b1和b2都为NULLfalse−−b1和b2不相等f(n−>left)&f(n−>right)
两个二叉树采用一种相同的遍历顺序,同时遍历,同时出发,在这之中,有一个结点
bool like(BTNode* b1,BTNode *b2){
if(b1==NULL&&b2==NULL)
return true;
if(b1==b2){
return (like(b1->right,b2->right)&&like(b1->left,b2->left));
}else{
return false;
}
}
设计一个高效的算法,输出值为x的结点的所有祖先
其递归模型为
f ( n ) = { f a l s e − − 当 树 为 空 树 时 t r u e − − 结 点 b 的 左 孩 子 和 右 孩 子 的 d a t a 为 x t r u e − − f ( n − > l e f t , x ) 为 t r u e 或 f ( n − > r i g h t , x ) 为 t r u e f a l s e − − 其 他 情 况 f(n) = \begin{cases} false--当树为空树时\\true--结点b的左孩子和右孩子的data为x\\true--f(n->left,x)为true或f(n->right,x)为true\\false--其他情况\end{cases} f(n)=⎩⎪⎪⎪⎨⎪⎪⎪⎧false−−当树为空树时true−−结点b的左孩子和右孩子的data为xtrue−−f(n−>left,x)为true或f(n−>right,x)为truefalse−−其他情况
bool ancestor(BTNode *b,x){
if(b==NULL)
return false;
else if(b->left!=NULL&&b->left->data==x||b->right!=NULL&&b->rigth->data==x){
std::cout<<b->data<<std::endl;
return true;
}
return ancestor(b->left,x)||ancestor(b->right,x);
}
遍历的非递归算法
先序遍历
用栈来实现,先序遍历的执行顺序为,先访问根节点,在访问左子树,再访问右子树,那么入栈的操作即为右子树先入栈,左子树再入栈
将根节点入栈 while(栈非空){ 栈中结点出栈 输出结点 if(结点右子树非空){ 将右子树入栈 } if(结点左子树非空){ 将左子树入栈 } }
实现
void preOrder(BSNode *b){ if(b==nullptr) return; BSNode *p; vector<BSNode*> stack; stack.push_back(b); while(!stack.empty()){ p = stack.back(); stack.pop_back(); cout<<p->data<<" "; if(p->right!=nullptr){ } if(p->left!=nullptr){ } } cout<<endl; p = nullptr; }
中序遍历
对于中序遍历,它的遍历顺序为左子树、根节点、右子树,对于遍历的非递归算法,最主要的其实也就是如何将递归顺序展现出来,如何去隐藏小的细节,对于二叉树的中序遍历,可以这样实现
p = b; while(栈不空||结点值不为nullptr){ while(结点p不为空){ 将结点p入栈 p = p->left; } //将栈顶结点的左子树和左孩子都入栈,入栈完后,栈顶结点应该是最底层最左边的叶子节点 if(栈不空){ p = 栈顶元素 元素出栈 输入结点p的值 p = p->right; //指向右节点 } }
实现
void midOrder(BTNode *b){ BTNode *p = b; vector<BTNode*> stack; while(!stack.empty()||p!=nullptr){ while(p!=nullptr){ stack.push_back(p); p = p->left; } if(!stack.empty()){ p = stack.back(); stack.pop_back(); cout<<p->data<<" "; p = p->right; } } cout<<endl; p = nullptr; }
后序遍历的非递归算法
后序遍历的顺序为,先左子树,然后右子树,最后根节点,既然是这一个顺序,如果使用栈来实现的话,首先需要将根节点最左边的子树压入栈中,然后出栈,此时出栈的是一个根节点,但是后续遍历是先左再右最后根节点,所以需要判断这个根结点的右子树是否都已经遍历完,至于这个根子树的右节点是否遍历完可以用一个bool值来实现
临时结点 r; 根节点 p; 指示值 flag; do{ //首先将根节点的所有左孩子入栈 while(p!=nullptr){ p入栈 p = p->left; } r = nullptr; flag = true; while(flag&&栈非空){ 获取栈顶元素 if(栈顶元素的右孩子都已经遍历完){ 输出结点值 结点出栈 r = 结点值; } else{ flag = false; p = p->right; } } }while(栈非空)
实现
void lastOrder(BTNode *b){ BTNode *p,*r; bool flag; vector<BTNode*> stack; p = b; do{ while(p!=nullptr){ stack.push_back(p); p = p->left; } r = nullptr; flag = true; //表示根节点的左子树全部入栈完了 while(!stack.empty()&&flag){ p = stack.back(); //获取栈顶元素 if(p->right==r){ cout<<p->data<<" "; stack.pop_back(); r = p; //r指向刚刚访问过的结点 } else{ p = p->right; flag = false; } } }while(!stack.empty()); p = nullptr; r = nullptr; cout<<endl; }
ps
假设二叉树采用二叉链式存储结构,设计一个高效的算法输出从根结点到每个叶子结点的路径的逆序列,要求采用后续的非递归算法实现
分析
这个就是在非递归后续遍历的基础上,在输出的时候判断该节点是否为叶子结点,如果是的话,就输出栈中所有的元素,从栈顶到栈底
代码实现
void solution(BTNode *b){ BTNode *p,*r; bool flag; vector<BTNode*> stack; p = b; do{ while(p!=nullptr){ stack.push_back(p); p = p->left; } r = nullptr; flag = true; //表示根节点的左子树全部入栈完了 while(!stack.empty()&&flag){ p = stack.back(); //获取栈顶元素 if(p->right==r){ if(p->left==nullptr&&p->right==nullptr){ //逆序输出路径 int i = stack.size(); while(i>0){ cout<<stack[i-1]->data<<" "; i--; } cout<<endl; } stack.pop_back(); r = p; //r指向刚刚访问过的结点 } else{ p = p->right; flag = false; } } }while(!stack.empty()); p = nullptr; r = nullptr; cout<<endl; }