二叉树 前序 中序 建树 迭代非递归

这里是对力扣题目力扣官方解答中方法二的解释,我认为官方解答可能不太好理解,在这里做一个笔记,供之后回忆时查看。

class Solution {
public:
    TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
        if (!preorder.size()) {
            return nullptr;
        }
        TreeNode* root = new TreeNode(preorder[0]);
        stack<TreeNode*> stk;
        stk.push(root);
        int inorderIndex = 0;
        for (int i = 1; i < preorder.size(); ++i) {
            int preorderVal = preorder[i];
            TreeNode* node = stk.top();
            if (node->val != inorder[inorderIndex]) {
                node->left = new TreeNode(preorderVal);
                stk.push(node->left);
            }
            else {
                while (!stk.empty() && stk.top()->val == inorder[inorderIndex]) {
                    node = stk.top();
                    stk.pop();
                    ++inorderIndex;
                }
                node->right = new TreeNode(preorderVal);
                stk.push(node->right);
            }
        }
        return root;
    }
};

作者:LeetCode-Solution
链接:https://leetcode.cn/problems/zhong-jian-er-cha-shu-lcof/solution/mian-shi-ti-07-zhong-jian-er-cha-shu-by-leetcode-s/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

 

为什么我们需要 首先 得到下面这个结论?


在先序遍历的序列中,如果有前后相邻的两个结点u、v,那么有且仅有下面三种关系:


1. u有左子,那么v是u的左子
2. u没有左子,有右子,那么v是u的右子
3. u没有左子,没有右子,那么u是一个叶子结点,所以访问u结束后意味着u所在的左子树访问结束,v是这个左子树的祖先的右子

综上,有两种关系:


1. v是u的左子
2. v是u或者u的某个祖先的右子 

这肯定是因为这是方法二的基础和关键理论,简称理论基础。

那么,一句话概括,方法二是怎么从先序、中序序列中构造出二叉树的呢?

简单来说就是由于二叉树中除了根节点外,每个结点都有祖先结点,或者是某个结点的左子,或者是其右子。然后找到这些有父节点的结点的父节点是哪个,建立起来这个关系。

然而,先序顺序中第一个元素就是没有祖先的根节点,所以通过从下标1开始遍历先序顺序,就是遍历那些需要祖先结点的结点,只需要在遍历这些结点的过程中 

  1. 创建结点;
  2. 找到其祖先结点,也就是让某个结点的左子或者右子指向它

所以我们一开始需要明白先序顺序中相邻的两个结点的关系。

接下来我们解释for循环中的判断:

  • 为什么栈顶结点不等于index指向结点时,i (从1开始遍历先序序列的指针)指向的结点是栈顶结点的左子并入栈?
  • 为什么栈顶结点等于index指向的结点时,需要弹栈并且右移index,并且 i 指向的结点时最右一个弹出的结点的右子?

简单来说就是,这是stack和index的定义决定的,并且维持这样一个属性:index指向的元素是栈顶元素的的子树中序遍历的第一个元素,这暗含这另一个属性,就是index没有左子。

比如在起始时,根节点入栈并且index指向中序的第一个元素,这是满足我们上面这个属性的。

下面我们解释这个属性何时被破坏以及如何维护它。

如果栈顶元素不等于index指向的元素,那么index指向的元素在栈顶元素的左子中。

这是因为中序遍历的第一个元素是”最左的元素“,也就是没有左子的最高结点。如果根节点没有左子,那么根节点就是中序遍历的第一个结点。反过来说,当根节点不是中序遍历的第一个结点,那么根节点有左子,并且index指向的元素在左子树中。所以更新根节点为当前根节点的左子。这就暗含一个关系,栈顶元素有左子。

并且,根据首先得到的结论,如果一个元素有左子,那么其左子在先序中排在其后并且相邻,这就回答了我们上面提到的第一个为什么。

在上面入栈的过程中,上面提到的属性没有被破坏,直到栈顶元素等于index指向的元素(仍未被破坏),因为index指向的元素仍是栈顶元素的子树中序遍历的第一个元素。

由于index指向的元素是没有左子的,所以此时某个左子树被建立完毕,这个左子树目前来看就是栈顶元素本身作为根节点的左子树,但是我们还可以进一步探寻同时被建立完成的左子树,因为当栈顶元素的祖先没有右子,那么这个祖先为根节点的左子树也被建立完毕。

所以我们需要找到最高的右子没有被访问的结点,这个结点是栈中的某个结点,并且 i 是这个结点的右子。

由于中序顺序的特点,如果uv相邻,那么v是u的祖先或者u的右子树的第一个中序结点,这两种情况是对立事件,意味着如果一个不成立,那么一定是另一种情况。

此时栈顶元素是等于index指向的元素的,再同时右移index和弹栈。如果栈顶元素和index指向的元素相同,那么index此时指向的元素是上一个元素的祖先,并且上一个index指向的元素没有右子(由上面提到的对立事件)。所以被构造完毕的子树又高了一层,如此右移index和弹栈,直到栈被弹空或者两者元素不相等。这时有 i 指向的元素是最后一个弹出的元素的右子。

在弹栈和index右移的过程中,我们上面提到的属性显然被破坏。

根据我们上面的分析,我们找到了被完全建立的左子树,并且找到这个左子树的父节点。现在的index指向这个父节点的右子树中序第一个结点,并且我们把新建立的结点入栈。如此上面的属性重新被维护。

所以下一次进入循环的时候,对if的第一个条件内的操作仍然成立。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,以下是C++数据结构树的二叉树前序中序和后序遍历的递归和非递归实现代码。 ```c++ // 定义二叉树的结构体 struct TreeNode { int val; TreeNode* left; TreeNode* right; TreeNode(int x) : val(x), left(NULL), right(NULL) {} }; // 二叉树前序遍历(递归)代码 void preorderTraversal(TreeNode* root) { if (root == NULL) return; cout << root->val << " "; // 输出当前节点的值 preorderTraversal(root->left); // 递归遍历左子树 preorderTraversal(root->right); // 递归遍历右子树 } // 二叉树前序遍历(非递归)代码 void preorderTraversal(TreeNode* root) { if (root == NULL) return; stack<TreeNode*> s; // 定义栈 s.push(root); // 根节点入栈 while (!s.empty()) { TreeNode* cur = s.top(); // 取出栈顶元素 s.pop(); cout << cur->val << " "; // 输出当前节点的值 if (cur->right != NULL) s.push(cur->right); // 先将右子树入栈 if (cur->left != NULL) s.push(cur->left); // 再将左子树入栈 } } // 二叉树中序遍历(递归)代码 void inorderTraversal(TreeNode* root) { if (root == NULL) return; inorderTraversal(root->left); // 递归遍历左子树 cout << root->val << " "; // 输出当前节点的值 inorderTraversal(root->right); // 递归遍历右子树 } // 二叉树中序遍历(非递归)代码 void inorderTraversal(TreeNode* root) { if (root == NULL) return; stack<TreeNode*> s; // 定义栈 TreeNode* cur = root; while (cur != NULL || !s.empty()) { while (cur != NULL) { // 将当前节点及其左子树入栈 s.push(cur); cur = cur->left; } cur = s.top(); // 取出栈顶元素 s.pop(); cout << cur->val << " "; // 输出当前节点的值 cur = cur->right; // 遍历右子树 } } // 二叉树后序遍历(递归)代码 void postorderTraversal(TreeNode* root) { if (root == NULL) return; postorderTraversal(root->left); // 递归遍历左子树 postorderTraversal(root->right); // 递归遍历右子树 cout << root->val << " "; // 输出当前节点的值 } // 二叉树后序遍历(非递归)代码 void postorderTraversal(TreeNode* root) { if (root == NULL) return; stack<TreeNode*> s1, s2; // 定义两个栈 s1.push(root); // 根节点入栈 while (!s1.empty()) { TreeNode* cur = s1.top(); // 取出栈顶元素 s1.pop(); s2.push(cur); // 将栈顶元素存入第二个栈 if (cur->left != NULL) s1.push(cur->left); // 将左子树入栈 if (cur->right != NULL) s1.push(cur->right); // 将右子树入栈 } while (!s2.empty()) { cout << s2.top()->val << " "; // 输出第二个栈的元素 s2.pop(); } } ``` 希望这些代码可以帮助到您。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值