二叉树的前序、中序、后序遍历非递归实现
2015-12-17 16:31
148 查看
这是leetcode上的3个题目,要求用非递归实现,其中以后序遍历实现最难,既然递归实现的三种遍历程序只需要改变输入代码顺序,为什么循环不可以呢,带着这种执拗的想法,我开始了这次研究
我依然是将递归用栈来实现,而不打算使用改变二叉树结构的方法,那个我打算日后研究
首先以前序遍历为例
递归实现是:
利用循环和栈来实现递归
我的思路是每次循环对应一次函数调用,每次函数调用的root加入栈中,思考后,写出下面的程序
但是经测试发现,这段程序有个巨大的漏洞,以至于无法结束循环,问题出现在pop某节点后,返回到父节点,会再次将该节点push到栈中,从而无限循环
对照递归的实现,脑补计算机函数进栈出栈的抽象图,发现计算机的函数堆栈绝非是保存root的栈可以替代的,它还保存了代码的执行位置,也就是栈指针和帧指针。
我们能否用变量保存上次循环执行的代码位置呢,我们可以设置一些tag标记来模拟栈指针。但是,这里还有更好的实现方法。
我们可以保存上次循环结束时的root节点,其名lastRoot,
如果lastRoot = root->left,则说明root->left已经遍历过一遍,不用再将其加入栈中,也就是下面的代码段不用再执行
如果lastRoot = root->right,则说明root->left和root->right都已经遍历过一遍,可以直接将root出栈,下面的代码段不用执行
理解之后,不难写出代码,前序,中序,后续遍历的区别在于改变输出当前节点代码段的位置,同递归实现一样,只是顺序的区别,程序员就是这样懒,妄想一招鲜吃遍天下
另外还有其他实现方法,比如前序遍历,网上比较流行的方法是下面这种,这两种写法的思路是一样的,不过下面的要更简洁一些,虽然这种思路一开始我有些难以接受
不过还是要总结一下
1.选择一个root节点
2.将其左节点依次加入栈中
3.输出栈顶节点,弹出,然后将root设置为其右节点,重复1步骤
上面这些仅仅是程序的说明步骤,不能算是思路吧。不过要认真想一下的话,也许是跟人脑中遍历树的方式差不多,这里不做深入探讨。觉得麻烦的话,可以直接背上面总结的步骤。
另外一种实现思路,这次栈中仅仅保存右节点,因为是前序遍历,左节点可以直接输出,注意,这种方法仅能用于前序遍历
中序遍历其他方法(同前序遍历):
后序遍历只能使用保存上次root节点的或tag标记的方法
后记:算法的研究深不见底,你总能从中发现新的东西,越是研究的深入,就越会发现我们平时习惯了的人类思维方式是多么神奇!
我依然是将递归用栈来实现,而不打算使用改变二叉树结构的方法,那个我打算日后研究
首先以前序遍历为例
递归实现是:
void preorderTraversal(TreeNode* root) { if (root == nullptr) return; cout << root->val; preorderTraversal(root->left); preorderTraversal(root->right); }
利用循环和栈来实现递归
我的思路是每次循环对应一次函数调用,每次函数调用的root加入栈中,思考后,写出下面的程序
void preorderTraversal(TreeNode* root) { if (root == nullptr) return; stack<TreeNode*> sta; sta.push(root); while (!sta.empty()) { root = sta.top(); cout << root->val; if (root->left != nullptr) { sta.push(root->left); continue; } if (root->right != nullptr) { sta.push(root->right); continue; } sta.pop(); } }
但是经测试发现,这段程序有个巨大的漏洞,以至于无法结束循环,问题出现在pop某节点后,返回到父节点,会再次将该节点push到栈中,从而无限循环
对照递归的实现,脑补计算机函数进栈出栈的抽象图,发现计算机的函数堆栈绝非是保存root的栈可以替代的,它还保存了代码的执行位置,也就是栈指针和帧指针。
我们能否用变量保存上次循环执行的代码位置呢,我们可以设置一些tag标记来模拟栈指针。但是,这里还有更好的实现方法。
我们可以保存上次循环结束时的root节点,其名lastRoot,
如果lastRoot = root->left,则说明root->left已经遍历过一遍,不用再将其加入栈中,也就是下面的代码段不用再执行
if (root->left != nullptr) { sta.push(root->left); continue; }
如果lastRoot = root->right,则说明root->left和root->right都已经遍历过一遍,可以直接将root出栈,下面的代码段不用执行
if (root->left != nullptr) { sta.push(root->left); continue; }
if (root->right != nullptr) {
sta.push(root->right);
continue;
}
理解之后,不难写出代码,前序,中序,后续遍历的区别在于改变输出当前节点代码段的位置,同递归实现一样,只是顺序的区别,程序员就是这样懒,妄想一招鲜吃遍天下
// 前序遍历
vector<int> preorderTraversal(TreeNode* root) {
vector<int> out;
if (root == nullptr)
return out;
stack<TreeNode*> sta;
sta.push(root);
TreeNode* lastRoot = root;
while (!sta.empty())
{
root = sta.top();
if(lastRoot != root->right)
{
if (lastRoot != root->left) {
out.push_back(root->val);
if (root->left != nullptr) { sta.push(root->left); continue; }
}
if (root->right != nullptr) {
sta.push(root->right);
continue;
}
}
lastRoot = root;
sta.pop();
}
return out;
}
// 中序遍历
vector<int> inorderTraversal(TreeNode* root) {
vector<int> out;
if (root == nullptr)
return out;
stack<TreeNode*> sta;
sta.push(root);
TreeNode* lastRoot = root;
while (!sta.empty())
{
root = sta.top();
if (lastRoot != root->right)
{
if (lastRoot != root->left) {
if (root->left != nullptr) { sta.push(root->left); continue; }
}
out.push_back(root->val);
if (root->right != nullptr) {
sta.push(root->right);
continue;
}
}
lastRoot = root;
sta.pop();
}
return out;
}
// 后序遍历
vector<int> postorderTraversal(TreeNode* root) {
vector<int> out;
if (root == nullptr)
return out;
stack<TreeNode*> sta;
sta.push(root);
TreeNode* lastRoot = root;
while (!sta.empty())
{
root = sta.top();
if (lastRoot != root->right)
{
if (lastRoot != root->left) {
if (root->left != nullptr) { sta.push(root->left); continue; }
}
if (root->right != nullptr) {
sta.push(root->right);
continue;
}
}
out.push_back(root->val);
lastRoot = root;
sta.pop();
}
return out;
}
另外还有其他实现方法,比如前序遍历,网上比较流行的方法是下面这种,这两种写法的思路是一样的,不过下面的要更简洁一些,虽然这种思路一开始我有些难以接受
不过还是要总结一下
1.选择一个root节点
2.将其左节点依次加入栈中
3.输出栈顶节点,弹出,然后将root设置为其右节点,重复1步骤
上面这些仅仅是程序的说明步骤,不能算是思路吧。不过要认真想一下的话,也许是跟人脑中遍历树的方式差不多,这里不做深入探讨。觉得麻烦的话,可以直接背上面总结的步骤。
vector<int> preorderTraversal(TreeNode* root) { stack<TreeNode*> sta; vector<int> out; while (root || !sta.empty()) { while (root) { sta.push(root); out.push_back(root->val); root = root->left; } if (!sta.empty()) { root = sta.top(); sta.pop(); root = root->right; } } return out; } // 另一种写法 vector<int> preorderTraversal(TreeNode* root) { stack<TreeNode*> sta; vector<int> out; while (root || !sta.empty()) { if(root) { sta.push(root); out.push_back(root->val); root = root->left; } else { root = sta.top(); sta.pop(); root = root->right; } } return out; }
另外一种实现思路,这次栈中仅仅保存右节点,因为是前序遍历,左节点可以直接输出,注意,这种方法仅能用于前序遍历
vector<int> preorderTraversal(TreeNode* root) { vector<int> out; if (root == nullptr) return out; stack<TreeNode*> sta; sta.push(root); while (!sta.empty()) { out.push_back(root->val); if (root->right) sta.push(root->right); if (root->left) root = root->left; else { root = sta.top(); sta.pop(); } } return out; }
中序遍历其他方法(同前序遍历):
vector<int> inorderTraversal(TreeNode* root) { stack<TreeNode*> sta; vector<int> out; TreeNode* cur = root; while (cur != nullptr || !sta.empty()) { if (cur != nullptr) { sta.push(cur); cur = cur->left; } else { cur = sta.top(); sta.pop(); out.push_back(cur->val); cur = cur->right; } } return out; }
后序遍历只能使用保存上次root节点的或tag标记的方法
后记:算法的研究深不见底,你总能从中发现新的东西,越是研究的深入,就越会发现我们平时习惯了的人类思维方式是多么神奇!
相关文章推荐
- php \r \n 和 <br/> \t
- mysql and or
- 在CentOS上升级把Nginx
- C++常用的#include头文件总结
- 排序算法,堆算法实现TopK
- 初识贝叶斯网络
- H-L-S
- 关于分布式事务、两阶段提交协议、三阶提交协议
- 双向Dijstra算法
- maven 定义profile.properties
- JAVA IO (一) 基础深入理解
- [置顶] DIV+CSS 实现背景透明,文字不透明
- 机器人路径规划_遗传算法
- jQuery滚动显示图片列表
- 遇到的问题-----you can't add a second 'treePath' criteria. Query already contains
- mysql hash 索引
- 如何在应用和设备间共享数据之一共享简单的数据之接收
- js中数组indexOf
- 遇到的问题-----you can't add a second 'treePath' criteria. Query already contains
- 再学贝叶斯网络--TAN树型朴素贝叶斯算法