你新入职一家科技公司,HR 交给你一张组织架构图:总裁下面有 5 位副总裁,每位副总裁下面又有若干个部门经理,每个经理下面还有若干名员工……层级深浅不一,有的人手下几十号人,有的人一个兵也没有。
你的任务是把这张图存进程序,方便随时统计「某位领导手下一共有多少人」。
第一个方案:给每个节点开一个固定长度的儿子数组 son[u][5]——因为看起来最多的副总裁也就 5 个下属。
结果第二天组织架构调整,某位总监一口气划出 12 个小组,数组直接爆掉。而且如果开成 son[u][1000],全公司 10 万人,光儿子数组就要 10 亿个格子,内存直接炸。
第二个方案:用 vector<int> son[u] 变长数组。这确实解决了浪费,但 vector 有额外的容量、指针开销,节点一多常数就上来了;而且每个节点到底有几个儿子这件事,让遍历时的代码处处要判断边界。
就在你抓耳挠腮的时候,一位老工程师走过来,在白板上画了几笔:「每个节点只需要两个指针——第一个孩子是谁,以及下一个兄弟是谁。」
左边连「长子」,右边连「弟弟」——多叉树瞬间变成二叉树!
你惊讶地发现:存储空间从"不确定"变成了固定的两个指针;遍历代码统一成了二叉树的两分支形式;甚至连复杂的树上背包 DP,都能直接套用二叉树的写法。这就是本讲的主角——孩子兄弟表示法。
💡 故事背后的算法思想
① 多叉树的本质困难是「度不确定」。二叉树的每个节点儿子数固定为 2,可以用两个固定字段表示;而多叉树节点的儿子数可能从 0 到 n−1,用定长数组必然浪费或溢出。
② 换个角度看「兄弟」。与其在父节点里罗列所有儿子,不如让儿子们自己串成一条链——每个儿子只知道"我的下一个弟弟是谁"。这样父节点只需要记住长子,长子负责把弟弟们一个个串起来。
③ 两个指针就够。lc[u](左指针)指向 u 的第一个孩子;rs[u](右指针)指向 u 的下一个兄弟。空指针用 0 表示。整棵树被唯一地表示成一棵二叉树。
④ 更重要的是「保序」。这种转换保持了原树的访问顺序:二叉树先序遍历 = 原树先序遍历;二叉树中序遍历 = 原树后序遍历。所以转换不是"信息压缩",而是"换一种等价写法",遍历结果依然可还原。