6 条题解

  • 0
    @ 2025-12-4 16:32:53

    递归写法遍历会有可能MLE(内存溢出),请解释原因

    你好!这是一个非常深刻且在竞赛中至关重要的问题。你的代码逻辑是完全正确的,但在某些极端的测试数据下,它确实有可能导致 MLE (Memory Limit Exceeded)RE (Runtime Error),而这背后的元凶就是——递归栈溢出


    1. 什么情况下会 MLE / RE (栈溢出)?

    首先,要明确一点,这里的 MLE 和我们通常说的“数组开太大”导致的 MLE 不太一样。递归调用函数时,系统会使用一个叫做**“调用栈 (Call Stack)”的内存区域来存储函数的状态(如参数、局部变量、返回地址等)。这个栈的大小是有限的**。

    当递归的深度过大时,调用栈就会被耗尽,导致“栈溢出 (Stack Overflow)”。

    在不同的操作系统和评测姬上,栈溢出可能会表现为:

    • RE (Runtime Error):最常见,程序因为非法内存访问而崩溃。
    • MLE (Memory Limit Exceeded):如果评测系统将栈空间也算入总内存使用,并且栈空间在耗尽前超过了内存限制。

    对于你的代码,什么情况会导致递归深度过大?

    想象一下,如果测试数据构造出一棵极度不平衡的树,特别是一条长链1 -> 2 -> 3 -> 4 -> ... -> n

    当你的 preorder(1) 被调用时,它会调用 preorder(2),然后 preorder(2) 调用 preorder(3)……一直调用到 preorder(n)

    这时,你的调用栈里会同时存在 npreorder 函数的调用帧 (Call Frame):

    [ preorder(n)   ] <--- 栈顶
    [ preorder(n-1) ]
    [ ...           ]
    [ preorder(2)   ]
    [ preorder(1)   ] <--- 栈底
    

    如果 n 的值很大(比如 10510^510610^6),而系统默认的栈空间不够大,就会导致栈溢出。

    对于这道题,n26n \le 26,递归深度最多 26 层,是绝对安全的。但如果题目数据范围改成 n105n \le 10^5,你的代码就非常危险了。


    2. 应该怎么改造?—— “非递归”实现

    要从根本上解决栈溢出的问题,我们就必须把递归算法改造成迭代 (Iterative) 的,也就是非递归的。

    核心思想是:用我们自己手动管理的栈(std::stack)来模拟系统的调用栈。

    下面以前序遍历为例,讲解如何改造。

    a. 前序遍历 (根-左-右) 的非递归改造

    1. 创建一个 std::stack<int>
    2. 先把根节点 root 压入栈。
    3. 当栈不为空时,循环: a. 弹出栈顶节点 curr。 b. 访问(打印)curr。 c. 关键:先把 curr右孩子压入栈(如果存在)。 d. 再把 curr左孩子压入栈(如果存在)。

    为什么先压右再压左? 因为栈是“后进先出 (LIFO)”的。我们后压入的左孩子会先被弹出处理,这恰好符合“根-左-右”的顺序。

    b. 中序遍历 (左-根-右) 的非递归改造

    中序遍历稍微复杂一点,因为它需要先一路向左走到尽头。

    1. 创建一个 std::stack<int>
    2. 创建一个指针(或索引)curr 指向根节点。
    3. curr 不为空或栈不为空时,循环: a. 如果 curr 不为空: i. 把 curr 压入栈。 ii. curr 移动到它的左孩子 (curr = tree[curr].l)。 b. 如果 curr 为空(说明左边走到头了): i. 从栈顶弹出一个节点,记为 node。 ii. 访问(打印)node。 iii. curr 移动到 node 的右孩子 (curr = tree[node].r),准备处理右子树。

    c. 后序遍历 (左-右-根) 的非递归改造

    后序遍历是最复杂的,因为它要求在左右子树都访问完之后才能访问根。

    • 技巧一(双栈法)
      1. 用一个栈 s1,按前序遍历的逻辑(根-右-左)遍历,但不是打印,而是把弹出的节点压入另一个栈 s2
      2. 遍历结束后,s2 中节点的顺序就是后序遍历的逆序。依次弹出 s2 并打印即可。
    • 技巧二(单栈法 + 标记)
      1. 用一个栈,一个指针 curr,一个变量 last_visited 记录上一个访问的节点。
      2. 逻辑比较复杂,需要判断当前栈顶节点的右孩子是否已经被访问过,来决定是深入右子树还是访问当前节点。

    3. 改造后的代码示例

    这里给出前序和中序的非递归改造代码,后序通常用递归写最简单,非递归实现较为复杂,但在竞赛中不常用。

    #include <iostream>
    #include <vector>
    #include <stack>
    
    using namespace std;
    
    struct Node {
        char data;
        int l;
        int r;
    };
    
    vector<Node> tree;
    
    // 非递归前序遍历
    void preorder_iterative(int root) {
        if (root == 0) return;
        
        stack<int> s;
        s.push(root);
        
        while (!s.empty()) {
            int current_index = s.top();
            s.pop();
            
            cout << tree[current_index].data;
            
            // 先压右孩子,再压左孩子
            if (tree[current_index].r != 0) {
                s.push(tree[current_index].r);
            }
            if (tree[current_index].l != 0) {
                s.push(tree[current_index].l);
            }
        }
    }
    
    // 非递归中序遍历
    void inorder_iterative(int root) {
        stack<int> s;
        int current_index = root;
        
        while (current_index != 0 || !s.empty()) {
            // 一路向左,把路径上的节点都压入栈
            while (current_index != 0) {
                s.push(current_index);
                current_index = tree[current_index].l;
            }
            
            // 左边到头了,从栈里弹出一个节点并访问
            current_index = s.top();
            s.pop();
            
            cout << tree[current_index].data;
            
            // 转向右子树
            current_index = tree[current_index].r;
        }
    }
    
    // 后序遍历(递归版,因为非递归版复杂且不常用)
    void postorder_recursive(int index) {
        if (index == 0) return;
        postorder_recursive(tree[index].l);
        postorder_recursive(tree[index].r);
        cout << tree[index].data;
    }
    
    
    int main() {
        // ... (main函数与你的原版完全一样)
        int n;
        cin >> n;
        tree.resize(n + 1);
        for (int i = 1; i <= n; i++) {
            char data;
            int l, r;
            cin >> data >> l >> r;
            tree[i].data = data;
            tree[i].l = l;
            tree[i].r = r;
        }
        int root = 1;
        
        preorder_iterative(root);
        cout << endl;
        
        inorder_iterative(root);
        cout << endl;
        
        postorder_recursive(root); // 保持递归
        cout << endl;
        
        return 0;
    }
    

    总结与教练建议

    • 何时用递归?

      • 当题目数据范围明确保证递归深度不大时(通常是几十到几千,取决于栈大小)。
      • 当算法逻辑用递归表达极其自然,而非递归实现非常复杂时(如大多数树形DP、后序遍历)。
      • 你的原始代码对于 N26N \le 26 的题目是完美且最优的选择,因为它最简洁、最清晰。
    • 何时必须用非递归?

      • 当题目数据范围可能导致很深的递归链时(如 N=105N=10^5 的链状图)。
      • 在一些需要手动控制遍历过程的复杂算法中。
    • 竞赛中的“潜规则”

      • Windows 系统下的默认栈空间通常较小(约 1-2MB),容易栈溢出。
      • Linux 系统下的默认栈空间较大(通常 8MB 或更多),相对安全一些。大多数在线评测系统 (OJ) 是 Linux。
      • 在一些比赛中,可以通过编译指令或代码来手动扩栈,但这不通用。
      • 最稳妥的办法:看到 N105N \ge 10^5 且图可能退化成链时,就要有意识地考虑非递归实现,或者使用 BFS 替代 DFS。
    • 0
      @ 2025-12-4 16:06:57

      1、这个题解里故意留有几个小错误,请自测解决; 2、这里用了递归实现,如果树的高度很深则有可能爆栈内存MLE。(不过因为是log复杂度所以一般树的高度都不会太深)

      #include <bits/stdc++.h>
      using namespace std;
      
      strut Node{
          char data;
          int l;
          int r;
      }
      
      vector<Node> tree;
      
      void preorder(int index){
          if(index==0){
              return;
          }
          cout<<tree[index].data;
          preorder(tree[index].l);
          preorder(tree[index].r);
      }
      
      void postorder(int index){
          if(index==0){
              return;
          }
          
          postorder(tree[index].l);
          postorder(tree[index].r);
          cout<<tree[index].data;
      }
      
      int inorder(int index){
          if(index==0){
              return;
          }
          inorder(tree[index].l);
          cout<<tree[index].data;
          inorder(tree[index].r);
      }
      
      int main(){
          int n;
          cin>>n;
          tree.resize(n+1);
          for(int i=1;i<=n;i++){
              char data;
              int l,r;
              cin>>data>>l>>r;
              tree[i].data=data;
              tree[i].l=l;
              tree[i].r=r;
          }
          int root=1;
          preorder(root);
          cout<<endl;
          inorder(root);
          cout<<endl;
          postorder(root);
          cout<<endl;
          return 0;  
      }
      
      • 0
        @ 2025-12-4 15:53:15

        能用数组下标i, 2i+1的这种线性数组表示法吗?

        绝佳的问题!你提到的这种用 arr[i] 的父子关系是 arr[2*i]arr[2*i+1] 的方法,是一种非常重要且高效的存储方式,但它有一个极其严格的使用前提

        作为你的教练,我来为你彻底剖析这种方法的原理、适用场景以及为什么不能用于解决你当前这道题。


        1. 这种表示法叫什么?

        这种方法通常被称为二叉树的顺序存储结构线性数组表示法。它最经典的应用就是堆 (Heap) 数据结构。

        规则 (以1-based索引为例):

        • 节点 i 的左孩子是 2 * i
        • 节点 i 的右孩子是 2 * i + 1
        • 节点 i 的父节点是 i / 2 (整除)

        (以0-based索引为例):

        • 节点 i 的左孩子是 2 * i + 1
        • 节点 i 的右孩子是 2 * i + 2
        • 节点 i 的父节点是 (i - 1) / 2 (整除)

        2. 它什么时候才能用?

        这种表示法能够成立的唯一前提是:这棵树必须是一棵完全二叉树 (Complete Binary Tree)

        什么是完全二叉树? 一棵二叉树,如果除了最后一层外,其他各层都是满的,并且最后一层的节点都从左到右连续排列,中间没有空缺,那么它就是一棵完全二叉树。

        为什么必须是完全二叉树? 因为 2*i 这个公式是隐式地定义了树的结构。它假设了树的形态是完美且无缝填充的。如果你在数组中存储一棵完全二叉树,它会恰好填满数组的前 n 个位置,没有任何空间浪费

        3. 为什么不能用在你这道题上?

        你这道题的输入格式是 字符 左孩子序号 右孩子序号,它定义的是一棵任意结构的普通二叉树,它几乎不可能是完全二叉树。

        让我们用你的样例来尝试一下,看看会发生什么灾难性的后果。

        样例树的结构:

              1(F)
             /    \
            2(C)   3(E)
           /  \      \
          4(A) 5(D)   6(G)
               /
              7(B)
        

        尝试用线性数组存储: 我们开一个足够大的 char 数组 tree_array,并把根节点 1(F) 放在 tree_array[1]

        1. tree_array[1] = 'F'。它的孩子是 2(C)3(E)。根据公式,它们应该在 [2][3]

          • tree_array[2] = 'C'
          • tree_array[3] = 'E'
          • 到目前为止,看起来还行。
        2. 看节点 2(C),它在 [2]。它的孩子是 4(A)5(D)。根据公式,它们应该在 [4][5]

          • tree_array[4] = 'A'
          • tree_array[5] = 'D'
          • 也还行。
        3. 看节点 3(E),它在 [3]。它的左孩子是 0 (空),右孩子是 6(G)

          • 根据公式,它的孩子应该在 [6][7]
          • 所以 tree_array[6] = 'G',而 tree_array[7] 必须空着!
        4. 灾难来了! 看节点 5(D),它在 [5]。它的左孩子是 7(B),右孩子是 0 (空)。

          • 根据公式,它的孩子应该在 [10][11]
          • 所以我们必须把 'B' 放到 tree_array[10]
          • 数组状态[F, C, E, A, D, G, 空, 空, 空, B, ...]

        你发现了什么问题吗?

        • 巨大的空间浪费:为了存储节点 7(B),我们不得不把数组开到至少 10 的大小,而中间的 [7], [8], [9] 位置都是空的,被白白浪费了。
        • 结构不匹配:输入明确告诉我们 7 号节点是 B,但在我们的数组里,它却被放到了第 10 个位置。输入给出的节点编号和数组下标完全对不上了!

        如果这棵树再“偏”一点,比如是一条长链 1 -> 2 -> 3 -> ... -> n,那么节点 n 会被存储在数组的 2^(n-1) 的位置,空间复杂度会变成指数级!

        4. 总结与教练建议

        存储方法 适用场景 优点 缺点
        vector<struct> 或 平行数组 通用,适用于任何树或图结构 结构灵活,空间利用率高,忠实反映输入 需要额外空间存储左右孩子的索引
        线性数组 (2*i) 仅限完全二叉树 空间极致紧凑 (对完全二叉树而言),无需存孩子索引,父子关系计算快 对非完全二叉树会造成巨大空间浪费,不通用

        给你的建议:

        当你看到题目是通过显式的父子关系(比如 u v 表示边,或者 parent[i])来定义一棵树时,你应该立即放弃使用线性数组 2*i 的表示法。

        请务去使用 vector<struct> 或者 平行数组,因为只有这两种方法才能忠实地、不浪费空间地表示题目给出的任意树结构。

        线性数组 2*i 这种方法,请把它和你脑海中的 “堆” 或者 “完全二叉树” 这两个词绑定在一起。

        • 0
          @ 2025-12-4 15:46:14

          能用数组+下标的表示法存储吗?

          当然可以!你问到的“数组+下标”的表示法,是一种非常经典且在 C 语言时代非常主流的存储方式,我们通常称之为**“平行数组” (Parallel Arrays)** 或 “结构化数组” (Structure of Arrays, SoA)

          这种方法的核心思想是:不用一个结构体把节点的所有信息打包在一起,而是用多个独立的数组,每个数组存储一种信息。同一个下标 i 在所有数组中都代表同一个节点。


          1. 思路与数据结构变化

          vector<struct> 写法 “平行数组”写法 说明
          struct Node { char data; int left; int right; }; char data[N]; data[i] 存节点 i 的值
          vector<Node> tree; int left_child[N]; left_child[i] 存节点 i 的左孩子索引
          int right_child[N]; right_child[i] 存节点 i 的右孩子索引

          这里的 N 是一个预先定义的常量,比如 const int MAXN = 30;,因为题目说了 n <= 26

          2. 代码实现的变化

          a. 读入数据的变化

          原来是:

          tree[i].data = data;
          tree[i].left_child = left;
          tree[i].right_child = right;
          

          现在变成了:

          data[i] = data_char; // 变量名改一下防止冲突
          left_child[i] = left;
          right_child[i] = right;
          

          b. 遍历函数的变化

          原来访问一个节点的属性是 tree[index].属性,现在是 属性数组[index]

          • 前序遍历
            • 原:cout << tree[index].data; -> 新:cout << data[index];
            • 原:pre_order(tree[index].left_child); -> 新:pre_order(left_child[index]);
            • 原:pre_order(tree[index].right_child); -> 新:pre_order(right_child[index]);

          中序和后序遍历的修改方式完全相同。

          3. 完整代码实现 (平行数组版)

          #include <iostream>
          
          using namespace std;
          
          // 预定义最大节点数,比题目要求稍大一点是个好习惯
          const int MAXN = 30;
          
          // 使用三个独立的数组来存储树的信息
          char data[MAXN];      // data[i] 存储节点 i 的字符值
          int left_child[MAXN];   // left_child[i] 存储节点 i 的左孩子索引
          int right_child[MAXN];  // right_child[i] 存储节点 i 的右孩子索引
          
          // 前序遍历
          void pre_order(int index) {
              if (index == 0) {
                  return;
              }
              // 1. 访问根节点
              cout << data[index];
              // 2. 递归遍历左子树
              pre_order(left_child[index]);
              // 3. 递归遍历右子树
              pre_order(right_child[index]);
          }
          
          // 中序遍历
          void in_order(int index) {
              if (index == 0) {
                  return;
              }
              // 1. 递归遍历左子树
              in_order(left_child[index]);
              // 2. 访问根节点
              cout << data[index];
              // 3. 递归遍历右子树
              in_order(right_child[index]);
          }
          
          // 后序遍历
          void post_order(int index) {
              if (index == 0) {
                  return;
              }
              // 1. 递归遍历左子树
              post_order(left_child[index]);
              // 2. 递归遍历右子树
              post_order(right_child[index]);
              // 3. 访问根节点
              cout << data[index];
          }
          
          int main() {
              // 优化IO
              ios::sync_with_stdio(false);
              cin.tie(0);
          
              int n;
              cin >> n;
          
              // 读入 n 行数据,填充三个数组
              for (int i = 1; i <= n; i++) {
                  char data_char;
                  int left, right;
                  cin >> data_char >> left >> right;
                  
                  data[i] = data_char;
                  left_child[i] = left;
                  right_child[i] = right;
              }
          
              int root_index = 1;
          
              // 执行并输出三种遍历结果
              pre_order(root_index);
              cout << endl;
          
              in_order(root_index);
              cout << endl;
          
              post_order(root_index);
              cout << endl;
          
              return 0;
          }
          

          4. 两种方法的对比与教练建议

          特性 vector<struct> (AoS) 平行数组 (SoA) 教练点评
          代码组织性 结构体将一个节点的全部信息封装在一起,逻辑清晰,符合面向对象思想。
          可读性 tree[i].datadata[i] 更能自解释其意。
          扩展性 如果要给节点增加新属性(如父节点、深度),struct 只需加一个成员。平行数组需要新增一个完整的数组。
          内存管理 自动 手动 vector 自动管理内存。数组需要预先定义大小,可能浪费空间或不够用。
          性能 理论上更好 理论上稍差 对于树的遍历,我们通常会连续访问一个节点的多个属性 (data, left, right)。struct 将这些数据在内存中紧凑排列,缓存命中率 (Cache Locality) 更高。但对于本题这种小数据量,性能差异可以完全忽略不计。

          给你的建议:

          1. 首选 vector<struct>:在现代 C++ 竞赛中,vector 配合 struct 是表示图和树的主流且推荐的方法。它更清晰、更安全、更易于维护。
          2. 理解“平行数组”:平行数组是一种非常底层的思想,了解它有助于你理解计算机是如何组织数据的。在某些特定的、需要极致性能优化的场景(例如,你只需要遍历所有节点的 data 属性,而完全不需要 left/right),平行数组因其数据连续性可能反而更快。

          总而言之,两种方法都能正确解决问题。但从代码工程和现代编程实践的角度看,vector<struct> 是更优的选择。

          • 0
            @ 2025-12-4 15:41:31

            使用链表表示的题解

            你好!这是一个非常好的问题。在算法竞赛中,使用 vector 来存储图或树是一种灵活且现代的方法。对于这道二叉树遍历的题目,我们通常需要一个结构体来存储每个节点的信息,然后用 vector 来管理这些节点。

            下面我将分步讲解如何用 vector 来实现,并给出完整的代码和注释。


            1. 思路分析与数据结构设计

            a. 如何表示一个节点?

            一个二叉树节点需要存储三个核心信息:

            1. 节点的值:题目中是字符(如 'F', 'C' 等)。
            2. 左孩子的引用/索引:一个指向左孩子节点的“指针”。
            3. 右孩子的引用/索引:一个指向右孩子节点的“指针”。

            我们可以定义一个 structclass 来表示它:

            struct Node {
                char data;     // 节点的值
                int left_child;  // 左孩子的索引
                int right_child; // 右孩子的索引
            };
            

            这里的 left_childright_child 存储的是孩子节点在 vector 中的下标(索引)。如果题目给的序号是从 1 开始的,我们在代码里通常也习惯用 1-based 索引,这样和输入保持一致。如果为 0,则表示没有孩子。

            b. 如何表示整棵树?

            既然每个节点的信息都打包好了,我们只需要一个容器来存放所有的节点。std::vector 就是完美的选择。

            vector<Node> tree;
            

            tree[i] 就代表了编号为 i 的那个节点的所有信息。

            c. 如何处理输入?

            题目的输入格式是:字符 左孩子序号 右孩子序号。但这个输入的顺序是不固定的,比如第一行输入的可能是 2 号节点的信息,第二行是 5 号节点。

            我们需要一种方法,把字符和它的序号对应起来。

            • 方法一(推荐):由于节点总数 n 不大(<=26),我们可以直接开一个大小为 n+1vector。输入 F 2 3,我们就知道这是 1 号节点的信息(因为它是第一个输入的),我们就把 F 存到 tree[1].data2 存到 tree[1].left_child3 存到 tree[1].right_child

            • 方法二(更通用,但本题没必要):如果节点编号不是按顺序给的,可以用一个 std::map<char, int> 来映射字符到节点编号。

            本题中,输入 n 行,第 i 行(从1开始计数)描述的就是编号为 i 的节点。所以我们可以简化处理。

            2. 遍历算法的实现

            前、中、后序遍历都是基于递归(深度优先搜索)实现的。它们的区别仅仅在于访问根节点(打印 data)的时机。

            我们写一个 dfs 函数,接收当前要遍历的节点索引 index 作为参数。

            • 递归基线 (Base Case):如果 index 为 0,说明这个节点不存在(是空孩子),直接 return

            • 前序遍历 (Pre-order) -> 左 -> 右

              void pre_order(int index) {
                  if (index == 0) return;
                  cout << tree[index].data;    // 1. 访问根
                  pre_order(tree[index].left_child); // 2. 递归左
                  pre_order(tree[index].right_child); // 3. 递归右
              }
              
            • 中序遍历 (In-order):左 -> -> 右

              void in_order(int index) {
                  if (index == 0) return;
                  in_order(tree[index].left_child);   // 1. 递归左
                  cout << tree[index].data;      // 2. 访问根
                  in_order(tree[index].right_child);  // 3. 递归右
              }
              
            • 后序遍历 (Post-order):左 -> 右 ->

              void post_order(int index) {
                  if (index == 0) return;
                  post_order(tree[index].left_child);  // 1. 递归左
                  post_order(tree[index].right_child); // 2. 递归右
                  cout << tree[index].data;       // 3. 访问根
              }
              

            3. 完整代码实现

            下面是将上述思路整合起来的完整 C++ 代码。

            #include <iostream>
            #include <vector>
            #include <string>
            
            using namespace std;
            
            // 定义二叉树节点结构体
            struct Node {
                char data;      // 节点存储的字符值
                int left_child;   // 左孩子的索引,0表示没有
                int right_child;  // 右孩子的索引,0表示没有
            };
            
            // 使用 vector 来存储整棵树,tree[i] 表示编号为 i 的节点
            // 我们使用 1-based 索引,所以大小开 n+1
            vector<Node> tree;
            
            // 前序遍历
            void pre_order(int index) {
                // 递归出口:如果节点索引为0,表示是空节点
                if (index == 0) {
                    return;
                }
                // 1. 访问根节点
                cout << tree[index].data;
                // 2. 递归遍历左子树
                pre_order(tree[index].left_child);
                // 3. 递归遍历右子树
                pre_order(tree[index].right_child);
            }
            
            // 中序遍历
            void in_order(int index) {
                if (index == 0) {
                    return;
                }
                // 1. 递归遍历左子树
                in_order(tree[index].left_child);
                // 2. 访问根节点
                cout << tree[index].data;
                // 3. 递归遍历右子树
                in_order(tree[index].right_child);
            }
            
            // 后序遍历
            void post_order(int index) {
                if (index == 0) {
                    return;
                }
                // 1. 递归遍历左子树
                post_order(tree[index].left_child);
                // 2. 递归遍历右子树
                post_order(tree[index].right_child);
                // 3. 访问根节点
                cout << tree[index].data;
            }
            
            int main() {
                // 优化IO
                ios::sync_with_stdio(false);
                cin.tie(0);
            
                int n;
                cin >> n;
            
                // 调整 vector 大小以容纳 n+1 个节点 (索引从1到n)
                tree.resize(n + 1);
            
                // 读入 n 行数据,构建树
                // 题目的输入格式保证了第 i 次读入的是 i 号节点的信息
                for (int i = 1; i <= n; i++) {
                    char data;
                    int left, right;
                    cin >> data >> left >> right;
                    
                    tree[i].data = data;
                    tree[i].left_child = left;
                    tree[i].right_child = right;
                }
            
                // 题目没有明确说根节点是哪个,但通常二叉树问题默认第一个输入的节点(1号)为根
                int root_index = 1;
            
                // 执行并输出三种遍历结果
                pre_order(root_index);
                cout << endl;
            
                in_order(root_index);
                cout << endl;
            
                post_order(root_index);
                cout << endl;
            
                return 0;
            }
            

            教练划重点

            • 索引 vs 指针:在 C++ 中,我们通常用索引(数组/vector下标)来模拟指针,这比直接使用原生指针 Node* 更安全,也更容易管理内存。
            • 0-based vs 1-based:竞赛中输入的编号通常是 1n。代码内部也使用 1n 的索引(即 1-based)可以减少很多 +1-1 的转换,降低出错概率。代价是 vector 的 0 号位置被浪费了,但这通常无关紧要。
            • 根节点的确定:这道题比较简单,默认 1 号就是根。在更复杂的题目中,你可能需要自己找根(比如,找到那个没有被任何其他节点当作孩子的节点)。

            vectorstruct 来表示树是一种非常基础且重要的技能,一定要熟练掌握!

            • 0
              @ 2025-9-10 0:06:30

              C++ :

              #include <iostream>
              using namespace std;
              typedef struct node{
              	char x;
              	int  l;
              	int  r;
              }node ;
              void preOrder(node * t,int n){
              	cout<<t[n].x;
              	if(t[n].l==0&&t[n].r==0){
              		return ;
              	} 
              	if(t[n].l!=0)preOrder(t,t[n].l);
              	if(t[n].r!=0)preOrder(t,t[n].r);
              	return ;
              }
              void inOrder(node * t,int n){
              	
              	if(t[n].l!=0)inOrder(t,t[n].l);
              	cout<<t[n].x;
              	if(t[n].l==0&&t[n].r==0){
              		return ;
              	} 
              	if(t[n].r!=0)inOrder(t,t[n].r);
              	return ;
              }
              void postOrder(node * t,int n){
              	
              	if(t[n].l!=0)postOrder(t,t[n].l);
              	
              	if(t[n].r!=0)postOrder(t,t[n].r);
              	cout<<t[n].x;
              	if(t[n].l==0&&t[n].r==0){
              		return ;
              	} 
              	return ;
              }
              int main(){
              	int n;
              	cin>>n;
              	node * t = new node [n+1];
              	for(int i=1;i<=n;i++){
              		cin>>t[i].x>>t[i].l>>t[i].r;
              	}
              	
              	//----------------ÏÈÐò±éÀú--------------- 
              	preOrder(t,1);cout<<endl;
              	//----------------ÖÐÐò±éÀú---------------
              	inOrder(t,1);cout<<endl;
              	//----------------ºóÐò±éÀú---------------
              	postOrder(t,1);cout<<endl;
              
              	return 0;
              }
              
              • 1

              信息

              ID
              2681
              时间
              1000ms
              内存
              128MiB
              难度
              10
              标签
              递交数
              1
              已通过
              1
              上传者