94. 二叉树的中序遍历

94. 二叉树的中序遍历 - 力扣(LeetCode)

二刷

class Solution {
public:
  vector<int> inorderTraversal(TreeNode* root) {
    vector<int> Result;
    stack<TreeNode *> Stack;

    while (root || !Stack.empty()) {
      while (root) {
        Stack.push(root);
        root = root->left;
      }
      root = Stack.top();
      Stack.pop();
      Result.push_back(root->val);
      root = root->right;
    }

    return Result;
  }
};

说实话这个迭代真不好想吧,感觉纯粹就是凑出来的,丑得要命,只能硬记了,而且说不定还没什么大用。还是递归好。

三刷

class Solution {
public:
  vector<int> inorderTraversal(TreeNode* root) {
    vector<int> Result;
    stack<TreeNode *> S;

    // These two while conditions correspond to:
    // if (!root)
    //   return;
    // And also the IMPLICIT return statement at the end of the function.
    while (!S.empty() || root) {
      while (root) {
        // Traverse(root->left);
        S.push(root);
        root = root->left;
      }
      // Visit(root); Note that this is basically a BACKTRACK.
      root = S.top();
      S.pop();
      Result.push_back(root->val);
      // Traverse(root->right);
      root = root->right;
    }

    return Result;
  }
};

其实也不难理解。这两层 while 循环共同构成了两个不动点——一个是递归方法下函数开头的判空,另一个是访问完子节点之后的退栈。理解了这一点,就可以理解为什么我们要用这么别扭的写法了。

此外,也可以根据上面注释里给出的提示,对迭代法的代码进行对比记忆。

四刷

class Solution {
public:
  vector<int> inorderTraversal(TreeNode* root) {
    vector<int> Ret;
    stack<TreeNode *> S;
    // The predicative looks like this because the root IS NOT PRESUMABLY NON-
    // NULL, thus we need to perform a double-check: one for the root, another
    // for the stack.
    while (root != nullptr || !S.empty()) {
      // The fixed point in every tree traversal algorithm is always `!root`,
      // instead of `!root->left` or `!root->right`.
      while (root != nullptr) {
        S.push(root);
        root = root->left;
      }
	  // else {
      // After the while loop above, root will always be nullptr; so we shall
      // get the root IN THE PREVIOUS LAYER through the stack.
      root = S.top();
      Ret.emplace_back(root->val);
      S.pop();
      // Now we're safe to traverse the right part.
      root = root->right;
      // }
    }
    return Ret;
  }
};

记住树遍历的不动点永远是root == nullptr而不是root->left == nullptr && root->right == nullptr之后,这题的S.push(root);就容易理解很多——push进去的东西永远是符合不动点规则的。

然后你也可以接着理解为什么我们的两层while循环的谓词都是root != nullptr,这也是受到不动点的影响。

此外,还要注意每个while (pred) { body; }后面都有一个else { body; },这样就能看清楚很多隐含条件。