
在处理具有层级关系的树形数据时,我们经常需要根据某个特定节点的标识(例如 key)来更新其自身以及其所有父级节点的信息。一个典型的场景是,当子节点发生某个事件时,其父级节点也需要相应地汇总或更新状态。然而,在某些情况下,我们可能希望更新操作只影响到指定节点及其上级节点,而排除最顶层的根节点。
考虑以下这种嵌套的JavaScript对象数组结构:
const data = [
{
key: "id1",
name: "Category 1",
curr: 0,
total: 0,
nodes: [
{
key: "id2",
name: "Applications",
curr: 20,
total: 30,
nodes: [
{
key: "id3",
name: "Gaming",
curr: 5,
total: 10,
nodes: []
},
{
key: "id4",
name: "Operating System",
curr: 15,
total: 20,
nodes: []
}
]
}
]
},
{
key: "id5",
name: "Category 2",
curr: 0,
total: 0,
nodes: [
{
key: "id6",
name: "Sub Category",
curr: 12,
total: 48,
nodes: [
{
key: "id7",
name: "Inside Sub",
curr: 12,
total: 48,
nodes: []
}
]
}
]
},
{
key: "id8",
name: "Last One",
curr: 0,
total: 0,
nodes: []
}
];每个节点都包含一个唯一的 key、name、curr(当前值)、total(总值)以及一个 nodes 数组,表示其子节点。我们的目标是,当传入一个 key 时,找到匹配的节点,将其 curr 值递增,并且将其所有父节点的 curr 值也递增,但不递增最顶层(level 0)的节点。
例如,如果调用 incrementRecursively(data, "id4"),预期的输出是 id4 的 curr 从 15 变为 16,其父节点 id2 的 curr 从 20 变为 21。而 id1(level 0)的 curr 保持不变。
直接的递归遍历通常只能在找到目标节点时进行操作,但无法直接通知其父节点进行更新。如果父节点需要根据子节点的状态进行更新,传统的 forEach 遍历或简单的递归函数难以实现这种“自下而上”的更新传播。此外,如何精确控制更新的层级(即排除根节点)也是一个需要巧妙处理的问题。
解决此类问题的关键在于利用递归函数的返回值来向上级传递状态信息,并引入一个深度参数来控制更新的边界。
算法思路:
代码实现:
/**
* 递归更新树形结构中指定节点及其父节点的curr值,排除根节点。
* @param {Array<Object>} nodes - 当前层级的节点数组。
* @param {string} key - 要查找并更新的目标节点的key。
* @param {number} depth - 当前节点的深度,根节点为0。
* @returns {boolean} - 如果当前节点或其子节点中发生了更新,则返回true;否则返回false。
*/
function inc(nodes, key, depth = 0) {
// 遍历当前层级的每一个节点
for (let n of nodes) {
// 检查当前节点的key是否匹配,或者其子节点(如果存在)中是否发生了更新
// n.nodes ?? [] 用于处理nodes可能为null或undefined的情况
if (n.key === key || inc(n.nodes ?? [], key, depth + 1)) {
// 如果当前节点是匹配节点或其祖先节点,并且不是最顶层的根节点(depth > 0)
if (depth > 0) {
n.curr++; // 递增当前节点的curr值
}
// 返回true,向上层调用者表明在其子树中发生了更新
return true;
}
}
// 遍历完当前层级,没有找到匹配项,也没有子节点更新
return false;
}代码解析:
让我们使用上述 inc 函数来更新我们的 data 结构。
const data = [
{
key: "id1",
name: "Category 1",
curr: 0,
total: 0,
nodes: [
{
key: "id2",
name: "Applications",
curr: 20,
total: 30,
nodes: [
{
key: "id3",
name: "Gaming",
curr: 5,
total: 10,
nodes: []
},
{
key: "id4",
name: "Operating System",
curr: 15,
total: 20,
nodes: []
}
]
}
]
},
{
key: "id5",
name: "Category 2",
curr: 0,
total: 0,
nodes: [
{
key: "id6",
name: "Sub Category",
curr: 12,
total: 48,
nodes: [
{
key: "id7",
name: "Inside Sub",
curr: 12,
total: 48,
nodes: []
}
]
}
]
},
{
key: "id8",
name: "Last One",
curr: 0,
total: 0,
nodes: []
}
];
console.log("原始数据:", JSON.stringify(data, null, 2));
// 示例1: 更新 'id4'
inc(data, 'id4');
console.log("\n更新 'id4' 后的数据:", JSON.stringify(data, null, 2));
/* 预期输出:
id4.curr: 15 -> 16
id2.curr: 20 -> 21
id1.curr: 0 (不变,因为 depth > 0 限制)
*/
// 示例2: 更新 'id7'
inc(data, 'id7');
console.log("\n更新 'id7' 后的数据:", JSON.stringify(data, null, 2));
/* 预期输出:
id7.curr: 12 -> 13
id6.curr: 12 -> 13
id5.curr: 0 (不变)
*/
// 示例3: 尝试更新一个不存在的key
inc(data, 'nonExistentKey');
console.log("\n尝试更新不存在的key后的数据 (无变化):", JSON.stringify(data, null, 2));通过结合递归函数的返回值来传递更新状态和深度参数来控制更新范围,我们能够高效且精确地解决在树形结构中,根据指定键值递归更新节点及其祖先节点,同时排除根节点的问题。这种模式在处理复杂嵌套数据结构时非常有用,为树形数据的状态管理提供了一个强大的工具。理解并灵活运用这种递归策略,可以有效简化许多树形数据操作的逻辑。
以上就是递归更新树形结构中指定节点及其父节点的数值(排除根节点)的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号