Day14算法训练营|二叉树

树的定义

树的抽象数据类型

树的存储结构

二叉树的定义

二叉树的性质

二叉树的存储结构

遍历二叉树

二叉树的建立

线索二叉树

树、森林与二叉树的转换

赫夫曼树

总结

递归

二叉树的存储

// 定义一个二叉树节点的结构体TreeNode
struct TreeNode {
    int val;    // 节点值
    TreeNode *left; // 指向左子树的指针
    TreeNode *right;  // 指向右子树的指针
    TreeNode(int x) : val(x), left(NULL), right(NULL) {} // 构造函数初始化
};

二叉树的递归遍历

写递归算法时,需要注意的点(1)确定递归函数的参数和返回值;(2)确定终止条件;(3)确定单层递归的逻辑。

// 前序遍历
class Solution {
public:
    void traversal(TreeNode* cur, vector<int>& vec) {
        if (cur == NULL) return;
        vec.push_back(cur->val);    // 中
        traversal(cur->left, vec);  // 左
        traversal(cur->right, vec); // 右
    }
    vector<int> preorderTraversal(TreeNode* root) {
        vector<int> result;
        traversal(root, result);
        return result;
    }
};
//TreeNode* root,这是一个指向 TreeNode 类型对象的指针。在函数调用时,传递的是指向二叉树根节点的指针,表示从根节点开始进行先序遍历。

一次二叉树的递归调用过程:

当调用 `traversal(root, result)` 时,实际上触发了一个递归的过程,该过程会遍历二叉树并将节点的值加入到 `result` 中。让我用一个简单的例子来说明递归遍历的过程。

假设有以下二叉树的结构:

```
      1
     / \
    2   3
   / \
  4   5
```

对应的 `TreeNode` 结构可能如下:

```cpp
struct TreeNode {
    int val;
    TreeNode* left;
    TreeNode* right;
    TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};
```

现在,我们调用 `traversal(root, result)`,其中 `root` 指向根节点。递归过程如下:

1. **第一次调用:** `traversal(root, result)`,其中 `root` 指向节点 1。
   - 将节点 1 的值(1)加入 `result` 中。
   - 调用 `traversal(root->left, result)`,即 `traversal` 函数的第二个递归调用,其中 `root` 指向节点 2。

2. **第二次调用:** `traversal(root, result)`,其中 `root` 指向节点 2。
   - 将节点 2 的值(2)加入 `result` 中。
   - 调用 `traversal(root->left, result)`,即 `traversal` 函数的第三个递归调用,其中 `root` 指向节点 4。

3. **第三次调用:** `traversal(root, result)`,其中 `root` 指向节点 4。
   - 将节点 4 的值(4)加入 `result` 中。
   - 节点 4 为叶子节点,没有左右子树,递归结束。

4. **返回到第二次调用:** 返回到节点 2 的递归调用,调用 `traversal(root->right, result)`,即 `traversal` 函数的第四个递归调用,其中 `root` 指向节点 5。

5. **第四次调用:** `traversal(root, result)`,其中 `root` 指向节点 5。
   - 将节点 5 的值(5)加入 `result` 中。
   - 节点 5 为叶子节点,没有左右子树,递归结束。

6. **返回到第二次调用:** 返回到节点 2 的递归调用,节点 2 的左右子树都已经遍历完毕,递归结束。

7. **返回到第一次调用:** 返回到节点 1 的递归调用,调用 `traversal(root->right, result)`,即 `traversal` 函数的第五个递归调用,其中 `root` 指向节点 3。

8. **第五次调用:** `traversal(root, result)`,其中 `root` 指向节点 3。
   - 将节点 3 的值(3)加入 `result` 中。
   - 调用 `traversal(root->left, result)`,即 `traversal` 函数的第六个递归调用,其中 `root` 指向 `NULL`(因为节点 3 的左子树为空)。

9. **第六次调用:** `traversal(root, result)`,其中 `root` 指向 `NULL`。
   - 由于 `root` 为 `NULL`,递归直接结束。

10. **返回到第五次调用:** 返回到节点 3 的递归调用,调用 `traversal(root->right, result)`,即 `traversal` 函数的第七个递归调用,其中 `root` 指向 `NULL`(因为节点 3 的右子树为空)。

11. **第七次调用:** `traversal(root, result)`,其中 `root` 指向 `NULL`。
    - 由于 `root` 为 `NULL`,递归直接结束。

12. **返回到第五次调用:** 返回到节点 3 的递归调用,节点 3 的左右子树都已经遍历完毕,递归结束。

13. **返回到第一次调用:** 返回到节点 1 的递归调用,节点 1 的左右子树都已经遍历完毕,递归结束。

最终,`result` 中的值为 `[1, 2, 4, 5, 3]`,即二叉树的先序遍历结果。

递归并不会结束,只会返回上一层,因为其是基于栈构建的

在递归调用中,当遇到叶子节点时执行 `return` 语句,表示当前递归分支的结束。这个 `return` 语句会将控制权返回到调用该递归的上一层。如果递归发生在函数的最后一行,那么函数调用会结束,控制权将返回到调用方。

在先序遍历的例子中,递归调用 `traversal` 函数是在处理每个节点的过程中,其中包括左子树的递归调用和右子树的递归调用。当遇到叶子节点时,递归分支会通过 `return` 结束,但这只是结束了当前分支的递归调用,而不是整个程序的执行。

在递归的过程中,程序会回溯到之前的调用点,继续执行后续的代码。这是因为递归是通过调用栈(call stack)来管理的,每次函数调用都会在栈上创建一个新的帧(frame)。当递归分支结束时,栈帧会被弹出,控制权返回到上一层的调用点。

具体来说,在先序遍历的例子中,当递归调用结束时,程序会返回到上一层的递归调用点,继续执行那个调用点之后的代码。这个过程会一直持续,直到栈上的所有帧都被弹出,整个程序执行完毕。

//后序遍历
class Solution{
public:
    void traversal(TreeNode* node, vector<int>& vec)
    {
        if (node == NULL) return;
        traversal(node->left, vec);
        traversal(node->right, vec); // right
        vec.push_back(node->val);
    }
    vector<int> postorderTraversal(TreeNode* root)
    {
        vector<int> result;
        traversal(root,result);
        return result;
    }
};

中序:


class Solution
{
public:
    void Traversal(TreeNode* cur,vector<int>& vec)
    {
        if(cur == NULL) return;
        Traversal(cur->left,vec);
        vec.push_back(cur->val);
        Traversal(cur->right,vec);
    }
    vector<int> inorderTraversal(TreeNode* root)
    {
        vector<int> result;
        Traversal(root,result);
        return result;
    }
};

二叉树的迭代遍历

两个点:访问结点(获取该处数据),处理结点(将结点放至数组当中)

前序

其是按顺序走的,先访问中节点,先处理的也是中节点

class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) {
       stack<TreeNode*> st;
       vector<int> result;
       if (root == NULL) return result;
       st.push(root);
       while (!st.empty()) {
           TreeNode* node = st.top(); // 取栈顶元素,中
           st.pop();
           result.push_back(node->val);
           if (node->right) st.push(node->right); // 先将右子树入栈
           if (node->left) st.push(node->left);   // 再将左子树入栈
       }
       return result;
    }
};

后序(前序颠倒)

前序是中左右,后序遍历是左右中,所以实施过程,中右左,那反过来就是左右中了

class Solution{
public:
    vector<int> postorderTraversal(TreeNode* root)
    {
        vector<int> result;
        stack<TreeNode*> st;
        if(root == NULL) return result;
        st.push(root);
        while(!st.empty())
        {
            TreeNode* node = st.top();
            st.pop();
            result.push_back(node->val);
            if(node->left) st.push(node->left);
            if(node->right) st.push(node->right);
        }
        reverse(result.begin(),result.end());
        return result;
    }
}

中序先访问根节点,但先处理的是左子树的左叶子,访问的顺序和处理的顺序不一样)

用指针遍历元素(这里相比于前序遍历就多了指针,前序遍历没有),用栈来处理元素(需要多看一下视频)

class Solution {
public:
    vector<int> inorderTraversal(TreeNode* root) {
        vector<int> result;
        stack<TreeNode*> st;
        TreeNode* cur = root;
        while (cur != NULL || !st.empty()) {
            if (cur != NULL) { // 指针来访问节点,访问到最底层
                st.push(cur); // 将访问的节点放进栈
                cur = cur->left;                // 左
            } else {
                cur = st.top(); // 从栈里弹出的数据,就是要处理的数据(放进result数组里的数据)
                st.pop();
                result.push_back(cur->val);     // 中
                cur = cur->right;               // 右
            }
        }
        return result;
    }
};

代码一定要天天敲,多敲几遍,加油

递归遍历是维护一个隐式的栈

迭代遍历是维护一个显式的栈

二叉树的统一遍历

统一迭代法很难理解,在这里不做赘述,后续熟练了可以考虑进一步掌握

二叉树的层序遍历

利用队列实现,逐层遍历

class Solution
{
 public:
    vector<vector<int>> levelorder(TreeNode* root)
    {
        vector<vector<int>> result;
        queue<TreeNode*> que;
        if(root != NULL) que.push(root);
        
        while(!que.empty())
        {
            int size = que.size();
            vector<int> vec;
            for(int i = 0; i < size; i++)
            {
                TreeNode* node = que.front();
                que.pop();
                vec.push_back(node->val);
                if(node->left) que.push(node->left);
                if(node->right) que.push(node->right);
            }
            result.push_back(vec);
        }
        return result;
        
        
    }
}

  • 28
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
代码随想录算法训练营是一个优质的学习和讨论平台,提供了丰富的算法训练内容和讨论交流机会。在训练营中,学员们可以通过观看视频讲解来学习算法知识,并根据讲解内容进行刷题练习。此外,训练营还提供了刷题建议,例如先看视频、了解自己所使用的编程语言、使用日志等方法来提高刷题效果和语言掌握程度。 训练营中的讨论内容非常丰富,涵盖了各种算法知识点和解题方法。例如,在第14天的训练营中,讲解了二叉树的理论基础、递归遍历、迭代遍历和统一遍历的内容。此外,在讨论中还分享了相关的博客文章和配图,帮助学员更好地理解和掌握二叉树的遍历方法。 训练营还提供了每日的讨论知识点,例如在第15天的讨论中,介绍了层序遍历的方法和使用队列来模拟一层一层遍历的效果。在第16天的讨论中,重点讨论了如何进行调试(debug)的方法,认为掌握调试技巧可以帮助学员更好地解决问题和写出正确的算法代码。 总之,代码随想录算法训练营是一个提供优质学习和讨论环境的平台,可以帮助学员系统地学习算法知识,并提供了丰富的讨论内容和刷题建议来提高算法编程能力。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [代码随想录算法训练营每日精华](https://blog.csdn.net/weixin_38556197/article/details/128462133)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值