递归完成树的遍历很好理解,倘若是非递归,不要告诉我算法导论上有,我要maker的思考过程
既然递归能够实现,那就模拟递归。递归的本质就是压栈。
首先简单树,观察递归的压栈过程
A、B即使节点的数据也代表节点的地址。
对这棵树使用递归完成前序创建
#include <iostream>
using namespace std;
struct treenode;
typedef struct treenode* TREE;
struct treenode
{
char data;
TREE left;
TREE right;
};
void tree_front(TREE& T)
{
char buff;
cout<<"please input ,or NULL(*)"<<endl;
cin>>buff;
if(buff!='*')
{
T=new struct treenode;
if(T==NULL)
exit(-1);
T->data=buff;
tree_front(T->left);
tree_front(T->right);
}
else
T=NULL;
}
int main()
{
TREE T;
tree_front(T);
return 0;
}
树建立起来
那么研究一下这个栈的调用过程
利用vs2008的反汇编窗口来看
1、进入main第一次调用函数
tree_front(T);
0084162E lea eax,[T]
00841631 push eax
00841632 call tree_front (8410B4h) //称为函数1
00841637 add esp,4
将T的地址传入函数
2、第一次进入函数1后
输入A,在在调用自身函数之前
tree_front(T->left);
0084156D mov eax,dword ptr [T]
00841570 mov ecx,dword ptr [eax]
00841572 add ecx,4
00841575 push ecx
00841576 call tree_front (8410B4h) //称为函数2
0084157B add esp,4
将A地址压栈
3、进入递归调用函数2
输入B,在在调用自身函数之前
tree_front(T->left);
0084156D mov eax,dword ptr [T]
00841570 mov ecx,dword ptr [eax]
00841572 add ecx,4
00841575 push ecx
00841576 call tree_front (8410B4h) //称为函数3
0084157B add esp,4
将B压栈
4、进入递归调用函数3
输入×,表示输入NULL
T=NULL;
00841591 mov eax,dword ptr [T]
00841594 mov dword ptr [eax],0
返回调用函数3的地方
执行下一条指令
0084157B add esp,4
就是出栈,将B出栈
准备进入右子树调用函数
5、进入递归右子树调用函数
tree_front(T->right);
0084157E mov eax,dword ptr [T]
00841581 mov ecx,dword ptr [eax]
00841583 add ecx,8
00841586 push ecx
00841587 call tree_front (8410B4h) //称为函数4
0084158C add esp,4
此时又将B压栈
6、进入递归调用函数4
输入×,表示输入NULL
T=NULL;
00841591 mov eax,dword ptr [T]
00841594 mov dword ptr [eax],0
返回函数4,执行下一条指令
0084158C add esp,4
将B出栈,这时,将从函数2出来,进入函数2的下一条右子树的调用
7、进入函数2的下一条右子树的调用
输入×,执行
T=NULL;
00841591 mov eax,dword ptr [T]
00841594 mov dword ptr [eax],0
然后退出函数,执行下一条指令、
。。。。
知道最后完成
用一个调用图来表示
分析一下这个栈的情况
不管是节点A还是节点B,在进入左子树时,都会将本节点的地址保存起来,以便返回
所以可以用vector容器来模拟这个压入过程
初始一些变量
struct treenode;
typedef struct treenode* TREE;
struct treenode
{
char data;
TREE left;
TREE right;
};
则原始非递归前序遍历的伪代码为
void preorder(TREE T)
{
while(1)
{
访问节点T;
压栈T;
T=T->left(T变成将左子树)
}
}
完成上图A到B的遍历,
B的做节点为NULL这时,应该pop出B节点
然后将B的右子树作为新的节点
然后修改伪代码
void preorder(TREE T)
{
while(1)
{
if(T!=NULL)
{
访问节点T;
压栈T;
T=T->left(T变成将左子树)
}
else
{
T=出栈;
T=T->right;
}
}
}
出栈就是出现在遍历到左子树为NULL时,根据最新入栈的节点,切换到右子树
右子树为NULL时回到父树的父树,然后变成,父树父树的右子树。
再考虑,while的结束条件,观察图中,A的右子树返回时,遍历结束,此时栈中不再包含A、B的节点,
修改伪代码
void preorder(TREE T)
{
初始化栈;
while(栈不为空)
{
if(T!=NULL)
{
访问节点T;
压栈T;
T=T->left(T变成将左子树)
}
else
{
T=出栈;
T=T->right;
}
}
}
条件还是有个bug,当A左子树返回时,栈就为空了,但是此时还要遍历A的右子树
看看整个树遍历时,栈的情况
栈空有三处,所以还要加上一个条件
这里有的变量就是节点,所以上次栈空时对应的节点情况
有效->有效->空(最后一个右子树)
而要退出是在最后一个栈空时,根据逻辑判断,两者相与
修改伪代码
void preorder(TREE T)
{
初始化栈;
while(栈不为空 || 节点不为NULL)
{
if(T!=NULL)
{
访问节点T;
压栈T;
T=T->left(T变成将左子树)
}
else
{
T=出栈;
T=T->right;
}
}
}
所以代码实现为
void preorder(TREE T)
{
vector <TREE> stack;
//vector <char> view;
while(T!=NULL || stack.size()!=0)
{
if(T!=NULL)
{
cout<<T->data<<endl;
stack.push_back(T);
//view.push_back(T->data);
T=T->left;
}
else
{
T=stack.back();
stack.pop_back();
// view.pop_back();
T = T->right;
}
}
}
整个测试代码为(包含递归遍历,作为比较)
#include <iostream>
#include <vector>
using namespace std;
struct treenode;
typedef struct treenode* TREE;
struct treenode
{
char data;
TREE left;
TREE right;
};
void tree_front(TREE& T)
{
char buff;
cout<<"please input ,or NULL(*)"<<endl;
cin>>buff;
if(buff!='*')
{
T=new struct treenode;
if(T==NULL)
exit(-1);
T->data=buff;
tree_front(T->left);
tree_front(T->right);
}
else
T=NULL;
}
void preorder(TREE T)
{
vector <TREE> stack;
vector <char> view;
while(T!=NULL || stack.size()!=0)
{
if(T!=NULL)
{
cout<<T->data<<endl;
stack.push_back(T);
view.push_back(T->data);
T=T->left;
}
else
{
T=stack.back();
stack.pop_back();
view.pop_back();
T = T->right;
}
}
}
void pre_compare(TREE T)
{
if(T!=NULL)
{
cout<<T->data<<endl;
pre_compare(T->left);
pre_compare(T->right);
}
}
int main()
{
TREE T;
tree_front(T);
preorder(T);
cout<<"follow is to compare"<<endl;
pre_compare(T);
return 0;
}