使用状态机算法简单解决二叉树非递归遍历
今天我本来是准备复习一下数据结构的。看到二叉树的遍历之后,想着老师好像讲过一种不用递归的遍历方法,于是就想自己写一下。写了一小时发现不对劲,去网上找了篇博客看看,妈耶这也太复杂了吧,硬着头皮写完前序遍历和中序遍历,一个比一个复杂。。。然后我灵机一动,想到用状态机,发现效果出奇的好,简简单单搞定了。下面是思路和分析。
树的定义
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};
先看看使用递归的方法
void funL(TreeNode* t){
if(t==NULL)return;
cout<<t->val<<" ";
funL(t->left);
funL(t->right);
}
简简单单,轻轻松松,甚至就跟定义一样长。。
然后尝试一下非递归方法,发现,在处理一个节点的时候,其实是没办法知道这个节点已经被怎么处理了。对先序遍历而言,应该是先输出,左节点入栈,右节点入栈。但是对取出的节点不能判断已经对它操作到了那一步。
通常的做法是,循环嵌套,进行左节点入栈时让他循环直到左节点为空,然后寻找可以遍历的右节点这样。。。每个步骤放在一个循环里,循环套循环这样子。。对我来说有点太难了。。。放弃。
//中序遍历
void funM(TreeNode *t){
if(t==NULL)return;
stack<TreeNode*> s;
s.push(t);
while(!s.empty()){
TreeNode *temp=s.top();s.pop();
if(temp==NULL)continue;
while(temp!=NULL){
s.push(temp);
temp=temp->left;
}
while(!s.empty()){
temp=s.top();s.pop();
cout<<temp->val<<" ";
if(temp->right!=NULL){
s.push(temp->right);
break;
}
}
}
cout<<endl;
}
回头想一下,为什么用递归就这么简单呢?我觉得主要在于,在这个递归使用时,其实不仅仅把参数传进去了,而且当前执行到的位置也被保存了,这样其实就是多了一些状态信息(知道已经执行到那一步了),知道现在在哪一步当然就不用那么麻烦了。
等等,状态?那就状态机走一波?加一个状态栈,保存现在执行到的状态。运行时可以根据该节点状态就可以知道要执行什么步骤了,因此只要来一个循环不断判断即可。状态机的状态定义如下:
状态1:待左遍历,如果左节点非空,左节点入栈,转到2状态
状态2:待右遍历,如果右节点非空,右节点出栈,转到3状态
状态3:待输出,输出该节点,出栈
初始状态为1
void funR(TreeNode *t){
stack<TreeNode *> data;
stack<int> sign;//保存状态
sign.push(1);data.push(t);
while(!data.empty()){
int nowSign = sign.top();
TreeNode *nowNode = data.top();
// printf("sign=%d,val=%d\n",nowSign,nowNode->val);
switch(nowSign){
case 1:
sign.pop();sign.push(2);
if(nowNode->left==NULL)break;
sign.push(1);
data.push(nowNode->left);
break;
case 2:
sign.pop();sign.push(3);
if(nowNode->right==NULL)break;
sign.push(1);
data.push(nowNode->right);
break;
case 3:
cout<<nowNode->val<<" ";
sign.pop();
data.pop();
break;
}
}
}
看起来挺多,其实也就是状态转换,只要写每个状态转换时的步骤即可。思想挺简单的。(起码比那篇博客的纯迭代容易理解多了。。)
写完手工!附上参考的那篇博客,其实写的挺好的,很容易就看明白了。