刷题的时候,这玩意肯定会遇到的,而且一般是初学者的一个大坑。
递归法很简单,迭代法第一次看还看不出个所以然,得看了几次,加上背算法细节,记忆,才可以,理解了再去记忆,几乎不可能,那就死记硬背。。。
我也是这个过程。
今天又重点看了下这个算法,通过别人的讲解有点启发,一下子就写出来三种遍历方法,有了自己的一套方便记忆的方法,算是开窍了。
个人还是比较推荐 leecode 上面的官方解法,不要去迷信什么统一的结构,什么代码随想录上的,个人感觉太复杂了。官方解法比较简洁,但是有点难理解而已。。
总的原则是:
- 前序 和 后序 结构是相同的,先写出前序,稍微改一下就是后序,所以,只要记忆 前序 即可。
- 中序 遍历和前后不一样,需要单独记忆
理论
理论上说,如何把递归转化为迭代,常规操作就是使用 stack, stack 的先进后出 的特性天然就拥有递归的性质,也是比较神奇的。所以,后面的算法中都是使用的 stack。
前序遍历:
口诀
口诀:先输出本省,有孩子,先压右边,再压左边
记住口诀就行
vector<int> pre_order(Node *head)
{
stack<int> st;
vector<int> res;
st.push(head);
while (!st.empty()) {
// 先输出中间
Node *node = st.top();
res.push_back(node->value);
st.pop();
// 先压右节点
if (node->right) {
st.push(node->right);
}
// 再压左节点
if (node->left) {
st.push(node->left);
}
}
return res;
}
解释
先输出中间的,很好理解,就是前序遍历嘛
先压右边,再压左边,也很好理解,栈嘛,后压的先出来,也就是 左边的先出来。
后序遍历
口诀
针对 前序改巴改巴就行,几个修改点,记住:
- 先右再左,改成,先左后右
- 原来输出的地方,改成输出到一个临时 stack 中,【因为需要逆序一下】
- 最后,逆序输出 stack 到 res 数组中即可
你说原理是啥,我也搞不懂,反正按照口诀来该就对了。
vector<int> postorderTraversal(TreeNode* root) {
stack<TreeNode*> st;
vector<int> res;
stack<int> tmp;
TreeNode *node;
if (!root) {
return res;
}
st.push(root);
while (!st.empty()) {
node = st.top();
// 原先输出的地方,改成输出到一个 临时的 stack 中,为了逆序输出
//res.push_back(node->val);
tmp.push(node->val);
st.pop();
// 改成,先左后右
if (node->left) {
st.push(node->left);
}
if (node->right) {
st.push(node->right);
}
}
int val;
while (!tmp.empty()) {
val = tmp.top();
res.push_back(val);
tmp.pop();
}
return res;
}
解释
没啥好解释的,我也不懂:)
中序遍历
中序遍历是另一套结构了,
口诀
口诀:左边一路走到黑,不断试探右边
感觉就和深度优先的思想差不多了,自己慢慢体会吧,脑子里一直是这张图:
vector<int> inorderTraversal(TreeNode* root) {
stack<TreeNode*> st;
vector<int> res;
TreeNode* temp;
temp = root;
while (temp || !st.empty()) {
if (temp) {
// 左边一路走到底
st.push(temp);
temp = temp->left;
} else {
// 走不通了,尝试右边
TreeNode *node = st.top();
res.push_back(node->val);
st.pop();
temp = node->right;
}
}
return res;
}
解释
其实还是挺容易理解的,左边就深度优先,到底,然后走不通了,退回来,一旦发现右子树有值了,继续在右子树上进行深度优先。