
本文介绍一种基于递归的、深度优先的树合并方法,适用于结构相同的嵌套树形数据(如目录结构),通过名称匹配自动合并同名节点,并合并其子节点。
本文介绍一种基于递归的、深度优先的树合并方法,适用于结构相同的嵌套树形数据(如目录结构),通过名称匹配自动合并同名节点,并合并其子节点。
在 Go 中合并两个具有相同结构的树(如 TreeData),核心思想是:按节点 Name 字段进行递归匹配与合并。当两棵树中存在同名节点时,将其子树递归合并;若仅存在于其中一棵树,则直接保留。该方案天然适配您提供的 JSON 树结构,且无需外部依赖,完全使用标准库即可实现。
✅ 合并逻辑说明
- 根节点必须一致(如示例中均为 "."),否则应先校验或定义策略(如抛错/忽略);
- 对每个层级的 Children 切片,遍历 tree1 的子节点,在 tree2 中查找 Name 相同的节点;
- 若找到匹配项,则递归合并二者 Children;否则将 tree1 的子节点原样保留;
- 最后将 tree2 中未被匹配的子节点追加到结果中(确保不丢失数据)。
? 完整可运行代码示例
package main
import (
"encoding/json"
"fmt"
)
type TreeData struct {
Name string `json:"name"`
Depth int `json:"depth"`
Children []TreeData `json:"children,omitempty"`
}
// MergeTrees 深度合并两棵树:以 tree1 为基准,将 tree2 中同名节点的子树合并进来
func MergeTrees(tree1, tree2 TreeData) TreeData {
// 根节点必须一致(可按需调整策略)
if tree1.Name != tree2.Name {
panic(fmt.Sprintf("cannot merge trees with different root names: %q vs %q", tree1.Name, tree2.Name))
}
// 初始化结果树(复用 tree1 的根信息)
result := TreeData{
Name: tree1.Name,
Depth: tree1.Depth,
}
// 构建 children 映射:name → index,便于快速查找
tree2ChildMap := make(map[string]int)
for i, child := range tree2.Children {
tree2ChildMap[child.Name] = i
}
// 遍历 tree1 的子节点,尝试合并
mergedChildren := make([]TreeData, 0, len(tree1.Children)+len(tree2.Children))
usedInTree2 := make(map[string]bool)
for _, c1 := range tree1.Children {
if idx, exists := tree2ChildMap[c1.Name]; exists {
// 名称匹配:递归合并子树
merged := MergeTrees(c1, tree2.Children[idx])
mergedChildren = append(mergedChildren, merged)
usedInTree2[c1.Name] = true
} else {
// tree1 独有:直接保留
mergedChildren = append(mergedChildren, c1)
}
}
// 添加 tree2 中未被使用的子节点(tree2 独有)
for _, c2 := range tree2.Children {
if !usedInTree2[c2.Name] {
mergedChildren = append(mergedChildren, c2)
}
}
result.Children = mergedChildren
return result
}
// 示例用法(含 JSON 解析验证)
func main() {
// 原始 JSON 数据(已补全缺失逗号和字段)
json1 := `{
"name": ".",
"depth": 1,
"children": [
{
"name": "com",
"depth": 2,
"children": [
{
"name": "didi",
"depth": 3,
"children": [
{
"name": "dev",
"depth": 4,
"children": null
}
]
}
]
}
]
}`
json2 := `{
"name": ".",
"depth": 1,
"children": [
{
"name": "com",
"depth": 2,
"children": [
{
"name": "didi",
"depth": 3,
"children": [
{
"name": "influxdb",
"depth": 4,
"children": [
{
"name": "cluster-one",
"depth": 5,
"children": null
}
]
}
]
}
]
}
]
}`
var t1, t2 TreeData
json.Unmarshal([]byte(json1), &t1)
json.Unmarshal([]byte(json2), &t2)
merged := MergeTrees(t1, t2)
out, _ := json.MarshalIndent(merged, "", " ")
fmt.Println(string(out))
}
⚠️ 注意事项
- 字段一致性:Depth 在合并时不参与逻辑判断,仅作为元数据保留。若需智能更新(如取最大深度),可在合并时显式处理;
- 重复名称处理:当前策略假设同层无重名节点(符合典型树结构)。若存在重名但语义不同,需引入唯一 ID 字段替代 Name 匹配;
- 空值安全:Children 使用 omitempty 标签,JSON 序列化时自动省略 nil 切片,避免冗余 "children": null;
- 性能提示:对超大深度/广度树,可考虑改用迭代(栈模拟)避免栈溢出,但一般业务场景递归完全足够。
✅ 总结
该方案简洁、可读性强,充分利用 Go 的结构体嵌套与递归能力,完美支持您描述的树合并需求。它不仅解决了 dev 与 influxdb 的同级共存问题,还保持了原有层级深度与结构语义,是构建配置合并、目录同步、权限树聚合等场景的理想基础组件。











