如何使用 JavaScript 在树中查找节点

我有和对象文字,本质上是一个树,没有一个固定数量的水平。如何在树中搜索特定的节点,然后在 javascript 中以高效的方式找到该节点时返回该节点?

实际上,我有一个这样的树,希望找到标题为‘ Random Node _ 1’的节点

var data = [
{
title: 'topNode',
children: [
{
title: 'node1',
children: [
{
title: 'randomNode_1'
},
{
title: 'node2',
children: [
{
title: 'randomNode_2',
children:[
{
title: 'node2',
children: [
{
title: 'randomNode_3',
}]
}
]
}]
}]
}
]
}];
128562 次浏览

这是一个基本的递归问题。

window.parser = function(searchParam, data) {
if(data.title != searchParam) {
returnData = window.parser(searchParam, children)
} else {
returnData = data;
}


return returnData;
}

必须使用递归。

var currChild = data[0];
function searchTree(currChild, searchString){
if(currChild.title == searchString){
return currChild;
}else if (currChild.children != null){
for(i=0; i < currChild.children.length; i ++){
if (currChild.children[i].title ==searchString){
return currChild.children[i];
}else{
searchTree(currChild.children[i], searchString);
}
}
return null;
}
return null;
}

以@Ravindra 的答案为基础,但使用真正的递归。

function searchTree(element, matchingTitle){
if(element.title == matchingTitle){
return element;
}else if (element.children != null){
var i;
var result = null;
for(i=0; result == null && i < element.children.length; i++){
result = searchTree(element.children[i], matchingTitle);
}
return result;
}
return null;
}

然后你可以称之为:

var element = data[0];
var result = searchTree(element, 'randomNode_1');

这里有一个迭代的解决方案:

var stack = [], node, ii;
stack.push(root);


while (stack.length > 0) {
node = stack.pop();
if (node.title == 'randomNode_1') {
// Found it!
return node;
} else if (node.children && node.children.length) {
for (ii = 0; ii < node.children.length; ii += 1) {
stack.push(node.children[ii]);
}
}
}


// Didn't find it. Return null.
return null;

我这边的情况如下:

function searchTree(data, value) {
if(data.title == value) {
return data;
}
if(data.children && data.children.length > 0) {
for(var i=0; i < data.children.length; i++) {
var node = traverseChildren(data.children[i], value);
if(node != null) {
return node;
}
}
}
return null;

}

我的答案是从 FishBasketGordo 的迭代答案中得到的灵感。它更复杂一些,但也更灵活,您可以拥有多个根节点。

/**searchs through all arrays of the tree if the for a value from a property
* @param aTree : the tree array
* @param fCompair : This function will receive each node. It's upon you to define which
condition is necessary for the match. It must return true if the condition is matched. Example:
function(oNode){ if(oNode["Name"] === "AA") return true; }
* @param bGreedy? : us true to do not stop after the first match, default is false
* @return an array with references to the nodes for which fCompair was true; In case no node was found an empty array
*         will be returned
*/
var _searchTree = function(aTree, fCompair, bGreedy){
var aInnerTree = []; // will contain the inner children
var oNode; // always the current node
var aReturnNodes = []; // the nodes array which will returned


// 1. loop through all root nodes so we don't touch the tree structure
for(keysTree in aTree) {
aInnerTree.push(aTree[keysTree]);
}
while(aInnerTree.length > 0) {
oNode = aInnerTree.pop();
// check current node
if( fCompair(oNode) ){
aReturnNodes.push(oNode);
if(!bGreedy){
return aReturnNodes;
}
} else { // if (node.children && node.children.length) {
// find other objects, 1. check all properties of the node if they are arrays
for(keysNode in oNode){
// true if the property is an array
if(oNode[keysNode] instanceof Array){
// 2. push all array object to aInnerTree to search in those later
for (var i = 0; i < oNode[keysNode].length; i++) {
aInnerTree.push(oNode[keysNode][i]);
}
}
}
}
}
return aReturnNodes; // someone was greedy
}

最后你可以像这样使用函数:

var foundNodes = _searchTree(data, function(oNode){ if(oNode["title"] === "randomNode_3") return true; }, false);
console.log("Node with title found: ");
console.log(foundNodes[0]);

如果你想找到所有带有这个标题的节点,你可以简单地切换 bGreedy 参数:

var foundNodes = _searchTree(data, function(oNode){ if(oNode["title"] === "randomNode_3") return true; }, true);
console.log("NodeS with title found: ");
console.log(foundNodes);

这个函数是通用的,并且是递归搜索。 如果输入树是对象(单个根)或对象数组(许多根对象) ,那么这并不重要。您可以配置道具名,以便在树对象中保存子数组。

// Searches items tree for object with specified prop with value
//
// @param {object} tree nodes tree with children items in nodesProp[] table, with one (object) or many (array of objects) roots
// @param {string} propNodes name of prop that holds child nodes array
// @param {string} prop name of searched node's prop
// @param {mixed} value value of searched node's  prop
// @returns {object/null} returns first object that match supplied arguments (prop: value) or null if no matching object was found


function searchTree(tree, nodesProp, prop, value) {
var i, f = null; // iterator, found node
if (Array.isArray(tree)) { // if entry object is array objects, check each object
for (i = 0; i < tree.length; i++) {
f = searchTree(tree[i], nodesProp, prop, value);
if (f) { // if found matching object, return it.
return f;
}
}
} else if (typeof tree === 'object') { // standard tree node (one root)
if (tree[prop] !== undefined && tree[prop] === value) {
return tree; // found matching node
}
}
if (tree[nodesProp] !== undefined && tree[nodesProp].length > 0) { // if this is not maching node, search nodes, children (if prop exist and it is not empty)
return searchTree(tree[nodesProp], nodesProp, prop, value);
} else {
return null; // node does not match and it neither have children
}
}

我在本地测试了它,它工作正常,但是它不能在 jsfiddle 或 jsbin 上运行... (这些站点上的循环问题? ?)

运行代码:

    var data = [{
title: 'topNode',
children: [{
title: 'node1',
children: [{
title: 'randomNode_1'
}, {
title: 'node2',
children: [{
title: 'randomNode_2',
children: [{
title: 'node2',
children: [{
title: 'randomNode_3',
}]
}]
}]
}]
}]
}];


var r = searchTree(data, 'children', 'title', 'randomNode_1');
//var r = searchTree(data, 'children', 'title', 'node2');  // check it too
console.log(r);

它工作在 http://www.pythontutor.com/live.html#mode=edit(粘贴代码)

灵活的递归解决方案,适用于任何树

// predicate: (item) => boolean
// getChildren: (item) => treeNode[]
searchTree(predicate, getChildren, treeNode) {
function search(treeNode) {
if (!treeNode) {
return undefined;
}


for (let treeItem of treeNode) {
if (predicate(treeItem)) {
return treeItem;
}


const foundItem = search(getChildren(treeItem));


if (foundItem) {
return foundItem;
}
}
}


return search(treeNode);
}

下面是一个使用 Stack 方法的迭代函数,它受到了 戈多的回答的启发,但是利用了一些 ES2015语法来缩短代码。

因为这个问题已经被浏览过很多次了,所以我决定更新我的答案,同时提供一个带参数的函数,使它更加灵活:

function search (tree, value, key = 'id', reverse = false) {
const stack = [ tree[0] ]
while (stack.length) {
const node = stack[reverse ? 'pop' : 'shift']()
if (node[key] === value) return node
node.children && stack.push(...node.children)
}
return null
}

通过这种方式,现在可以传递数据 tree本身、要搜索的所需 value以及可以具有所需值的属性 key:

search(data, 'randomNode_2', 'title')

最后,我的原始答案使用 Array.pop,这导致在多个匹配的情况下匹配最后一个项目。事实上,有些东西可能会让人很困惑。受到 超级评论的启发,我现在让它使用 Array.shift,因此 先进先出行为是默认的。

如果你真的想要旧的 后进先出行为,我已经提供了一个额外的参数 reverse:

search(data, 'randomNode_2', 'title', true)

找到树中元素的所有父元素

let objects = [{
id: 'A',
name: 'ObjA',
children: [
{
id: 'A1',
name: 'ObjA1'
},
{
id: 'A2',
name: 'objA2',
children: [
{
id: 'A2-1',
name: 'objA2-1'
},
{
id: 'A2-2',
name: 'objA2-2'
}
]
}
]
},
{
id: 'B',
name: 'ObjB',
children: [
{
id: 'B1',
name: 'ObjB1'
}
]
}
];


let docs = [
{


object: {
id: 'A',
name: 'docA'
},
typedoc: {
id: 'TD1',
name: 'Typde Doc1'
}
},
{


object: {
id: 'A',
name: 'docA'
},
typedoc: {
id: 'TD2',
name: 'Typde Doc2'
}
},
{


object: {
id: 'A1',
name: 'docA1'
},
typedoc: {
id: 'TDx1',
name: 'Typde Doc x1'
}
},
{


object: {
id: 'A1',
name: 'docA1'
},
typedoc: {
id: 'TDx2',
name: 'Typde Doc x1'
}
},
{


object: {
id: 'A2',
name: 'docA2'
},
typedoc: {
id: 'TDx2',
name: 'Type de Doc x2'
}
},
{


object: {
id: 'A2-1',
name: 'docA2-1'
},
typedoc: {
id: 'TDx2-1',
name: 'Type de Docx2-1'
},
},
{


object: {
id: 'A2-2',
name: 'docA2-2'
},
typedoc: {
id: 'TDx2-2',
name: 'Type de Docx2-2'
},
},
{


object: {
id: 'B',
name: 'docB'
},
typedoc: {
id: 'TD1',
name: 'Typde Doc1'
}
},
{


object: {
id: 'B1',
name: 'docB1'
},
typedoc: {
id: 'TDx1',
name: 'Typde Doc x1'
}


}
];






function buildAllParents(doc, objects) {
for (let o = 0; o < objects.length; o++) {
let allParents = [];
let getAllParents = (o, eleFinded) => {
if (o.id === doc.object.id) {
doc.allParents = allParents;
eleFinded = true;
return { doc, eleFinded };
}
if (o.children) {
allParents.push(o.id);
for (let c = 0; c < o.children.length; c++) {
let { eleFinded, doc } = getAllParents(o.children[c], eleFinded);
if (eleFinded) {
return { eleFinded, doc };
} else {
continue;
}
}


}
return { eleFinded };
};
if (objects[o].id === doc.object.id) {
doc.allParents = [objects[o].id];
return doc;
} else if (objects[o].children) {
allParents.push(objects[o].id);
for (let c = 0; c < objects[o].children.length; c++) {
let eleFinded = null;`enter code here`
let res = getAllParents(objects[o].children[c], eleFinded);
if (res.eleFinded) {
return res.doc;
} else {
continue;
}
}
}


}
}


docs = docs.map(d => buildAllParents(d, objects`enter code here`))

这里有一个更复杂的选项——它在一个类似树的节点中找到第一个条目,并提供(node、 nodeChildrenKey、键/值对和可选的附加键/值对)

const findInTree = (node, childrenKey, key, value,  additionalKey?, additionalValue?) => {
let found = null;
if (additionalKey && additionalValue) {
found = node[childrenKey].find(x => x[key] === value && x[additionalKey] === additionalValue);
} else {
found = node[childrenKey].find(x => x[key] === value);
}
if (typeof(found) === 'undefined') {
for (const item of node[childrenKey]) {
if (typeof(found) === 'undefined' && item[childrenKey] && item[childrenKey].length > 0) {
found = findInTree(item, childrenKey, key, value, additionalKey, additionalValue);
}
}
}
return found;
};


export { findInTree };

希望能帮到别人。

这是一个迭代广度优先搜索。它返回包含给定名称(nodeName)和给定值(nodeValue)的子节点的第一个节点。

getParentNode(nodeName, nodeValue, rootNode) {
const queue= [ rootNode ]
while (queue.length) {
const node = queue.shift()
if (node[nodeName] === nodeValue) {
return node
} else if (node instanceof Object) {
const children = Object.values(node)
if (children.length) {
queue.push(...children)
}
}
}
return null
}

它可以像这样用来解决最初的问题:

getParentNode('title', 'randomNode_1', data[0])

ES6 + :

const deepSearch = (data, value, key = 'title', sub = 'children', tempObj = {}) => {
if (value && data) {
data.find((node) => {
if (node[key] == value) {
tempObj.found = node;
return node;
}
return deepSearch(node[sub], value, key, sub, tempObj);
});
if (tempObj.found) {
return tempObj.found;
}
}
return false;
};


const result = deepSearch(data, 'randomNode_1', 'title', 'children');

基于“ 埃里克 · 佩特鲁塞利”的代码增强

  1. 删除“反向”选项
  2. 添加多根支持
  3. 添加一个选项来控制“子”的可见性
  4. 打字机准备好了
  5. 单元测试准备就绪
function searchTree(
tree: Record<string, any>[],
value: unknown,
key = 'value',
withChildren = false,
) {
let result = null;
if (!Array.isArray(tree)) return result;


for (let index = 0; index < tree.length; index += 1) {
const stack = [tree[index]];
while (stack.length) {
const node = stack.shift()!;
if (node[key] === value) {
result = node;
break;
}
if (node.children) {
stack.push(...node.children);
}
}
if (result) break;
}
if (withChildren !== true) {
delete result?.children;
}


return result;
}

测试可以在 https://gist.github.com/aspirantzhang/a369aba7f84f26d57818ddef7d108682找到

没有废话版本:

const find = (root, title) =>
root.title === title ?
root :
root.children?.reduce((result, n) => result || find(n, title), undefined)

根据我的需要又写了一本

  1. 注射。
  2. 找到分支的路径可用
  3. 当前路径可以在条件语句中使用
  4. 可用于将树项目映射到另一个对象
// if predicate returns true, the search is stopped
function traverse2(tree, predicate, path = "") {
if (predicate(tree, path)) return true;
for (const branch of tree.children ?? [])
if (traverse(branch, predicate, `${path ? path + "/" : ""}${branch.name}`))
return true;
}

例子

let tree = {
name: "schools",
children: [
{
name: "farzanegan",
children: [
{
name: "classes",
children: [
{ name: "level1", children: [{ name: "A" }, { name: "B" }] },
{ name: "level2", children: [{ name: "C" }, { name: "D" }] },
],
},
],
},
{ name: "dastgheib", children: [{ name: "E" }, { name: "F" }] },
],
};


traverse(tree, (branch, path) => {
console.log("searching ", path);
if (branch.name === "C") {
console.log("found ", branch);
return true;
}
});


输出

searching
searching  farzanegan
searching  farzanegan/classes
searching  farzanegan/classes/level1
searching  farzanegan/classes/level1/A
searching  farzanegan/classes/level1/B
searching  farzanegan/classes/level2
searching  farzanegan/classes/level2/C
found  { name: 'C' }

在树上找到一个节点:

假设我们有一棵树

let tree = [{
id: 1,
name: 'parent',
children: [
{
id: 2,
name: 'child_1'
},
{
id: 3,
name: 'child_2',
children: [
{
id: '4',
name: 'child_2_1',
children: []
},
{
id: '5',
name: 'child_2_2',
children: []
}
]
}
]
}];




function findNodeById(tree, id) {


let result = null
if (tree.id === id) {
return tree;
}


if (Array.isArray(tree.children) && tree.children.length > 0) {
tree.children.some((node) => {
result = findNodeById(node, id);
return result;
});
}
return result;}

在2022年使用 TypeScript 和 ES5

只需使用基本的重新创建和内置的数组方法在数组上循环。不要使用 Array.find(),因为它会返回错误的节点。使用 Array.some()代替,它允许您中断循环。

 interface iTree {
id: string;
children?: iTree[];
}


function findTreeNode(tree: iTree, id: string) {
let result: iTree | null = null;


if (tree.id === id) {
result = tree;
} else if (tree.children) {
tree.children.some((node) => {
result = findTreeNode(node, id);
return result; // break loop
});
}


return result;
}

const flattenTree = (data: any) => {
return _.reduce(
data,
(acc: any, item: any) => {
acc.push(item);
if (item.children) {
acc = acc.concat(flattenTree(item.children));
delete item.children;
}
return acc;
},
[]
);
};

将嵌套树转换为深度为0的对象的方法。 我们可以像这样在对象中转换对象,并且可以更容易地执行搜索。