二叉树的非递归遍历
最近看书上说道要掌握二叉树遍历的6种编写方式,之前只用递归方式编写过,这次就用非递归方式编写试一试。
C++编程练习(8)----“二叉树的建立以及二叉树的三种遍历方式“(前序遍历、中序遍历、后续遍历)
递归的思想也就是栈的思想,既然不用递归,那就改用栈的方式。
“递归=栈”
1、前序遍历
前序遍历按照“根结点-左孩子-右孩子”的顺序进行访问。
a)递归实现前序遍历:
- void PreOrderTraverse(BiTNode *T) /*递归前序遍历*/
- {
- if (T==NULL)
- return;
- std::cout<<T->data<<"\t";
- PreOrderTraverse(T->lchild);
- PreOrderTraverse(T->rchild);
- }
b)非递归实现前序遍历:
对于根结点P:
1)访问结点P,并将结点P入栈;
2)判断结点P的左孩子是否为空,若为空,则取栈顶结点并进行出栈操作,并将栈顶结点的右孩子置为当前的结点P,循环至1);若不为空,则将P的左孩子置为当前的结点P;
3)直到P为NULL并且栈为空,则遍历结束。
- void nPreOrderTraverse(BiTNode *T) /*非递归前序遍历*/
- {
- if (T==NULL)
- return;
- BiTNode *p;
- p = T;
- std::stack<BiTNode*> stk;
- while(p!=NULL||!stk.empty())
- {
- while(p!=NULL)
- {
- std::cout<<p->data<<"\t";
- stk.push(p);
- p = p->lchild;
- }
- if(!stk.empty())
- {
- p = stk.top();
- stk.pop();
- p = p->rchild;
- }
- }
- }
2、中序遍历
中序遍历按照“左孩子-根结点-右孩子”的顺序进行访问。
a)递归实现中序遍历
- void InOrderTraverse(BiTNode *T) /*递归中序遍历*/
- {
- if (T==NULL)
- return;
- InOrderTraverse(T->lchild);
- std::cout<<T->data<<"\t";
- InOrderTraverse(T->rchild);
- }
b)非递归实现中序遍历
对于根结点P:
1)若其左孩子不为空,则将P入栈并将P的左孩子置为当前的P,然后对当前结点P再进行相同的处理;
2)若其左孩子为空,则取栈顶元素并进行出栈操作,访问该栈顶结点,然后将当前的P置为栈顶结点的右孩子;
3)直到P为NULL并且栈为空则遍历结束。
- void nInOrderTraverse(BiTNode *T) /*非递归中序遍历*/
- {
- if(T==NULL)
- return;
- std::stack<BiTNode*> stk;
- BiTNode* p;
- p = T;
- while(p!=NULL || !stk.empty())
- {
- while(p!=NULL)
- {
- stk.push(p);
- p = p->lchild;
- }
- if(!stk.empty())
- {
- p = stk.top();
- stk.pop();
- std::cout<<p->data<<"\t";
- p = p->rchild;
- }
- }
- }
3、后序遍历
后序遍历按照“左孩子-右孩子-根结点”的顺序进行访问。
a)递归实现后序遍历
- void PostOrderTraverse(BiTNode *T) /*递归后序遍历*/
- {
- if(T==NULL)
- return;
- PostOrderTraverse(T->lchild);
- PostOrderTraverse(T->rchild);
- std::cout<<T->data<<"\t";
- }
b)非递归实现后序遍历
要保证根结点在左孩子和右孩子访问之后才能访问,因此对于任一结点P,先将其入栈。如果P不存在左孩子和右孩子,则可以直接访问它;或者P存在左孩子或者右孩子,但是其左孩子和右孩子都已被访问过了,则同样可以直接访问该结点。若非上述两种情况,则将P的右孩子和左孩子依次入栈,这样就保证了每次取栈顶元素的时候,左孩子在右孩子前面被访问,左孩子和右孩子都在根结点前面被访问。
对于根结点P:
1)将P入栈,设置当前结点 cur ;
2)将当前的 cur 置为栈顶结点,如果 cur 不存在左孩子和右孩子,或者 cur 存在左孩子或者右孩子,但是其左孩子和右孩子都已被访问过了,则可以直接访问该结点并进行出栈操作。否则将 cur 的右孩子和左孩子依次入栈;
3)直到栈为空则遍历结束。
- void nPostOrderTraverse(BiTNode *T) /*非递归后序遍历*/
- {
- if(T==NULL)
- return;
- BiTNode* cur; /*当前结点*/
- BiTNode* pre = NULL; /*前一次输出的结点*/
- std::stack<BiTNode*> stk;
- stk.push(T);
- while(!stk.empty())
- {
- cur = stk.top();
- if((cur->lchild==NULL && cur->rchild==NULL) ||
- (pre!=NULL && (cur->lchild==pre || cur->rchild==pre)))
- { /*如果当前结点没有孩子结点或者孩子节点都已被访问过*/
- std::cout<<cur->data<<"\t";
- stk.pop();
- pre = cur;
- }
- else
- {
- if(cur->rchild!=NULL)
- stk.push(cur->rchild);
- if(cur->lchild!=NULL)
- stk.push(cur->lchild);
- }
- }
- }
4、完整测试代码
1)BiTree.h头文件
- /* BiTree.h头文件 */
- #include<iostream>
- #include<stack>
- typedef char TElemType;
- class BiTNode{ /*创建结点类,使用的是左右孩子表示法*/
- public:
- BiTNode():data(0),lchild(NULL),rchild(NULL){}
- TElemType data;
- BiTNode *lchild,*rchild;
- };
- void CreateBiTree(BiTNode **T) /*二叉树的建立,这里形参用的是双指针,需要注意*/
- { /*这里输入的是一个扩展二叉树,每个结点若有空指针,*/
- TElemType ch; /*则将其值设为一个特定值,本代码中是'#'*/
- std::cin>>ch;
- std::cin.clear();
- if(ch=='#')
- *T=NULL;
- else
- {
- *T=new BiTNode;
- if(!*T)
- exit(1);
- (*T)->data=ch;
- CreateBiTree(&(*T)->lchild);
- CreateBiTree(&(*T)->rchild);
- }
- }
- void PreOrderTraverse(BiTNode *T) /*递归前序遍历*/
- {
- if (T==NULL)
- return;
- std::cout<<T->data<<"\t";
- PreOrderTraverse(T->lchild);
- PreOrderTraverse(T->rchild);
- }
- void nPreOrderTraverse(BiTNode *T) /*非递归前序遍历*/
- {
- if (T==NULL)
- return;
- BiTNode *p;
- p = T;
- std::stack<BiTNode*> stk;
- while(p!=NULL||!stk.empty())
- {
- while(p!=NULL)
- {
- std::cout<<p->data<<"\t";
- stk.push(p);
- p = p->lchild;
- }
- if(!stk.empty())
- {
- p = stk.top();
- stk.pop();
- p = p->rchild;
- }
- }
- }
- void InOrderTraverse(BiTNode *T) /*递归中序遍历*/
- {
- if (T==NULL)
- return;
- InOrderTraverse(T->lchild);
- std::cout<<T->data<<"\t";
- InOrderTraverse(T->rchild);
- }
- void nInOrderTraverse(BiTNode *T) /*非递归中序遍历*/
- {
- if(T==NULL)
- return;
- std::stack<BiTNode*> stk;
- BiTNode* p;
- p = T;
- while(p!=NULL || !stk.empty())
- {
- while(p!=NULL)
- {
- stk.push(p);
- p = p->lchild;
- }
- if(!stk.empty())
- {
- p = stk.top();
- stk.pop();
- std::cout<<p->data<<"\t";
- p = p->rchild;
- }
- }
- }
- void PostOrderTraverse(BiTNode *T) /*递归后序遍历*/
- {
- if(T==NULL)
- return;
- PostOrderTraverse(T->lchild);
- PostOrderTraverse(T->rchild);
- std::cout<<T->data<<"\t";
- }
- void nPostOrderTraverse(BiTNode *T) /*非递归后序遍历*/
- {
- if(T==NULL)
- return;
- BiTNode* cur; /*当前结点*/
- BiTNode* pre = NULL; /*前一次输出的结点*/
- std::stack<BiTNode*> stk;
- stk.push(T);
- while(!stk.empty())
- {
- cur = stk.top();
- if((cur->lchild==NULL && cur->rchild==NULL) ||
- (pre!=NULL && (cur->lchild==pre || cur->rchild==pre)))
- { /*如果当前结点没有孩子结点或者孩子节点都已被访问过*/
- std::cout<<cur->data<<"\t";
- stk.pop();
- pre = cur;
- }
- else
- {
- if(cur->rchild!=NULL)
- stk.push(cur->rchild);
- if(cur->lchild!=NULL)
- stk.push(cur->lchild);
- }
- }
- }
2)main文件
- #include"BiTree.h"
- using namespace std;
- int main()
- {
- BiTNode *T=new BiTNode;
- std::cout<<"请前序遍历输入各节点:";
- CreateBiTree(&T);
- cout<<"\n该树的前序遍历输出为:"<<endl;
- PreOrderTraverse(T);
- cout<<endl;
- nPreOrderTraverse(T);
- cout<<"\n该树的中序遍历输出为:"<<endl;
- InOrderTraverse(T);
- cout<<endl;
- nInOrderTraverse(T);
- cout<<"\n该树的后序遍历输出为:"<<endl;
- PostOrderTraverse(T);
- cout<<endl;
- nPostOrderTraverse(T);
- cout<<endl;
- return 0;
- }
5、测试结果
最后,用表格罗列一下前序、中序和后续遍历编程实现的区别,以便加强记忆。
| 不同点 | 相同点 |
前序遍历 | 1)在最外侧左子节点压入栈中的同时,输出该子节点的值; 2)发现节点存在“右节点”时,需要将当前节点弹出; | 第一步,以根节点为起始,将左右最外侧左子节点压入栈中; 第二步,从栈顶元素进行判别,看是否存在右子节点; 第三步,重复第一、第二步,知道栈中元素为空 |
中序遍历 | 1)在最外侧左子节点压入栈中的时候,并不进行节点输出; 2)发现节点存在“右节点”时,需要将当前节点弹出; | |
后序遍历 | 1)在最外侧左子节点压入栈中的时候,并不进行节点输出; 2)发现节点存在“右节点”时,不需要弹出当前节点; 【注意】:第二次遍历到“既含有左节点又含有右节点”的根节点时,需要弹出该节点。因此需要利用两个堆栈来记录重复遍历的节点。 |